diff --git a/charts/flagger/README.md b/charts/flagger/README.md index fa5589ae..0c684acc 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` +`includeLabelPrefix` | List of prefixes of labels that are copied when creating primary deployments or daemonsets. Use * to include all | `""` `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..363c18ad 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.includeLabelPrefix }} + - -include-label-prefix={{ .Values.includeLabelPrefix }} + {{- end }} {{- if .Values.ingressClass }} - -ingress-class={{ .Values.ingressClass }} {{- end }} diff --git a/cmd/flagger/main.go b/cmd/flagger/main.go index 0ce5c76c..5267f08e 100644 --- a/cmd/flagger/main.go +++ b/cmd/flagger/main.go @@ -43,6 +43,7 @@ var ( logLevel string port string msteamsURL string + includeLabelPrefix 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(&includeLabelPrefix, "include-label-prefix", "", "List of prefixes of labels that are copied when creating primary deployments or daemonsets. Use * to include all.") 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) + includeLabelPrefixArray := strings.Split(includeLabelPrefix, ",") + + canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, labels, includeLabelPrefixArray, logger) c := controller.NewController( kubeClient, diff --git a/pkg/canary/daemonset_controller.go b/pkg/canary/daemonset_controller.go index fdd656c1..b3c39143 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 + includeLabelPrefix []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.includeLabelPrefix) 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, includeLabelPrefix []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 := includeLabelsByPrefix(canaryDae.Labels, includeLabelPrefix) + label, labelValue, err := c.getSelectorLabel(canaryDae) primaryLabelValue := fmt.Sprintf("%s-primary", labelValue) if err != nil { @@ -239,11 +243,10 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error // create primary daemonset primaryDae = &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ - Name: primaryName, - Namespace: cd.Namespace, - Labels: map[string]string{ - label: primaryLabelValue, - }, + Name: primaryName, + Namespace: cd.Namespace, + Labels: makePrimaryLabels(labels, primaryLabelValue, label), + Annotations: canaryDae.Annotations, 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..10064acc 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 + includeLabelPrefix []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.includeLabelPrefix); 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, includeLabelPrefix []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 := includeLabelsByPrefix(canaryDep.Labels, includeLabelPrefix) + label, labelValue, err := c.getSelectorLabel(canaryDep) primaryLabelValue := fmt.Sprintf("%s-primary", labelValue) if err != nil { @@ -240,11 +244,10 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err // create primary deployment primaryDep = &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: primaryName, - Namespace: cd.Namespace, - Labels: map[string]string{ - label: primaryLabelValue, - }, + Name: primaryName, + Namespace: cd.Namespace, + Labels: makePrimaryLabels(labels, primaryLabelValue, label), + Annotations: canaryDep.Annotations, 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..9c121b2b 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 + includeLabelPrefix []string } func NewFactory(kubeClient kubernetes.Interface, flaggerClient clientset.Interface, configTracker Tracker, labels []string, + includeLabelPrefix []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, + includeLabelPrefix: includeLabelPrefix, } } 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, + includeLabelPrefix: factory.includeLabelPrefix, } daemonSetCtrl := &DaemonSetController{ logger: factory.logger, diff --git a/pkg/canary/util.go b/pkg/canary/util.go index 254cd38a..75d789fe 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,20 @@ func makeAnnotations(annotations map[string]string) (map[string]string, error) { return res, nil } +func includeLabelsByPrefix(labels map[string]string, includeLabelPrefixes []string) map[string]string { + filteredLabels := make(map[string]string) + for key, value := range labels { + for _, includeLabelPrefix := range includeLabelPrefixes { + if includeLabelPrefix == "*" || strings.HasPrefix(key, includeLabelPrefix) { + filteredLabels[key] = value + break + } + } + } + + 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..e8e36f01 --- /dev/null +++ b/pkg/canary/util_test.go @@ -0,0 +1,55 @@ +package canary + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIncludeLabelsByPrefix(t *testing.T) { + labels := map[string]string{ + "foo": "foo-value", + "bar": "bar-value", + "lorem": "ipsum", + } + includeLabelPrefix := []string{"foo", "lor"} + + filteredLabels := includeLabelsByPrefix(labels, includeLabelPrefix) + + assert.Equal(t, filteredLabels, map[string]string{ + "foo": "foo-value", + "lorem": "ipsum", + // bar excluded + }) +} + +func TestIncludeLabelsByPrefixWithWildcard(t *testing.T) { + labels := map[string]string{ + "foo": "foo-value", + "bar": "bar-value", + "lorem": "ipsum", + } + includeLabelPrefix := []string{"*"} + + filteredLabels := includeLabelsByPrefix(labels, includeLabelPrefix) + + assert.Equal(t, filteredLabels, map[string]string{ + "foo": "foo-value", + "bar": "bar-value", + "lorem": "ipsum", + }) +} + +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..b4d2f52a 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{""}, 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..3b84e220 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{""}, logger) ctrl := &Controller{ kubeClient: kubeClient,