diff --git a/charts/flagger/README.md b/charts/flagger/README.md index fa5589ae..b82e27c8 100644 --- a/charts/flagger/README.md +++ b/charts/flagger/README.md @@ -125,6 +125,7 @@ Parameter | Description | Default `serviceAccount.name` | The name of the service account to create or use. If not set and `serviceAccount.create` is `true`, a name is generated using the Flagger fullname | `""` `serviceAccount.annotations` | Annotations for service account | `{}` `ingressAnnotationsPrefix` | Annotations prefix for ingresses | `custom.ingress.kubernetes.io` +`excludedLabelsPrefixes` | List of prefixes of labels that are excluded when creating primary controllers | `"fluxcd,jenkins"` `rbac.create` | If `true`, create and use RBAC resources | `true` `rbac.pspEnabled` | If `true`, create and use a restricted pod security policy | `false` `crd.create` | If `true`, create Flagger's CRDs (should be enabled for Helm v2 only) | `false` diff --git a/charts/flagger/templates/deployment.yaml b/charts/flagger/templates/deployment.yaml index cb6ebd58..d67b4273 100644 --- a/charts/flagger/templates/deployment.yaml +++ b/charts/flagger/templates/deployment.yaml @@ -106,6 +106,9 @@ spec: {{- if .Values.ingressAnnotationsPrefix }} - -ingress-annotations-prefix={{ .Values.ingressAnnotationsPrefix }} {{- end }} + {{- if .Values.excludedLabelsPrefixes }} + - -excluded-labels-prefixes={{ .Values.excludedLabelsPrefixes }} + {{- end }} {{- if .Values.ingressClass }} - -ingress-class={{ .Values.ingressClass }} {{- end }} diff --git a/cmd/flagger/main.go b/cmd/flagger/main.go index 0ce5c76c..217d5b70 100644 --- a/cmd/flagger/main.go +++ b/cmd/flagger/main.go @@ -43,6 +43,7 @@ var ( logLevel string port string msteamsURL string + excludedLabelsPrefixes string slackURL string slackUser string slackChannel string @@ -74,6 +75,7 @@ func init() { flag.StringVar(&slackChannel, "slack-channel", "", "Slack channel.") flag.StringVar(&eventWebhook, "event-webhook", "", "Webhook for publishing flagger events") flag.StringVar(&msteamsURL, "msteams-url", "", "MS Teams incoming webhook URL.") + flag.StringVar(&excludedLabelsPrefixes, "excluded-labels-prefixes", "fluxcd,jenkins", "List of prefixes of labels that are excluded when creating primary controllers.") flag.IntVar(&threadiness, "threadiness", 2, "Worker concurrency.") flag.BoolVar(&zapReplaceGlobals, "zap-replace-globals", false, "Whether to change the logging level of the global zap logger.") flag.StringVar(&zapEncoding, "zap-encoding", "json", "Zap logger encoding.") @@ -184,7 +186,9 @@ func main() { configTracker = &canary.NopTracker{} } - canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, labels, logger) + excludedLabelsPrefixesArray := strings.Split(excludedLabelsPrefixes, ",") + + canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, labels, excludedLabelsPrefixesArray, logger) c := controller.NewController( kubeClient, diff --git a/pkg/canary/daemonset_controller.go b/pkg/canary/daemonset_controller.go index fdd656c1..d7582845 100644 --- a/pkg/canary/daemonset_controller.go +++ b/pkg/canary/daemonset_controller.go @@ -22,11 +22,12 @@ var ( // DaemonSetController is managing the operations for Kubernetes DaemonSet kind type DaemonSetController struct { - kubeClient kubernetes.Interface - flaggerClient clientset.Interface - logger *zap.SugaredLogger - configTracker Tracker - labels []string + kubeClient kubernetes.Interface + flaggerClient clientset.Interface + logger *zap.SugaredLogger + configTracker Tracker + labels []string + excludedLabelsPrefixes []string } func (c *DaemonSetController) ScaleToZero(cd *flaggerv1.Canary) error { @@ -76,7 +77,7 @@ func (c *DaemonSetController) ScaleFromZero(cd *flaggerv1.Canary) error { // Initialize creates the primary DaemonSet, scales down the canary DaemonSet, // and returns the pod selector label and container ports func (c *DaemonSetController) Initialize(cd *flaggerv1.Canary) (err error) { - err = c.createPrimaryDaemonSet(cd) + err = c.createPrimaryDaemonSet(cd, c.excludedLabelsPrefixes) if err != nil { return fmt.Errorf("createPrimaryDaemonSet failed: %w", err) } @@ -200,7 +201,7 @@ func (c *DaemonSetController) GetMetadata(cd *flaggerv1.Canary) (string, string, return label, labelValue, ports, nil } -func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error { +func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary, excludedLabelsPrefixes []string) error { targetName := cd.Spec.TargetRef.Name primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name) @@ -215,6 +216,9 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error targetName, cd.Namespace, canaryDae.Spec.UpdateStrategy.Type) } + // Create the labels map but filter unwanted labels + labels := excludeLabelsByPrefix(canaryDae.Labels, excludedLabelsPrefixes) + label, labelValue, err := c.getSelectorLabel(canaryDae) primaryLabelValue := fmt.Sprintf("%s-primary", labelValue) if err != nil { @@ -241,9 +245,7 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error ObjectMeta: metav1.ObjectMeta{ Name: primaryName, Namespace: cd.Namespace, - Labels: map[string]string{ - label: primaryLabelValue, - }, + Labels: labels, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(cd, schema.GroupVersionKind{ Group: flaggerv1.SchemeGroupVersion.Group, diff --git a/pkg/canary/deployment_controller.go b/pkg/canary/deployment_controller.go index 40f3ba91..f0fa37ef 100644 --- a/pkg/canary/deployment_controller.go +++ b/pkg/canary/deployment_controller.go @@ -20,18 +20,19 @@ import ( // DeploymentController is managing the operations for Kubernetes Deployment kind type DeploymentController struct { - kubeClient kubernetes.Interface - flaggerClient clientset.Interface - logger *zap.SugaredLogger - configTracker Tracker - labels []string + kubeClient kubernetes.Interface + flaggerClient clientset.Interface + logger *zap.SugaredLogger + configTracker Tracker + labels []string + excludedLabelsPrefixes []string } // Initialize creates the primary deployment, hpa, // scales to zero the canary deployment and returns the pod selector label and container ports func (c *DeploymentController) Initialize(cd *flaggerv1.Canary) (err error) { primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name) - if err := c.createPrimaryDeployment(cd); err != nil { + if err := c.createPrimaryDeployment(cd, c.excludedLabelsPrefixes); err != nil { return fmt.Errorf("createPrimaryDeployment failed: %w", err) } @@ -202,15 +203,18 @@ func (c *DeploymentController) GetMetadata(cd *flaggerv1.Canary) (string, string return label, labelValue, ports, nil } -func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) error { +func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary, excludedLabelsPrefixes []string) error { targetName := cd.Spec.TargetRef.Name primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name) canaryDep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(context.TODO(), targetName, metav1.GetOptions{}) if err != nil { - return fmt.Errorf("deplyoment %s.%s get query error: %w", targetName, cd.Namespace, err) + return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err) } + // Create the labels map but filter unwanted labels + labels := excludeLabelsByPrefix(canaryDep.Labels, excludedLabelsPrefixes) + label, labelValue, err := c.getSelectorLabel(canaryDep) primaryLabelValue := fmt.Sprintf("%s-primary", labelValue) if err != nil { @@ -242,9 +246,7 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err ObjectMeta: metav1.ObjectMeta{ Name: primaryName, Namespace: cd.Namespace, - Labels: map[string]string{ - label: primaryLabelValue, - }, + Labels: labels, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(cd, schema.GroupVersionKind{ Group: flaggerv1.SchemeGroupVersion.Group, diff --git a/pkg/canary/factory.go b/pkg/canary/factory.go index 95f243b8..99db8041 100644 --- a/pkg/canary/factory.go +++ b/pkg/canary/factory.go @@ -8,34 +8,38 @@ import ( ) type Factory struct { - kubeClient kubernetes.Interface - flaggerClient clientset.Interface - logger *zap.SugaredLogger - configTracker Tracker - labels []string + kubeClient kubernetes.Interface + flaggerClient clientset.Interface + logger *zap.SugaredLogger + configTracker Tracker + labels []string + excludedLabelsPrefixes []string } func NewFactory(kubeClient kubernetes.Interface, flaggerClient clientset.Interface, configTracker Tracker, labels []string, + excludedLabelsPrefixes []string, logger *zap.SugaredLogger) *Factory { return &Factory{ - kubeClient: kubeClient, - flaggerClient: flaggerClient, - logger: logger, - configTracker: configTracker, - labels: labels, + kubeClient: kubeClient, + flaggerClient: flaggerClient, + logger: logger, + configTracker: configTracker, + labels: labels, + excludedLabelsPrefixes: excludedLabelsPrefixes, } } func (factory *Factory) Controller(kind string) Controller { deploymentCtrl := &DeploymentController{ - logger: factory.logger, - kubeClient: factory.kubeClient, - flaggerClient: factory.flaggerClient, - labels: factory.labels, - configTracker: factory.configTracker, + logger: factory.logger, + kubeClient: factory.kubeClient, + flaggerClient: factory.flaggerClient, + labels: factory.labels, + configTracker: factory.configTracker, + excludedLabelsPrefixes: factory.excludedLabelsPrefixes, } daemonSetCtrl := &DaemonSetController{ logger: factory.logger, diff --git a/pkg/canary/util.go b/pkg/canary/util.go index 254cd38a..d3554a93 100644 --- a/pkg/canary/util.go +++ b/pkg/canary/util.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "fmt" "io" + "strings" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -75,6 +76,24 @@ func makeAnnotations(annotations map[string]string) (map[string]string, error) { return res, nil } +func excludeLabelsByPrefix(labels map[string]string, excludedLabelsPrefixes []string) map[string]string { + filteredLabels := make(map[string]string) + for key, value := range labels { + isPrefixExcluded := false + for _, excludeLabelPrefix := range excludedLabelsPrefixes { + if strings.HasPrefix(key, excludeLabelPrefix) { + isPrefixExcluded = true + break + } + } + if !isPrefixExcluded { + filteredLabels[key] = value + } + } + + return filteredLabels +} + func makePrimaryLabels(labels map[string]string, labelValue string, label string) map[string]string { res := make(map[string]string) for k, v := range labels { diff --git a/pkg/canary/util_test.go b/pkg/canary/util_test.go new file mode 100644 index 00000000..f4858f27 --- /dev/null +++ b/pkg/canary/util_test.go @@ -0,0 +1,38 @@ +package canary + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExcludeLabelsByPrefix(t *testing.T) { + labels := map[string]string{ + "foo": "bar", + "jenkins": "foo", + "flux123": "bar", + } + excludedLabelsPrefixes := []string{"jenkins", "flux"} + + filteredLabels := excludeLabelsByPrefix(labels, excludedLabelsPrefixes) + + assert.Equal(t, filteredLabels, map[string]string{ + "foo": "bar", + // jenkins excluded + // and flux123 also excluded + }) +} + +func TestMakePrimaryLabels(t *testing.T) { + labels := map[string]string{ + "lorem": "ipsum", + "foo": "old-bar", + } + + primaryLabels := makePrimaryLabels(labels, "new-bar", "foo") + + assert.Equal(t, primaryLabels, map[string]string{ + "lorem": "ipsum", // values from old map + "foo": "new-bar", // overriden value for a specific label + }) +} diff --git a/pkg/controller/scheduler_daemonset_fixture_test.go b/pkg/controller/scheduler_daemonset_fixture_test.go index 3c85484b..81c7574e 100644 --- a/pkg/controller/scheduler_daemonset_fixture_test.go +++ b/pkg/controller/scheduler_daemonset_fixture_test.go @@ -87,7 +87,7 @@ func newDaemonSetFixture(c *flaggerv1.Canary) daemonSetFixture { KubeClient: kubeClient, FlaggerClient: flaggerClient, } - canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, logger) + canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, []string{"jenkins"}, logger) ctrl := &Controller{ kubeClient: kubeClient, diff --git a/pkg/controller/scheduler_deployment_fixture_test.go b/pkg/controller/scheduler_deployment_fixture_test.go index e0a95758..73d59d2e 100644 --- a/pkg/controller/scheduler_deployment_fixture_test.go +++ b/pkg/controller/scheduler_deployment_fixture_test.go @@ -115,7 +115,7 @@ func newDeploymentFixture(c *flaggerv1.Canary) fixture { KubeClient: kubeClient, FlaggerClient: flaggerClient, } - canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, logger) + canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, []string{"jenkins"}, logger) ctrl := &Controller{ kubeClient: kubeClient,