diff --git a/images/hooks/cmd/virtualization-module-hooks/register.go b/images/hooks/cmd/virtualization-module-hooks/register.go index 5912430cec..fb83002026 100644 --- a/images/hooks/cmd/virtualization-module-hooks/register.go +++ b/images/hooks/cmd/virtualization-module-hooks/register.go @@ -20,6 +20,7 @@ import ( _ "hooks/pkg/hooks/copy-custom-certificate" _ "hooks/pkg/hooks/discovery-clusterip-service-for-dvcr" _ "hooks/pkg/hooks/discovery-workload-nodes" + _ "hooks/pkg/hooks/drop-openshift-labels" _ "hooks/pkg/hooks/generate-secret-for-dvcr" _ "hooks/pkg/hooks/migrate-delete-renamed-validation-admission-policy" _ "hooks/pkg/hooks/migrate-virthandler-kvm-node-labels" diff --git a/images/hooks/pkg/hooks/drop-openshift-labels/hook.go b/images/hooks/pkg/hooks/drop-openshift-labels/hook.go new file mode 100644 index 0000000000..b6f255d83c --- /dev/null +++ b/images/hooks/pkg/hooks/drop-openshift-labels/hook.go @@ -0,0 +1,103 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package drop_openshift_labels + +import ( + "context" + "fmt" + "strings" + + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/pkg/registry" + "k8s.io/utils/ptr" + + "hooks/pkg/settings" +) + +const ( + moduleNamespace = "virtualization-module-namespace" +) + +const ( + openshiftClusterMonitoringLabel = "openshift.io/cluster-monitoring" +) + +// TODO: Delete me after v1 +var _ = registry.RegisterFunc(configModuleNamespace, handlerModuleNamespace) + +var configModuleNamespace = &pkg.HookConfig{ + OnAfterHelm: &pkg.OrderedConfig{Order: 5}, + Kubernetes: []pkg.KubernetesConfig{ + { + Name: moduleNamespace, + APIVersion: "v1", + Kind: "Namespace", + JqFilter: ".metadata", + + NameSelector: &pkg.NameSelector{ + MatchNames: []string{settings.ModuleNamespace}, + }, + ExecuteHookOnEvents: ptr.To(false), + }, + }, + + Queue: fmt.Sprintf("modules/%s", settings.ModuleName), +} + +type NamespaceMetadata struct { + Name string `json:"name"` + Labels map[string]string `json:"labels"` +} + +func handlerModuleNamespace(_ context.Context, input *pkg.HookInput) error { + snaps := input.Snapshots.Get(moduleNamespace) + if len(snaps) == 0 { + input.Logger.Debug("no namespace found") + return nil + } + ns := &NamespaceMetadata{} + err := snaps[0].UnmarshalTo(ns) + if err != nil { + input.Logger.Error("failed to unmarshal namespace", "error", err) + return err + } + + if _, exist := ns.Labels[openshiftClusterMonitoringLabel]; !exist { + input.Logger.Debug("no labels to update") + return nil + } + + patch := []map[string]interface{}{ + {"op": "remove", "path": fmt.Sprintf("/metadata/labels/%s", jsonPatchEscape(openshiftClusterMonitoringLabel)), "value": nil}, + } + input.PatchCollector.PatchWithJSON( + patch, + "v1", + "Namespace", + "", + settings.ModuleNamespace, + ) + + input.Logger.Debug(fmt.Sprintf("Added patch to PatchCollector for replace labels on %q namespace", settings.ModuleNamespace)) + input.Logger.Debug("Hook finished") + + return nil +} + +func jsonPatchEscape(s string) string { + return strings.NewReplacer("~", "~0", "/", "~1").Replace(s) +} diff --git a/images/hooks/pkg/hooks/drop-openshift-labels/hook_test.go b/images/hooks/pkg/hooks/drop-openshift-labels/hook_test.go new file mode 100644 index 0000000000..f35cdafbbb --- /dev/null +++ b/images/hooks/pkg/hooks/drop-openshift-labels/hook_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package drop_openshift_labels + +import ( + "context" + "fmt" + "testing" + + "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/testing/mock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "hooks/pkg/settings" +) + +func TestDropOpenshiftLabels(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Drop openshift labels Suite") +} + +var _ = Describe("Drop openshift labels", func() { + var ( + snapshots *mock.SnapshotsMock + patchCollector *mock.PatchCollectorMock + ) + + newInput := func() *pkg.HookInput { + return &pkg.HookInput{ + Snapshots: snapshots, + PatchCollector: patchCollector, + Logger: log.NewNop(), + } + } + + newSnapshot := func(with bool) pkg.Snapshot { + return mock.NewSnapshotMock(GinkgoT()).UnmarshalToMock.Set(func(v any) (err error) { + obj, ok := v.(*NamespaceMetadata) + Expect(ok).To(BeTrue()) + obj.Name = settings.ModuleNamespace + if with { + if obj.Labels == nil { + obj.Labels = make(map[string]string) + } + obj.Labels[openshiftClusterMonitoringLabel] = "true" + } + return nil + }) + } + + setSnapshots := func(snaps ...pkg.Snapshot) { + snapshots.GetMock.When(moduleNamespace).Then(snaps) + } + + BeforeEach(func() { + snapshots = mock.NewSnapshotsMock(GinkgoT()) + patchCollector = mock.NewPatchCollectorMock(GinkgoT()) + }) + + It("Should drop label", func() { + setSnapshots(newSnapshot(true)) + patchCollector.PatchWithJSONMock.Set(func(patch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + Expect(apiVersion).To(Equal("v1")) + Expect(kind).To(Equal("Namespace")) + Expect(namespace).To(Equal("")) + Expect(name).To(Equal(settings.ModuleNamespace)) + Expect(opts).To(HaveLen(0)) + + jsonPatch, ok := patch.([]map[string]interface{}) + Expect(ok).To(BeTrue()) + Expect(jsonPatch).To(HaveLen(1)) + Expect(jsonPatch[0]["op"]).To(Equal("remove")) + Expect(jsonPatch[0]["path"]).To(Equal(fmt.Sprintf("/metadata/labels/%s", jsonPatchEscape(openshiftClusterMonitoringLabel)))) + Expect(jsonPatch[0]["value"]).To(BeNil()) + }) + + Expect(handlerModuleNamespace(context.Background(), newInput())).To(Succeed()) + }) + It("Should do nothing", func() { + setSnapshots(newSnapshot(false)) + Expect(handlerModuleNamespace(context.Background(), newInput())).To(Succeed()) + }) +})