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
12 changes: 11 additions & 1 deletion api/v1beta1/eventtrigger_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type GeneratorReference struct {
// - `.Cluster.metadata.namespace`: namespace of the managed cluster
// - `.Cluster.metadata.name`: name of the managed cluster
// - `.Cluster.kind`: kind of the managed cluster object
// - event data
// +kubebuilder:validation:MinLength=1
InstantiatedResourceNameFormat string `json:"nameFormat"`

Expand Down Expand Up @@ -117,6 +118,15 @@ type EventTriggerSpec struct {
// +optional
OneForEvent bool `json:"oneForEvent,omitempty"`

// InstantiatedProfileNameFormat defines a template used to generate the name
// of the ClusterProfile created in the management cluster. The template can reference:
// - `.Cluster.metadata.namespace`: namespace of the managed cluster
// - `.Cluster.metadata.name`: name of the managed cluster
// - `.Cluster.kind`: kind of the managed cluster object
// - event data
// +optional
InstantiatedProfileNameFormat string `json:"profileNameFormat,omitempty"`

// EventSourceName is the name of the referenced EventSource.
// Resources contained in the referenced ConfigMaps/Secrets and HelmCharts
// will be customized using information from resources matching the EventSource
Expand Down Expand Up @@ -223,6 +233,7 @@ type EventTriggerSpec struct {
// In any managed cluster that matches this ClusterProfile, the add-ons and applications
// defined in this instance will not be deployed until all add-ons and applications in the
// ClusterProfiles listed as dependencies are deployed.
// This field will be directly transferred to the ClusterProfile Spec
DependsOn []string `json:"dependsOn,omitempty"`

// TemplateResourceRefs is a list of resource to collect from the management cluster.
Expand All @@ -231,7 +242,6 @@ type EventTriggerSpec struct {
// +patchMergeKey=identifier
// +patchStrategy=merge,retainKeys
// This field will be directly transferred to the ClusterProfile Spec
// generated in response to events.
// +optional
TemplateResourceRefs []configv1beta1.TemplateResourceRef `json:"templateResourceRefs,omitempty"`

Expand Down
13 changes: 12 additions & 1 deletion config/crd/bases/lib.projectsveltos.io_eventtriggers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ spec:
- `.Cluster.metadata.namespace`: namespace of the managed cluster
- `.Cluster.metadata.name`: name of the managed cluster
- `.Cluster.kind`: kind of the managed cluster object
- event data
minLength: 1
type: string
namespace:
Expand Down Expand Up @@ -113,6 +114,7 @@ spec:
In any managed cluster that matches this ClusterProfile, the add-ons and applications
defined in this instance will not be deployed until all add-ons and applications in the
ClusterProfiles listed as dependencies are deployed.
This field will be directly transferred to the ClusterProfile Spec
items:
type: string
type: array
Expand Down Expand Up @@ -994,6 +996,15 @@ spec:
type: object
type: array
x-kubernetes-list-type: atomic
profileNameFormat:
description: |-
InstantiatedProfileNameFormat defines a template used to generate the name
of the ClusterProfile created in the management cluster. The template can reference:
- `.Cluster.metadata.namespace`: namespace of the managed cluster
- `.Cluster.metadata.name`: name of the managed cluster
- `.Cluster.kind`: kind of the managed cluster object
- event data
type: string
reloader:
default: false
description: |-
Expand Down Expand Up @@ -1028,6 +1039,7 @@ spec:
- `.Cluster.metadata.namespace`: namespace of the managed cluster
- `.Cluster.metadata.name`: name of the managed cluster
- `.Cluster.kind`: kind of the managed cluster object
- event data
minLength: 1
type: string
namespace:
Expand Down Expand Up @@ -1140,7 +1152,6 @@ spec:
Those resources' values will be used to instantiate templates contained in referenced
PolicyRefs and Helm charts
This field will be directly transferred to the ClusterProfile Spec
generated in response to events.
items:
properties:
identifier:
Expand Down
84 changes: 74 additions & 10 deletions controllers/eventtrigger_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,8 @@ func instantiateClusterProfileForResource(ctx context.Context, c client.Client,
}
labels = appendServiceAccountLabels(eventTrigger, labels)

clusterProfileName, err := getClusterProfileName(ctx, c, labels)
clusterProfileName, err := getClusterProfileName(ctx, c, clusterNamespace, clusterName,
eventTrigger, object, labels, logger)
if err != nil {
logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get ClusterProfile name: %v", err))
return nil, err
Expand Down Expand Up @@ -1417,7 +1418,8 @@ func instantiateOneClusterProfilePerAllResource(ctx context.Context, c client.Cl
eventReport, clusterType)
labels = appendServiceAccountLabels(eventTrigger, labels)

clusterProfileName, err := getClusterProfileName(ctx, c, labels)
clusterProfileName, err := getClusterProfileName(ctx, c, clusterNamespace, clusterName,
eventTrigger, objects, labels, logger)
if err != nil {
logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get ClusterProfile name: %v", err))
return nil, err
Expand Down Expand Up @@ -2117,25 +2119,87 @@ func getClusterRef(clusterNamespace, clusterName string,
// getClusterProfileName returns the name for a given ClusterProfile given the labels such ClusterProfile
// should have. It also returns whether the ClusterProfile must be created (if create a false, ClusterProfile
// should be simply updated). And an error if any occurs.
func getClusterProfileName(ctx context.Context, c client.Client, labels map[string]string,
func getClusterProfileName(ctx context.Context, c client.Client, clusterNamespace, clusterName string,
eventTrigger *v1beta1.EventTrigger, data any, labels map[string]string, logger logr.Logger,
) (name string, err error) {

listOptions := []client.ListOption{
client.MatchingLabels(labels),
}

clusterProfileList := &configv1beta1.ClusterProfileList{}
err = c.List(ctx, clusterProfileList, listOptions...)
if eventTrigger.Spec.InstantiatedProfileNameFormat == "" {
clusterProfileList := &configv1beta1.ClusterProfileList{}
err = c.List(ctx, clusterProfileList, listOptions...)
if err != nil {
return "", err
}

objects := make([]client.Object, len(clusterProfileList.Items))
for i := range clusterProfileList.Items {
objects[i] = &clusterProfileList.Items[i]
}

return getInstantiatedObjectName(ctx, c, objects)
}

templateName := getTemplateName(clusterNamespace, clusterName, eventTrigger.Name)
instantiatedName, err := instantiateSection(templateName, []byte(eventTrigger.Spec.InstantiatedProfileNameFormat),
data, funcmap.HasTextTemplateAnnotation(eventTrigger.Annotations), logger)
if err != nil {
return
return "", err
}

objects := make([]client.Object, len(clusterProfileList.Items))
for i := range clusterProfileList.Items {
objects[i] = &clusterProfileList.Items[i]
// If the profile exists, make sure this is owned by this EventTrigger instance. Fail it otherwise
clusterProfile := &configv1beta1.ClusterProfile{}
err = c.Get(ctx, types.NamespacedName{Name: templateName}, clusterProfile)
if err != nil {
if apierrors.IsNotFound(err) {
return string(instantiatedName), nil
}
return "", err
}

return getInstantiatedObjectName(ctx, c, objects)
err = validateClusterProfileLabels(clusterProfile, eventTrigger.Name, clusterNamespace,
clusterName, string(instantiatedName))
if err != nil {
return "", err
}

return string(instantiatedName), nil
}

// validateLabels checks if the ClusterProfile has the exact set of expected labels.
func validateClusterProfileLabels(clusterProfile *configv1beta1.ClusterProfile,
eventTriggerName, clusterNamespace, clusterName, instantiatedName string) error {

expectedLabels := map[string]string{
eventTriggerNameLabel: eventTriggerName,
clusterNamespaceLabel: clusterNamespace,
clusterNameLabel: clusterName,
}

currentLabels := clusterProfile.Labels
if currentLabels == nil {
return fmt.Errorf("ClusterProfile %s does not have the expected labels indicating EventTrigger %s owns it",
instantiatedName, eventTriggerName)
}

// First, check if the number of labels matches. This is a quick pre-check.
if len(currentLabels) != len(expectedLabels) {
return fmt.Errorf("ClusterProfile %s has an incorrect number of labels. Expected %d, got %d",
instantiatedName, len(expectedLabels), len(currentLabels))
}

// Then, iterate and validate each expected label.
for k, v := range expectedLabels {
currentV, ok := currentLabels[k]
if !ok || currentV != v {
return fmt.Errorf("ClusterProfile %s does not have the labels indicating EventTrigger %s owns it",
instantiatedName, eventTriggerName)
}
}

return nil
}

func getResourceName(ctx context.Context, c client.Client, ref client.Object,
Expand Down
87 changes: 84 additions & 3 deletions controllers/eventtrigger_deployer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1155,14 +1155,93 @@ status:
},
}

eventTrigger := &v1beta1.EventTrigger{
ObjectMeta: metav1.ObjectMeta{Name: eventTriggerName},
}

c := fake.NewClientBuilder().WithScheme(scheme).Build()

labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName,
eventReport, clusterType)

name, err := controllers.GetClusterProfileName(context.TODO(), c, labels)
name, err := controllers.GetClusterProfileName(context.TODO(), c, clusterNamespace, clusterName,
eventTrigger, nil, labels, logger)
Expect(err).To(BeNil())
Expect(name).ToNot(BeEmpty())

clusterProfile := &configv1beta1.ClusterProfile{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName,
eventReport, clusterType),
},
}

Expect(c.Create(context.TODO(), clusterProfile)).To(Succeed())

var currentName string
currentName, err = controllers.GetClusterProfileName(context.TODO(), c, clusterNamespace, clusterName,
eventTrigger, nil, labels, logger)
Expect(err).To(BeNil())
Expect(currentName).To(Equal(name))
})

It("getClusterProfileName use InstantiatedProfileNameFormat when defined", func() {
eventTriggerName := randomString()
clusterNamespace := randomString()
clusterName := randomString()
clusterType := libsveltosv1beta1.ClusterTypeCapi

eventSourceName := randomString()

eventResource := corev1.ObjectReference{Kind: "Secret", APIVersion: corev1.SchemeGroupVersion.String(),
Namespace: randomString(), Name: randomString()}
eventReport := &libsveltosv1beta1.EventReport{
ObjectMeta: metav1.ObjectMeta{
Name: randomString(),
Labels: map[string]string{
libsveltosv1beta1.EventSourceNameLabel: eventSourceName,
},
},
Spec: libsveltosv1beta1.EventReportSpec{
MatchingResources: []corev1.ObjectReference{
eventResource,
},
},
}

eventTrigger := &v1beta1.EventTrigger{
ObjectMeta: metav1.ObjectMeta{Name: eventTriggerName},
Spec: v1beta1.EventTriggerSpec{
InstantiatedProfileNameFormat: "{{ .Cluster.metadata.namespace }}-{{ .Cluster.metadata.name }}-{{ .MatchingResource.Name }}-my-profile",
OneForEvent: true,
},
}

cluster := &clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: clusterNamespace,
},
}
Expect(addTypeInformationToObject(scheme, cluster)).To(Succeed())

initObjects := []client.Object{
cluster, eventTrigger, eventReport,
}
c := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjects...).Build()

objects, err := controllers.PrepareCurrentObjectList(ctx, c, clusterNamespace, clusterName, clusterType, eventReport, logger)
Expect(err).To(BeNil())

labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName,
eventReport, clusterType)

name, err := controllers.GetClusterProfileName(context.TODO(), c, clusterNamespace, clusterName,
eventTrigger, objects[0], labels, logger)
Expect(err).To(BeNil())
Expect(name).ToNot(BeEmpty())
Expect(name).To(Equal(fmt.Sprintf("%s-%s-%s-my-profile", clusterNamespace, clusterName, eventResource.Name)))

clusterProfile := &configv1beta1.ClusterProfile{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -1175,7 +1254,8 @@ status:
Expect(c.Create(context.TODO(), clusterProfile)).To(Succeed())

var currentName string
currentName, err = controllers.GetClusterProfileName(context.TODO(), c, labels)
currentName, err = controllers.GetClusterProfileName(context.TODO(), c, clusterNamespace, clusterName,
eventTrigger, objects[0], labels, logger)
Expect(err).To(BeNil())
Expect(currentName).To(Equal(name))
})
Expand Down Expand Up @@ -1887,7 +1967,7 @@ data:
{
Namespace: secretGenerator.Namespace,
Name: secretGenerator.Name,
InstantiatedResourceNameFormat: "{{ .Cluster.metadata.name}}-generated",
InstantiatedResourceNameFormat: "{{ .Cluster.metadata.name}}-{{ .MatchingResource.Name }}-generated",
},
},
},
Expand All @@ -1900,6 +1980,7 @@ data:
clusterNamespace, clusterName, clusterType, logger)
Expect(err).To(BeNil())
Expect(len(instantiatedSecrets)).To(Equal(1))
Expect(instantiatedSecrets[0].Name).To(Equal(fmt.Sprintf("%s-%s-generated", clusterName, secret.Name)))
})

