Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ config:
remove: boolean value, if true the annotation is removed, if false the annotation is added or changed, optional
```

### alterFinalizer
Adds or removes a finalizer.
```yaml
config:
key: the finalizer key to add or remove, required
remove: boolean value, if true the finalizer is removed, if false the finalizer is added, optional
```

### alterLabel
Adds, changes or removes a label.
```yaml
Expand Down
45 changes: 45 additions & 0 deletions plugin/impl/finalizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0

package impl

import (
"github.com/sapcc/ucfgwrap"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"github.com/sapcc/maintenance-controller/plugin"
)

// AlterFinalizer is a trigger plugin, which can alter properties of the Finalizer CRO of the node.
type AlterFinalizer struct {
Key string
Remove bool
}

// New creates a new AlterFinalizer instance with the given config.
func (a *AlterFinalizer) New(config *ucfgwrap.Config) (plugin.Trigger, error) {
conf := struct {
Key string `config:"key" validate:"required"`
Remove bool `config:"remove"`
}{}
if err := config.Unpack(&conf); err != nil {
return nil, err
}
return &AlterFinalizer{Key: conf.Key, Remove: conf.Remove}, nil
}

func (a *AlterFinalizer) ID() string {
return "alterFinalizer"
}

// Trigger ensures the Finalizer with the provided key is removed if removes is set to true.
// Otherwise, it sets the Finalizer with the provided key if required.
func (a *AlterFinalizer) Trigger(params plugin.Parameters) error {
if !a.Remove {
controllerutil.AddFinalizer(params.Node, a.Key)
return nil
}

controllerutil.RemoveFinalizer(params.Node, a.Key)
return nil
}
76 changes: 76 additions & 0 deletions plugin/impl/finalizer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0

package impl

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/sapcc/ucfgwrap"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/sapcc/maintenance-controller/plugin"
)

var _ = Describe("The Finalizer plugin", func() {
Describe("AlterFinalizer", func() {
It("fails parsing incorrect config", func() {
_, err := ucfgwrap.FromYAML([]byte("invalid_yaml"))
errMsg := "type 'string' is not supported on top level of config, only dictionary or list"
Expect(err).To(MatchError(errMsg))

config, err := ucfgwrap.FromYAML([]byte("value: test"))
Expect(err).To(Not(HaveOccurred()))

var base AlterFinalizer
_, err = base.New(&config)
Expect(err).To(MatchError("string value is not set accessing 'key'"))
})

It("has valid configuration", func() {
config, err := ucfgwrap.FromYAML([]byte("key: test.com/finalizer\nremove: true"))
Expect(err).To(Not(HaveOccurred()))

var base AlterFinalizer
plugin, err := base.New(&config)
Expect(err).To(Succeed())
Expect(plugin).To(Equal(&AlterFinalizer{
Key: "test.com/finalizer",
Remove: true,
}))
})

It("adds finalizer when not present", func() {
config, err := ucfgwrap.FromYAML([]byte("key: test.com/finalizer\nremove: false"))
Expect(err).To(Not(HaveOccurred()))

var base AlterFinalizer
addFinalizer, err := base.New(&config)
Expect(err).To(Not(HaveOccurred()))

testNode := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node"}}
err = addFinalizer.Trigger(plugin.Parameters{Node: testNode})
Expect(err).To(Not(HaveOccurred()))
Expect(testNode.Finalizers).To(ContainElement("test.com/finalizer"))
})

It("removes finalizer when present", func() {
testNode := &v1.Node{ObjectMeta: metav1.ObjectMeta{
Name: "test-node",
Finalizers: []string{"test.com/finalizer"},
}}

config, err := ucfgwrap.FromYAML([]byte("key: test.com/finalizer\nremove: true"))
Expect(err).To(Succeed())

var base AlterFinalizer
removeFinalizer, err := base.New(&config)
Expect(err).To(Not(HaveOccurred()))

err = removeFinalizer.Trigger(plugin.Parameters{Node: testNode})
Expect(err).To(Not(HaveOccurred()))
Expect(testNode.Finalizers).ToNot(ContainElement("test.com/finalizer"))
})
})
})
Loading