Skip to content
Open
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
24 changes: 21 additions & 3 deletions api/v1beta2/watcher_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ type WatcherSpec struct {
// ServiceInfo describes the service information of the listener
ServiceInfo Service `json:"serviceInfo"`

// Manager specifies the component responsible for handling webhook requests.
// If not set, falls back to the 'operator.kyma-project.io/managed-by' label.
// +optional
Manager string `json:"manager,omitempty"`

// LabelsToWatch describes the labels that should be watched
// +optional
LabelsToWatch map[string]string `json:"labelsToWatch,omitempty"`
Expand Down Expand Up @@ -118,12 +123,25 @@ type Watcher struct {
// 2. In the KCP, it is used as `http.match.uri.prefix` in the VirtualService to route the incoming
// webhook request to the appropriate manager service.
//
// The method uses the following priority:
// 1. First, it checks the spec.Manager field (preferred, explicit configuration)
// 2. If not set, it falls back to the 'operator.kyma-project.io/managed-by' label (backward compatibility)
// 3. If neither is set, it returns an empty string
//
// Consistency in this name ensures correct routing and handling of webhook validation logic.
func (watcher *Watcher) GetManagerName() string {
if watcher.Labels == nil {
return ""
// Priority 1: Use explicit spec.Manager field if set
if watcher.Spec.Manager != "" {
return watcher.Spec.Manager
}
return watcher.Labels[shared.ManagedBy]

// Priority 2: Fall back to managed-by label for backward compatibility
if watcher.Labels != nil {
return watcher.Labels[shared.ManagedBy]
}

// Priority 3: Return empty string if neither is configured
return ""
}

// +kubebuilder:object:root=true
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions config/crd/bases/operator.kyma-project.io_watchers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ spec:
type: string
description: LabelsToWatch describes the labels that should be watched
type: object
manager:
description: |-
Manager specifies the component responsible for handling webhook requests.
If not set, falls back to the 'operator.kyma-project.io/managed-by' label.
type: string
resourceToWatch:
description: ResourceToWatch is the GroupVersionResource of the resource
that should be watched.
Expand Down
1 change: 1 addition & 0 deletions config/watcher/kyma_watcher.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ metadata:
labels:
"operator.kyma-project.io/managed-by": "lifecycle-manager"
spec:
manager: "lifecycle-manager"
labelsToWatch:
"operator.kyma-project.io/watched-by": "kyma"
resourceToWatch:
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1409,8 +1409,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyma-project/runtime-watcher/listener v1.2.0 h1:36e1C2SkGT9fddJhVgaS+617YxUQ0uJBX0LaYmnZstQ=
github.com/kyma-project/runtime-watcher/listener v1.2.0/go.mod h1:nb6IFwNxZIkdTY7k1C7vb5aqILLwDtwiX6FNlj7CYnE=
github.com/kyma-project/runtime-watcher/listener v1.3.0 h1:cpwqKSe092PB7PY9uEKovP2KHLf5xSU2ZnCQzSrEPl8=
github.com/kyma-project/runtime-watcher/listener v1.3.0/go.mod h1:61P0hFo4pRHQACz4MgyXVX35Sm5zDPGaBeomB8i+30Q=
github.com/kyma-project/template-operator/api v1.0.0 h1:tIQ83GfmVdmWVBRLYTOHTZJ45j32quYRUcmyhyWn5+s=
Expand Down
3 changes: 1 addition & 2 deletions internal/controller/manifest/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"strings"

watcherevent "github.com/kyma-project/runtime-watcher/listener/pkg/v2/event"
"github.com/kyma-project/runtime-watcher/listener/pkg/v2/types"
Expand Down Expand Up @@ -64,7 +63,7 @@ func SetupWithManager(mgr manager.Manager,
}

runnableListener := watcherevent.NewSKREventListener(
settings.ListenerAddr, strings.ToLower(shared.OperatorName),
settings.ListenerAddr, shared.OperatorName,
verifyFunc,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func createWatcherCR(managerInstanceName string, statusOnly bool) *v1beta2.Watch
},
},
Spec: v1beta2.WatcherSpec{
Manager: managerInstanceName,
ServiceInfo: v1beta2.Service{
Port: 8082,
Name: managerInstanceName + "-svc",
Expand Down
113 changes: 113 additions & 0 deletions tests/integration/watcher/manager_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package watcher_test

import (
"bytes"
"os"
"path/filepath"

"gopkg.in/yaml.v3"
apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/kyma-project/lifecycle-manager/api/shared"
"github.com/kyma-project/lifecycle-manager/api/v1beta2"
"github.com/kyma-project/lifecycle-manager/tests/integration"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Watcher Manager Configuration", func() {
Context("kyma_watcher.yaml configuration", func() {
It("should have spec.manager equal to shared.OperatorName", func() {
// Read the kyma_watcher.yaml file
projectRoot := integration.GetProjectRoot()
watcherFilePath := filepath.Join(projectRoot, "config", "watcher", "kyma_watcher.yaml")

fileContent, err := os.ReadFile(watcherFilePath)
Expect(err).ToNot(HaveOccurred(), "should be able to read kyma_watcher.yaml")

// Parse YAML - handle multiple documents
decoder := yaml.NewDecoder(bytes.NewReader(fileContent))
var watcherFound bool

for {
var doc map[string]interface{}
err := decoder.Decode(&doc)
if err != nil {
break // End of documents
}

// Check if this document is a Watcher
kind, hasKind := doc["kind"].(string)
if !hasKind || kind != "Watcher" {
continue
}

// Parse the Watcher CR
var watcher v1beta2.Watcher
yamlBytes, err := yaml.Marshal(doc)
Expect(err).ToNot(HaveOccurred())

err = yaml.Unmarshal(yamlBytes, &watcher)

Check failure on line 51 in tests/integration/watcher/manager_config_test.go

View workflow job for this annotation

GitHub Actions / lint

the given struct should be annotated with the `yaml` tag (musttag)
Expect(err).ToNot(HaveOccurred(), "should be able to parse Watcher CR")

watcherFound = true

// Verify that spec.manager is set to shared.OperatorName
Expect(watcher.Spec.Manager).To(Equal(shared.OperatorName),
"spec.manager should be set to '%s' to ensure consistent routing configuration",
shared.OperatorName)

// Additional verification: GetManagerName() should return the same value
Expect(watcher.GetManagerName()).To(Equal(shared.OperatorName),
"GetManagerName() should return '%s'", shared.OperatorName)

break
}

Expect(watcherFound).To(BeTrue(), "should find a Watcher CR in kyma_watcher.yaml")
})

It("should have consistent manager configuration between spec field and GetManagerName()", func() {
// Test that GetManagerName() prioritizes spec.Manager over label
watcher := &v1beta2.Watcher{
ObjectMeta: apimetav1.ObjectMeta{},
Spec: v1beta2.WatcherSpec{
Manager: "lifecycle-manager",
},
}

// When spec.Manager is set, it should be returned
Expect(watcher.GetManagerName()).To(Equal("lifecycle-manager"))

// Test backward compatibility: when spec.Manager is empty, fall back to label
watcherWithLabel := &v1beta2.Watcher{
ObjectMeta: apimetav1.ObjectMeta{
Labels: map[string]string{
shared.ManagedBy: "fallback-manager",
},
},
Spec: v1beta2.WatcherSpec{
Manager: "", // Empty, should fall back to label
},
}

Expect(watcherWithLabel.GetManagerName()).To(Equal("fallback-manager"))

// Test priority: spec.Manager takes precedence over label
watcherWithBoth := &v1beta2.Watcher{
ObjectMeta: apimetav1.ObjectMeta{
Labels: map[string]string{
shared.ManagedBy: "label-manager",
},
},
Spec: v1beta2.WatcherSpec{
Manager: "spec-manager",
},
}

Expect(watcherWithBoth.GetManagerName()).To(Equal("spec-manager"),
"spec.Manager should take precedence over the label")
})
})
})
Loading