It("getCloudEvents processes collected CloudEvents", func() {
Expand Down
13 changes: 12 additions & 1 deletion manifest/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ spec:
- `.Cluster.metadata.namespace`: namespace of the managed cluster
- `.Cluster.metadata.name`: name of the managed cluster
- `.Cluster.kind`: kind of the managed cluster object
- event data
minLength: 1
type: string
namespace:
Expand Down Expand Up @@ -117,6 +118,7 @@ spec:
In any managed cluster that matches this ClusterProfile, the add-ons and applications
defined in this instance will not be deployed until all add-ons and applications in the
ClusterProfiles listed as dependencies are deployed.
This field will be directly transferred to the ClusterProfile Spec
items:
type: string
type: array
Expand Down Expand Up @@ -998,6 +1000,15 @@ spec:
type: object
type: array
x-kubernetes-list-type: atomic
profileNameFormat:
description: |-
InstantiatedProfileNameFormat defines a template used to generate the name
of the ClusterProfile created in the management cluster. The template can reference:
- `.Cluster.metadata.namespace`: namespace of the managed cluster
- `.Cluster.metadata.name`: name of the managed cluster
- `.Cluster.kind`: kind of the managed cluster object
- event data
type: string
reloader:
default: false
description: |-
Expand Down Expand Up @@ -1032,6 +1043,7 @@ spec:
- `.Cluster.metadata.namespace`: namespace of the managed cluster
- `.Cluster.metadata.name`: name of the managed cluster
- `.Cluster.kind`: kind of the managed cluster object
- event data
minLength: 1
type: string
namespace:
Expand Down Expand Up @@ -1144,7 +1156,6 @@ spec:
Those resources' values will be used to instantiate templates contained in referenced
PolicyRefs and Helm charts
This field will be directly transferred to the ClusterProfile Spec
generated in response to events.
items:
properties:
identifier:
Expand Down
Loading