From 74e767e03f7096dcd58a05222e1a6909b067394e Mon Sep 17 00:00:00 2001 From: Rizwana777 Date: Fri, 13 Sep 2024 15:17:06 +0530 Subject: [PATCH] Add metric and traffic plugins Signed-off-by: Rizwana777 --- api/v1alpha1/argorollouts_types.go | 14 ++ api/v1alpha1/zz_generated.deepcopy.go | 41 ++++ .../bases/argoproj.io_rolloutmanagers.yaml | 31 +++ controllers/configmap.go | 142 ++++++++++--- controllers/configmap_test.go | 188 +++++++++++++----- .../cluster_scoped_rollouts_test.go | 83 ++++++++ 6 files changed, 420 insertions(+), 79 deletions(-) diff --git a/api/v1alpha1/argorollouts_types.go b/api/v1alpha1/argorollouts_types.go index a08d32a..d2731b2 100644 --- a/api/v1alpha1/argorollouts_types.go +++ b/api/v1alpha1/argorollouts_types.go @@ -52,6 +52,20 @@ type RolloutManagerSpec struct { // SkipNotificationSecretDeployment lets you specify if the argo notification secret should be deployed SkipNotificationSecretDeployment bool `json:"skipNotificationSecretDeployment,omitempty"` + + // Plugins specify the traffic and metric plugins in Argo Rollout + Plugins Plugins `json:"plugins,omitempty"` +} + +type Plugin struct { + Name string `json:"name"` + Location string `json:"location"` + SHA256 string `json:"sha256,omitempty"` +} + +type Plugins struct { + TrafficManagement []Plugin `json:"trafficManagement,omitempty"` + Metric []Plugin `json:"metric,omitempty"` } // ArgoRolloutsNodePlacementSpec is used to specify NodeSelector and Tolerations for Rollouts workloads diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bb7896a..8c4bcd9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -26,6 +26,46 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Plugin) DeepCopyInto(out *Plugin) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plugin. +func (in *Plugin) DeepCopy() *Plugin { + if in == nil { + return nil + } + out := new(Plugin) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Plugins) DeepCopyInto(out *Plugins) { + *out = *in + if in.TrafficManagement != nil { + in, out := &in.TrafficManagement, &out.TrafficManagement + *out = make([]Plugin, len(*in)) + copy(*out, *in) + } + if in.Metric != nil { + in, out := &in.Metric, &out.Metric + *out = make([]Plugin, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plugins. +func (in *Plugins) DeepCopy() *Plugins { + if in == nil { + return nil + } + out := new(Plugins) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceMetadata) DeepCopyInto(out *ResourceMetadata) { *out = *in @@ -144,6 +184,7 @@ func (in *RolloutManagerSpec) DeepCopyInto(out *RolloutManagerSpec) { *out = new(v1.ResourceRequirements) (*in).DeepCopyInto(*out) } + in.Plugins.DeepCopyInto(&out.Plugins) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RolloutManagerSpec. diff --git a/config/crd/bases/argoproj.io_rolloutmanagers.yaml b/config/crd/bases/argoproj.io_rolloutmanagers.yaml index 3887030..b5d7d37 100644 --- a/config/crd/bases/argoproj.io_rolloutmanagers.yaml +++ b/config/crd/bases/argoproj.io_rolloutmanagers.yaml @@ -286,6 +286,37 @@ spec: type: object type: array type: object + plugins: + properties: + metric: + items: + properties: + location: + type: string + name: + type: string + sha256: + type: string + required: + - location + - name + type: object + type: array + trafficManagement: + items: + properties: + location: + type: string + name: + type: string + sha256: + type: string + required: + - location + - name + type: object + type: array + type: object skipNotificationSecretDeployment: description: SkipNotificationSecretDeployment lets you specify if the argo notification secret should be deployed diff --git a/controllers/configmap.go b/controllers/configmap.go index 4966875..3add7e2 100644 --- a/controllers/configmap.go +++ b/controllers/configmap.go @@ -2,17 +2,24 @@ package rollouts import ( "context" + "fmt" + "reflect" rolloutsmanagerv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1" "gopkg.in/yaml.v2" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) // From https://argo-rollouts.readthedocs.io/en/stable/features/traffic-management/plugins/ const TrafficRouterPluginConfigMapKey = "trafficRouterPlugins" +const MetricPluginConfigMapKey = "metricPlugins" // Reconcile the Rollouts Default Config Map. func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr rolloutsmanagerv1alpha1.RolloutManager) error { @@ -33,18 +40,66 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr ro setRolloutsLabelsAndAnnotationsToObject(&desiredConfigMap.ObjectMeta, cr) - trafficRouterPlugins := []pluginItem{ - { + trafficRouterPluginsMap := map[string]pluginItem{ + OpenShiftRolloutPluginName: { Name: OpenShiftRolloutPluginName, Location: r.OpenShiftRoutePluginLocation, }, } - pluginString, err := yaml.Marshal(trafficRouterPlugins) + + // Append traffic management plugins specified in RolloutManager CR + for _, plugin := range cr.Spec.Plugins.TrafficManagement { + // Prevent adding or modifying the OpenShiftRoutePluginName through the CR + if plugin.Name == OpenShiftRolloutPluginName { + return fmt.Errorf("the plugin %s cannot be modified or added through the RolloutManager CR", OpenShiftRolloutPluginName) + } + // Check for duplicate traffic plugins + if _, exists := trafficRouterPluginsMap[plugin.Name]; !exists { + trafficRouterPluginsMap[plugin.Name] = pluginItem{ + Name: plugin.Name, + Location: plugin.Location, + } + } + } + + // Convert traffic plugins map to slice + trafficRouterPlugins := make([]pluginItem, 0, len(trafficRouterPluginsMap)) + for _, plugin := range trafficRouterPluginsMap { + trafficRouterPlugins = append(trafficRouterPlugins, plugin) + } + + // Append metric plugins specified in RolloutManager CR + metricPluginsMap := map[string]pluginItem{} + for _, plugin := range cr.Spec.Plugins.Metric { + // Check for duplicate metric plugins + if _, exists := metricPluginsMap[plugin.Name]; !exists { + metricPluginsMap[plugin.Name] = pluginItem{ + Name: plugin.Name, + Location: plugin.Location, + Sha256: plugin.SHA256, + } + } + } + + // Convert metric plugins map to slice + metricPlugins := make([]pluginItem, 0, len(metricPluginsMap)) + for _, plugin := range metricPluginsMap { + metricPlugins = append(metricPlugins, plugin) + } + + desiredTrafficRouterPluginString, err := yaml.Marshal(trafficRouterPlugins) if err != nil { return fmt.Errorf("error marshalling trafficRouterPlugin to string %s", err) } + + desiredMetricPluginString, err := yaml.Marshal(metricPlugins) + if err != nil { + return fmt.Errorf("error marshalling metricPlugins to string %s", err) + } + desiredConfigMap.Data = map[string]string{ - TrafficRouterPluginConfigMapKey: string(pluginString), + TrafficRouterPluginConfigMapKey: string(desiredTrafficRouterPluginString), + MetricPluginConfigMapKey: string(desiredMetricPluginString), } actualConfigMap := &corev1.ConfigMap{} @@ -58,41 +113,66 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr ro return fmt.Errorf("failed to get the serviceAccount associated with %s: %w", desiredConfigMap.Name, err) } - var actualTrafficRouterPlugins []pluginItem + // Unmarshal the existing plugin data from the actual ConfigMap + var actualTrafficRouterPlugins, actualMetricPlugins []pluginItem if err = yaml.Unmarshal([]byte(actualConfigMap.Data[TrafficRouterPluginConfigMapKey]), &actualTrafficRouterPlugins); err != nil { - return fmt.Errorf("failed to unmarshal traffic router plugins from ConfigMap: %s", err) + return fmt.Errorf("failed to unmarshal traffic router plugins: %s", err) + } + if err = yaml.Unmarshal([]byte(actualConfigMap.Data[MetricPluginConfigMapKey]), &actualMetricPlugins); err != nil { + return fmt.Errorf("failed to unmarshal metric plugins: %s", err) } - // Check if the plugin already exists and if the URL is different, update the ConfigMap - for i, plugin := range actualTrafficRouterPlugins { - if plugin.Name == OpenShiftRolloutPluginName { - if plugin.Location != r.OpenShiftRoutePluginLocation { - actualTrafficRouterPlugins[i].Location = r.OpenShiftRoutePluginLocation - pluginBytes, err := yaml.Marshal(actualTrafficRouterPlugins) - if err != nil { - return fmt.Errorf("error marshalling trafficRouterPlugin to string %s", err) - } - - actualConfigMap.Data = map[string]string{ - TrafficRouterPluginConfigMapKey: string(pluginBytes), - } - - return r.Client.Update(ctx, actualConfigMap) - } else { - // Plugin URL is the same, nothing to do - return nil - } + // Check if an update is needed by comparing desired and actual plugin configurations + updateNeeded := !reflect.DeepEqual(actualTrafficRouterPlugins, trafficRouterPlugins) || !reflect.DeepEqual(actualMetricPlugins, metricPlugins) + + if updateNeeded { + // Update the ConfigMap's plugin data with the new values + actualConfigMap.Data[TrafficRouterPluginConfigMapKey] = string(desiredTrafficRouterPluginString) + actualConfigMap.Data[MetricPluginConfigMapKey] = string(desiredMetricPluginString) + + // Update the ConfigMap in the cluster + if err := r.Client.Update(ctx, actualConfigMap); err != nil { + return fmt.Errorf("failed to update ConfigMap: %v", err) + } + log.Info("ConfigMap updated successfully") + + // Restarting rollouts pod only if configMap is updated + if err := r.restartRolloutsPod(ctx, cr.Namespace); err != nil { + return err } } + log.Info("No changes detected in ConfigMap, skipping update and pod restart") + return nil +} - updatedTrafficRouterPlugins := append(actualTrafficRouterPlugins, trafficRouterPlugins...) +// restartRolloutsPod deletes the Rollouts Pod to trigger a restart +func (r *RolloutManagerReconciler) restartRolloutsPod(ctx context.Context, namespace string) error { + deployment := &appsv1.Deployment{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: DefaultArgoRolloutsResourceName, Namespace: namespace}, deployment); err != nil { + return fmt.Errorf("failed to get deployment: %w", err) + } - pluginString, err = yaml.Marshal(updatedTrafficRouterPlugins) - if err != nil { - return fmt.Errorf("error marshalling trafficRouterPlugin to string %w", err) + podList := &corev1.PodList{} + listOpts := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(deployment.Spec.Selector.MatchLabels), + } + if err := r.Client.List(ctx, podList, listOpts...); err != nil { + return fmt.Errorf("failed to list Rollouts Pods: %w", err) } - actualConfigMap.Data[TrafficRouterPluginConfigMapKey] = string(pluginString) + for i := range podList.Items { + pod := podList.Items[i] + log.Info("Deleting Rollouts Pod", "podName", pod.Name) + if err := r.Client.Delete(ctx, &pod); err != nil { + if errors.IsNotFound(err) { + log.Info(fmt.Sprintf("Pod %s already deleted", pod.Name)) + continue + } + return fmt.Errorf("failed to delete Rollouts Pod %s: %w", pod.Name, err) + } + log.Info("Rollouts Pod deleted successfully", "podName", pod.Name) + } - return r.Client.Update(ctx, actualConfigMap) + return nil } diff --git a/controllers/configmap_test.go b/controllers/configmap_test.go index a38c1cd..9375b1e 100644 --- a/controllers/configmap_test.go +++ b/controllers/configmap_test.go @@ -6,7 +6,6 @@ import ( "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -15,6 +14,9 @@ var _ = Describe("ConfigMap Test", func() { var ctx context.Context var a v1alpha1.RolloutManager var r *RolloutManagerReconciler + var sa *corev1.ServiceAccount + const trafficrouterPluginLocation = "https://custom-traffic-plugin-location" + const metricPluginLocation = "https://custom-metric-plugin-location" BeforeEach(func() { ctx = context.Background() @@ -22,6 +24,18 @@ var _ = Describe("ConfigMap Test", func() { r = makeTestReconciler(&a) Expect(createNamespace(r, a.Namespace)).To(Succeed()) + + sa = &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultArgoRolloutsResourceName, + Namespace: a.Namespace, + }, + } + Expect(r.Client.Create(ctx, sa)).To(Succeed()) + + existingDeployment := deploymentCR(DefaultArgoRolloutsResourceName, a.Namespace, DefaultArgoRolloutsResourceName, []string{"plugin-bin-test", "tmp-test"}, "linux-test", sa.Name, a) + Expect(r.Client.Create(ctx, existingDeployment)).To(Succeed()) + }) It("verifies that the default ConfigMap is created if it is not present", func() { @@ -53,77 +67,155 @@ var _ = Describe("ConfigMap Test", func() { }) - It("verifies that the config map reconciler will not overwrite a custom plugin that is added to the ConfigMap by the user", func() { - - // By("creating a ConfigMap containing default Openshift") + // Commented out because we are overwriting user-defined plugin values in the ConfigMap + // with the plugins defined in the CR. This will be removed once the PR has been reviewed. + + /*It("verifies that the config map reconciler will not overwrite a custom plugin that is added to the ConfigMap by the user", func() { + + // By("creating a ConfigMap containing default Openshift") + expectedConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultRolloutsConfigMapName, + Namespace: a.Namespace, + Labels: map[string]string{ + "app.kubernetes.io/name": DefaultRolloutsConfigMapName, + }, + }, + } + + By("calling reconcileConfigMap, which will add the default plugin to the ConfigMap") + Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) + + By("fetching the ConfigMap") + fetchedConfigMap := &corev1.ConfigMap{} + Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).ToNot(ContainSubstring("test/plugin")) + + By("adding a new trafficRouter plugin to test the update plugin logic") + trafficRouterPlugins := []pluginItem{ + { + Name: "test/plugin", + Location: "https://test-path", + }, + } + + newConfigMap := fetchedConfigMap.DeepCopy() + { + pluginString, err := yaml.Marshal(trafficRouterPlugins) + Expect(err).ToNot(HaveOccurred()) + + newConfigMap.Data = map[string]string{ + TrafficRouterPluginConfigMapKey: string(pluginString), + } + } + + By("updating the ConfigMap to contain only a user provided plugin") + Expect(r.Client.Update(ctx, newConfigMap)).To(Succeed()) + + By("calling reconcileConfigMap") + Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) + + By("verifying that when ConfigMap is reconciled, it contains both plugins") + + Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin")) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation)) + + By("calling reconcileConfigMap again, to verify nothing changes when reconcile is called again") + Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) + + Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin")) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation)) + + // overriding this value with new test url to verify whether it updated the existing configMap with the new url + r.OpenShiftRoutePluginLocation = "test-updated-url" + + By("calling reconcileConfigMap") + Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) + + Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin")) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test-updated-url")) + }) */ + + It("verifies traffic and metric plugin creation/modification and ensures OpenShiftRolloutPlugin existence", func() { expectedConfigMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: DefaultRolloutsConfigMapName, - Namespace: a.Namespace, - Labels: map[string]string{ - "app.kubernetes.io/name": DefaultRolloutsConfigMapName, - }, + Name: DefaultRolloutsConfigMapName, }, } - By("calling reconcileConfigMap, which will add the default plugin to the ConfigMap") + By("Adding traffic and metric plugins through the CR") + a.Spec.Plugins.TrafficManagement = []v1alpha1.Plugin{ + {Name: "custom-traffic-plugin", Location: trafficrouterPluginLocation}, + } + a.Spec.Plugins.Metric = []v1alpha1.Plugin{ + {Name: "custom-metric-plugin", Location: metricPluginLocation, SHA256: "sha256-test"}, + } + + By("Call reconcileConfigMap") Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) - By("fetching the ConfigMap") + By("Fetched ConfigMap") fetchedConfigMap := &corev1.ConfigMap{} Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) + + By("Verify that the fetched ConfigMap contains OpenShiftRolloutPlugin") + Expect(fetchedConfigMap.Name).To(Equal(expectedConfigMap.Name)) Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).ToNot(ContainSubstring("test/plugin")) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation)) - By("adding a new trafficRouter plugin to test the update plugin logic") - trafficRouterPlugins := []pluginItem{ - { - Name: "test/plugin", - Location: "https://test-path", - }, - } + By("Verify that the fetched ConfigMap contains plugins added by CR") + Expect(fetchedConfigMap.Data[MetricPluginConfigMapKey]).To(ContainSubstring(a.Spec.Plugins.Metric[0].Name)) + Expect(fetchedConfigMap.Data[MetricPluginConfigMapKey]).To(ContainSubstring(a.Spec.Plugins.Metric[0].Location)) - newConfigMap := fetchedConfigMap.DeepCopy() - { - pluginString, err := yaml.Marshal(trafficRouterPlugins) - Expect(err).ToNot(HaveOccurred()) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(a.Spec.Plugins.TrafficManagement[0].Name)) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(a.Spec.Plugins.TrafficManagement[0].Location)) - newConfigMap.Data = map[string]string{ - TrafficRouterPluginConfigMapKey: string(pluginString), - } - } + By("Update metric and traffic plugins through RolloutManager CR") + updatedPluginLocation := "https://test-updated-plugin-location" - By("updating the ConfigMap to contain only a user provided plugin") - Expect(r.Client.Update(ctx, newConfigMap)).To(Succeed()) + a.Spec.Plugins.TrafficManagement = []v1alpha1.Plugin{ + {Name: "custom-traffic-plugin", Location: updatedPluginLocation}, + } + a.Spec.Plugins.Metric = []v1alpha1.Plugin{ + {Name: "custom-metric-plugin", Location: updatedPluginLocation, SHA256: "sha256-test"}, + } - By("calling reconcileConfigMap") + By("Call reconcileConfigMap again after update") Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) - By("verifying that when ConfigMap is reconciled, it contains both plugins") - + By("Fetched ConfigMap") Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin")) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation)) - - By("calling reconcileConfigMap again, to verify nothing changes when reconcile is called again") - Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) - Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin")) + By("Verify that the fetched ConfigMap contains OpenShiftRolloutPlugin") + Expect(fetchedConfigMap.Name).To(Equal(expectedConfigMap.Name)) Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation)) - // overriding this value with new test url to verify whether it updated the existing configMap with the new url - r.OpenShiftRoutePluginLocation = "test-updated-url" + By("Verify that ConfigMap is updated with the plugins modified by RolloutManger CR") + Expect(fetchedConfigMap.Data[MetricPluginConfigMapKey]).To(ContainSubstring(a.Spec.Plugins.Metric[0].Name)) + Expect(fetchedConfigMap.Data[MetricPluginConfigMapKey]).To(ContainSubstring(updatedPluginLocation)) - By("calling reconcileConfigMap") - Expect(r.reconcileConfigMap(ctx, a)).To(Succeed()) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(a.Spec.Plugins.TrafficManagement[0].Name)) + Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(updatedPluginLocation)) - Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed()) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin")) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName)) - Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test-updated-url")) + By("Verify that OpenShiftRolloutPlugin is not updated through RolloutManager CR") + a.Spec.Plugins = v1alpha1.Plugins{ + TrafficManagement: []v1alpha1.Plugin{ + { + Name: OpenShiftRolloutPluginName, + Location: r.OpenShiftRoutePluginLocation, + }, + }, + } + By("Call reconcileConfigMap again after update") + Expect(r.reconcileConfigMap(ctx, a)).ToNot(Succeed(), "the plugin %s cannot be modified or added through the RolloutManager CR", OpenShiftRolloutPluginName) }) }) diff --git a/tests/e2e/cluster-scoped/cluster_scoped_rollouts_test.go b/tests/e2e/cluster-scoped/cluster_scoped_rollouts_test.go index 77f5624..3a88f38 100644 --- a/tests/e2e/cluster-scoped/cluster_scoped_rollouts_test.go +++ b/tests/e2e/cluster-scoped/cluster_scoped_rollouts_test.go @@ -2,6 +2,7 @@ package e2e import ( "context" + "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -13,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1" rmv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1" controllers "github.com/argoproj-labs/argo-rollouts-manager/controllers" @@ -324,5 +326,86 @@ var _ = Describe("Cluster-scoped RolloutManager tests", func() { By("2nd RM: Verify that Status.Condition is having success condition.") Eventually(rolloutsManagerCl2, "1m", "1s").Should(rmFixture.HaveSuccessCondition()) }) + + It("Should add, update, and remove traffic and metric plugins through RolloutManager CR", func() { + rolloutsManager := v1alpha1.RolloutManager{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-rollouts-manager", + Namespace: fixture.TestE2ENamespace, + }, + Spec: v1alpha1.RolloutManagerSpec{ + NamespaceScoped: false, + Plugins: v1alpha1.Plugins{ + TrafficManagement: []v1alpha1.Plugin{ + { + Name: "argoproj-labs/gatewayAPI", + Location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.0.1/gateway-api-plugin-darwin-arm64"}, + }, + Metric: []v1alpha1.Plugin{ + { + Name: "prometheus", + Location: "https://github.com/argoproj-labs/sample-rollouts-metric-plugin/releases/download/v0.0.3/metric-plugin-linux-amd64", + SHA256: "08f588b1c799a37bbe8d0fc74cc1b1492dd70b2c", + }}, + }, + }, + } + + Expect(k8sClient.Create(ctx, &rolloutsManager)).To(Succeed()) + + By("Verify that De is successfully created.") + Eventually(rolloutsManager, "1m", "1s").Should(rmFixture.HavePhase(rmv1alpha1.PhaseAvailable)) + + By("Verify traffic and metric plugin is added to ConfigMap") + configMap := corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: controllers.DefaultRolloutsConfigMapName, Namespace: rolloutsManager.Namespace}, + } + + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&configMap), &configMap)).To(Succeed()) + Expect(configMap.Data[controllers.TrafficRouterPluginConfigMapKey]).To(ContainSubstring(rolloutsManager.Spec.Plugins.TrafficManagement[0].Name)) + Expect(configMap.Data[controllers.TrafficRouterPluginConfigMapKey]).To(ContainSubstring(rolloutsManager.Spec.Plugins.TrafficManagement[0].Location)) + + Expect(configMap.Data[controllers.MetricPluginConfigMapKey]).To(ContainSubstring(rolloutsManager.Spec.Plugins.Metric[0].Name)) + Expect(configMap.Data[controllers.MetricPluginConfigMapKey]).To(ContainSubstring(rolloutsManager.Spec.Plugins.Metric[0].Location)) + + By("Update traffic and metric plugins") + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&rolloutsManager), &rolloutsManager)).To(Succeed()) + + rolloutsManager.Spec.Plugins.TrafficManagement[0].Location = "https://test-update-traffic-plugin" + rolloutsManager.Spec.Plugins.Metric[0].Location = "https://test-update-metric-plugin" + + Expect(k8sClient.Update(ctx, &rolloutsManager)).To(Succeed()) + Eventually(rolloutsManager, "1m", "1s").Should(rmFixture.HavePhase(rmv1alpha1.PhaseAvailable)) + + By("Verify traffic and metric plugin is updated in ConfigMap") + Eventually(func() bool { + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(&configMap), &configMap); err != nil { + return false + } + return strings.Contains(configMap.Data[controllers.TrafficRouterPluginConfigMapKey], rolloutsManager.Spec.Plugins.TrafficManagement[0].Name) && + strings.Contains(configMap.Data[controllers.TrafficRouterPluginConfigMapKey], rolloutsManager.Spec.Plugins.TrafficManagement[0].Location) && + strings.Contains(configMap.Data[controllers.MetricPluginConfigMapKey], rolloutsManager.Spec.Plugins.Metric[0].Name) && + strings.Contains(configMap.Data[controllers.MetricPluginConfigMapKey], rolloutsManager.Spec.Plugins.Metric[0].Location) + }, "1m", "1s").Should(BeTrue()) + + By("Remove traffic and metric plugins") + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&rolloutsManager), &rolloutsManager)).To(Succeed()) + + By("Remove plugins from RolloutManager CR") + rolloutsManager.Spec.Plugins.TrafficManagement = []v1alpha1.Plugin{} + rolloutsManager.Spec.Plugins.Metric = []v1alpha1.Plugin{} + Expect(k8sClient.Update(ctx, &rolloutsManager)).To(Succeed()) + Eventually(rolloutsManager, "1m", "1s").Should(rmFixture.HavePhase(rmv1alpha1.PhaseAvailable)) + + By("Verify that traffic and metric plugins are removed from ConfigMap") + Eventually(func() bool { + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(&configMap), &configMap); err != nil { + return false + } + return !strings.Contains(configMap.Data[controllers.TrafficRouterPluginConfigMapKey], "gatewayAPI") && + !strings.Contains(configMap.Data[controllers.MetricPluginConfigMapKey], "prometheus") + }, "1m", "1s").Should(BeTrue()) + + }) }) })