From c181eb464ca59aca8e570aa60a23eb791a15c50c Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Tue, 11 Feb 2020 11:36:16 +0200 Subject: [PATCH] Track projected configmaps and secrets - scan volumes with projected configmaps and secrets - update primary volumes with configmaps and/or secrets projections - add tests for configmaps and secrets projections --- pkg/canary/config_tracker.go | 45 ++++++++ pkg/canary/config_tracker_test.go | 131 +++++++++++++++++++++++ pkg/canary/deployment_controller_test.go | 63 +---------- pkg/canary/fixture.go | 115 +++++++++++++++++--- 4 files changed, 277 insertions(+), 77 deletions(-) create mode 100644 pkg/canary/config_tracker_test.go diff --git a/pkg/canary/config_tracker.go b/pkg/canary/config_tracker.go index 072c4b97..bf64cd49 100644 --- a/pkg/canary/config_tracker.go +++ b/pkg/canary/config_tracker.go @@ -124,6 +124,32 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf res[secret.GetName()] = *secret } } + + if projected := volume.Projected; projected != nil { + for _, source := range projected.Sources { + if cmv := source.ConfigMap; cmv != nil { + config, err := ct.getRefFromConfigMap(cmv.Name, cd.Namespace) + if err != nil { + ct.Logger.Errorf("configMap %s.%s query error %v", cmv.Name, cd.Namespace, err) + continue + } + if config != nil { + res[config.GetName()] = *config + } + } + + if sv := source.Secret; sv != nil { + secret, err := ct.getRefFromSecret(sv.Name, cd.Namespace) + if err != nil { + ct.Logger.Errorf("secret %s.%s query error %v", sv.Name, cd.Namespace, err) + continue + } + if secret != nil { + res[secret.GetName()] = *secret + } + } + } + } } // scan containers for _, container := range targetDep.Spec.Template.Spec.Containers { @@ -335,7 +361,26 @@ func (ct *ConfigTracker) ApplyPrimaryConfigs(spec corev1.PodSpec, refs map[strin spec.Volumes[i].Secret.SecretName += "-primary" } } + + if projected := volume.Projected; projected != nil { + for s, source := range projected.Sources { + if cmv := source.ConfigMap; cmv != nil { + name := fmt.Sprintf("%s/%s", ConfigRefMap, cmv.Name) + if _, exists := refs[name]; exists { + spec.Volumes[i].Projected.Sources[s].ConfigMap.Name += "-primary" + } + } + + if sv := source.Secret; sv != nil { + name := fmt.Sprintf("%s/%s", ConfigRefSecret, sv.Name) + if _, exists := refs[name]; exists { + spec.Volumes[i].Projected.Sources[s].Secret.Name += "-primary" + } + } + } + } } + // update containers for _, container := range spec.Containers { // update env diff --git a/pkg/canary/config_tracker_test.go b/pkg/canary/config_tracker_test.go new file mode 100644 index 00000000..b8550355 --- /dev/null +++ b/pkg/canary/config_tracker_test.go @@ -0,0 +1,131 @@ +package canary + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestConfigTracker_ConfigMaps(t *testing.T) { + mocks := newFixture() + configMap := newTestConfigMap() + configMapProjected := newTestConfigProjected() + + err := mocks.deployer.Initialize(mocks.canary, true) + if err != nil { + t.Fatal(err.Error()) + } + + depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + configPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name + if configPrimaryVolName != "podinfo-config-vol-primary" { + t.Errorf("Got config name %v wanted %v", configPrimaryVolName, "podinfo-config-vol-primary") + } + + configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if configPrimary.Data["color"] != configMap.Data["color"] { + t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"]) + } + + configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if configPrimaryEnv.Data["color"] != configMap.Data["color"] { + t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"]) + } + + configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if configPrimaryVol.Data["color"] != configMap.Data["color"] { + t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"]) + } + + configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name + if configProjectedName != "podinfo-config-projected-primary" { + t.Errorf("Got config name %v wanted %v", configProjectedName, "podinfo-config-projected-primary") + } + + configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if configPrimaryProjected.Data["color"] != configMapProjected.Data["color"] { + t.Errorf("Got ConfigMap color %s wanted %s", configPrimaryProjected.Data["color"], configMapProjected.Data["color"]) + } +} + +func TestConfigTracker_Secrets(t *testing.T) { + mocks := newFixture() + secret := newTestSecret() + secretProjected := newTestSecretProjected() + + err := mocks.deployer.Initialize(mocks.canary, true) + if err != nil { + t.Fatal(err.Error()) + } + + depPrimary, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + secretPrimaryVolName := depPrimary.Spec.Template.Spec.Volumes[1].VolumeSource.Secret.SecretName + if secretPrimaryVolName != "podinfo-secret-vol-primary" { + t.Errorf("Got config name %v wanted %v", secretPrimaryVolName, "podinfo-secret-vol-primary") + } + + secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) { + t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"]) + } + + secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) { + t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"]) + } + + secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) { + t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"]) + } + + secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name + if secretProjectedName != "podinfo-secret-projected-primary" { + t.Errorf("Got config name %v wanted %v", secretProjectedName, "podinfo-secret-projected-primary") + } + + secretPrimaryProjected, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-projected-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if string(secretPrimaryProjected.Data["apiKey"]) != string(secretProjected.Data["apiKey"]) { + t.Errorf("Got primary secret %s wanted %s", secretPrimaryProjected.Data["apiKey"], secretProjected.Data["apiKey"]) + } +} diff --git a/pkg/canary/deployment_controller_test.go b/pkg/canary/deployment_controller_test.go index 8e1d3ff2..0ea6f5e7 100644 --- a/pkg/canary/deployment_controller_test.go +++ b/pkg/canary/deployment_controller_test.go @@ -21,14 +21,7 @@ func TestCanaryDeployer_Sync(t *testing.T) { t.Fatal(err.Error()) } - configName := depPrimary.Spec.Template.Spec.Volumes[0].VolumeSource.ConfigMap.LocalObjectReference.Name - if configName != "podinfo-config-vol-primary" { - t.Errorf("Got config name %v wanted %v", configName, "podinfo-config-vol-primary") - } - dep := newTestDeployment() - configMap := NewTestConfigMap() - secret := NewTestSecret() primaryImage := depPrimary.Spec.Template.Spec.Containers[0].Image sourceImage := dep.Spec.Template.Spec.Containers[0].Image @@ -44,60 +37,6 @@ func TestCanaryDeployer_Sync(t *testing.T) { if hpaPrimary.Spec.ScaleTargetRef.Name != depPrimary.Name { t.Errorf("Got HPA target %s wanted %s", hpaPrimary.Spec.ScaleTargetRef.Name, depPrimary.Name) } - - configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{}) - if err != nil { - t.Fatal(err.Error()) - } - - if configPrimary.Data["color"] != configMap.Data["color"] { - t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"]) - } - - configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-all-env-primary", metav1.GetOptions{}) - if err != nil { - t.Fatal(err.Error()) - } - - if configPrimaryEnv.Data["color"] != configMap.Data["color"] { - t.Errorf("Got ConfigMap %s wanted %s", configPrimaryEnv.Data["a"], configMap.Data["color"]) - } - - configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-vol-primary", metav1.GetOptions{}) - if err != nil { - t.Fatal(err.Error()) - } - - if configPrimaryVol.Data["color"] != configMap.Data["color"] { - t.Errorf("Got ConfigMap color %s wanted %s", configPrimary.Data["color"], configMap.Data["color"]) - } - - secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{}) - if err != nil { - t.Fatal(err.Error()) - } - - if string(secretPrimary.Data["apiKey"]) != string(secret.Data["apiKey"]) { - t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"]) - } - - secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-all-env-primary", metav1.GetOptions{}) - if err != nil { - t.Fatal(err.Error()) - } - - if string(secretPrimaryEnv.Data["apiKey"]) != string(secret.Data["apiKey"]) { - t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"]) - } - - secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-vol-primary", metav1.GetOptions{}) - if err != nil { - t.Fatal(err.Error()) - } - - if string(secretPrimaryVol.Data["apiKey"]) != string(secret.Data["apiKey"]) { - t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret.Data["apiKey"]) - } } func TestCanaryDeployer_IsNewSpec(t *testing.T) { @@ -284,7 +223,7 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) { t.Fatalf("Status tracking configs are empty") } configs := *res.Status.TrackedConfigs - secret := NewTestSecret() + secret := newTestSecret() if _, exists := configs["secret/"+secret.GetName()]; !exists { t.Errorf("Secret %s not found in status", secret.GetName()) } diff --git a/pkg/canary/fixture.go b/pkg/canary/fixture.go index 49727de7..4a17ccfc 100644 --- a/pkg/canary/fixture.go +++ b/pkg/canary/fixture.go @@ -32,12 +32,14 @@ func newFixture() fixture { kubeClient := fake.NewSimpleClientset( newTestDeployment(), newTestHPA(), - NewTestConfigMap(), - NewTestConfigMapEnv(), - NewTestConfigMapVol(), - NewTestSecret(), - NewTestSecretEnv(), - NewTestSecretVol(), + newTestConfigMap(), + newTestConfigMapEnv(), + newTestConfigMapVol(), + newTestConfigProjected(), + newTestSecret(), + newTestSecretEnv(), + newTestSecretVol(), + newTestSecretProjected(), ) logger, _ := logger.NewLogger("debug") @@ -63,7 +65,7 @@ func newFixture() fixture { } } -func NewTestConfigMap() *corev1.ConfigMap { +func newTestConfigMap() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -90,7 +92,20 @@ func NewTestConfigMapV2() *corev1.ConfigMap { } } -func NewTestConfigMapEnv() *corev1.ConfigMap { +func newTestConfigProjected() *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-config-projected", + }, + Data: map[string]string{ + "color": "red", + }, + } +} + +func newTestConfigMapEnv() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -103,7 +118,7 @@ func NewTestConfigMapEnv() *corev1.ConfigMap { } } -func NewTestConfigMapVol() *corev1.ConfigMap { +func newTestConfigMapVol() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -116,7 +131,7 @@ func NewTestConfigMapVol() *corev1.ConfigMap { } } -func NewTestSecret() *corev1.Secret { +func newTestSecret() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -130,21 +145,21 @@ func NewTestSecret() *corev1.Secret { } } -func NewTestSecretV2() *corev1.Secret { +func newTestSecretProjected() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ Namespace: "default", - Name: "podinfo-secret-env", + Name: "podinfo-secret-projected", }, Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ - "apiKey": []byte("test2"), + "apiKey": []byte("test"), }, } } -func NewTestSecretEnv() *corev1.Secret { +func newTestSecretEnv() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -158,7 +173,7 @@ func NewTestSecretEnv() *corev1.Secret { } } -func NewTestSecretVol() *corev1.Secret { +func newTestSecretVol() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -311,6 +326,41 @@ func newTestDeployment() *appsv1.Deployment { }, }, }, + { + Name: "projected", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-projected", + }, + Items: []corev1.KeyToPath{ + { + Key: "color", + Path: "my-group/my-color", + }, + }, + }, + }, + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-secret-projected", + }, + Items: []corev1.KeyToPath{ + { + Key: "apiKey", + Path: "my-group/my-api-key", + }, + }, + }, + }, + }, + }, + }, + }, }, }, }, @@ -421,6 +471,41 @@ func newTestDeploymentV2() *appsv1.Deployment { }, }, }, + { + Name: "projected", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-projected", + }, + Items: []corev1.KeyToPath{ + { + Key: "color", + Path: "my-group/my-color", + }, + }, + }, + }, + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-secret-projected", + }, + Items: []corev1.KeyToPath{ + { + Key: "apiKey", + Path: "my-group/my-api-key", + }, + }, + }, + }, + }, + }, + }, + }, }, }, },