diff --git a/README.md b/README.md index 6622239..1658047 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,25 @@ spec: From now on, when a mounted ConfigMap or Secret is updated, Wave will update this `config-hash` annotation and cause a Rolling Update to occur. +### Advanced Features + +If your Pod is reading some ConfigMap or Secret using the API and you want it +to be restarted on change you can tell Wave in an annotation: + +``` +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + wave.pusher.com/update-on-config-change: "true" + wave.pusher.com/extra-configmaps: "some-namespace/my-configmap,configmap-in-same-namespace" + wave.pusher.com/extra-secrets: "some-namespace/my-secret,some-other-namespace/foo" +... +``` + +Wave will watch those ConfigMap or Secret and behave just like if they were +mounted. + ## Project Concepts This section outlines some of the underlying concepts that enable this diff --git a/pkg/core/children.go b/pkg/core/children.go index 7d9b351..e52246d 100644 --- a/pkg/core/children.go +++ b/pkg/core/children.go @@ -157,6 +157,30 @@ func getChildNamesByType(obj podController) (configMetadataMap, configMetadataMa } } } + + // Parse deployment annotations for cms/secrets used inside the pod + if annotations := obj.GetAnnotations(); annotations != nil { + if configMapString, ok := annotations[ExtraConfigMapsAnnotation]; ok { + for _, cm := range strings.Split(configMapString, ",") { + parts := strings.Split(cm, "/") + if len(parts) == 1 { + configMaps[GetNamespacedName(parts[0], obj.GetNamespace())] = configMetadata{required: false, allKeys: true} + } else if len(parts) == 2 { + configMaps[GetNamespacedName(parts[1], parts[0])] = configMetadata{required: false, allKeys: true} + } + } + } + if secretString, ok := annotations[ExtraSecretsAnnotation]; ok { + for _, secret := range strings.Split(secretString, ",") { + parts := strings.Split(secret, "/") + if len(parts) == 1 { + secrets[GetNamespacedName(parts[0], obj.GetNamespace())] = configMetadata{required: false, allKeys: true} + } else if len(parts) == 2 { + secrets[GetNamespacedName(parts[1], parts[0])] = configMetadata{required: false, allKeys: true} + } + } + } + } } // Range through all Containers and their respective EnvFrom, diff --git a/pkg/core/children_test.go b/pkg/core/children_test.go index 4dba0f5..5f1dc17 100644 --- a/pkg/core/children_test.go +++ b/pkg/core/children_test.go @@ -275,6 +275,24 @@ var _ = Describe("Wave children Suite", func() { configMaps, secrets = getChildNamesByType(podControllerDeployment) }) + It("returns ConfigMaps referenced in extra-configmaps annotations", func() { + Expect(configMaps).To(HaveKeyWithValue(GetNamespacedName("test-cm1", "ns1"), + configMetadata{required: false, allKeys: true})) + Expect(configMaps).To(HaveKeyWithValue(GetNamespacedName("test-cm2", "ns2"), + configMetadata{required: false, allKeys: true})) + Expect(configMaps).To(HaveKeyWithValue(GetNamespacedName("local-cm1", podControllerDeployment.GetNamespace()), + configMetadata{required: false, allKeys: true})) + }) + + It("returns Secrets referenced in extra-secrets annotations", func() { + Expect(secrets).To(HaveKeyWithValue(GetNamespacedName("test-secret1", "ns1"), + configMetadata{required: false, allKeys: true})) + Expect(secrets).To(HaveKeyWithValue(GetNamespacedName("test-secret2", "ns2"), + configMetadata{required: false, allKeys: true})) + Expect(secrets).To(HaveKeyWithValue(GetNamespacedName("local-secret1", podControllerDeployment.GetNamespace()), + configMetadata{required: false, allKeys: true})) + }) + It("returns ConfigMaps referenced in Volumes", func() { Expect(configMaps).To(HaveKeyWithValue(GetNamespacedName(cm1.GetName(), podControllerDeployment.GetNamespace()), configMetadata{required: true, allKeys: true})) @@ -364,8 +382,8 @@ var _ = Describe("Wave children Suite", func() { }) It("does not return extra children", func() { - Expect(configMaps).To(HaveLen(9)) - Expect(secrets).To(HaveLen(9)) + Expect(configMaps).To(HaveLen(12)) + Expect(secrets).To(HaveLen(12)) }) }) diff --git a/pkg/core/types.go b/pkg/core/types.go index 8bc1a94..93b1373 100644 --- a/pkg/core/types.go +++ b/pkg/core/types.go @@ -24,6 +24,14 @@ const ( // SchedulingDisabledSchedulerName is the dummy scheduler to disable scheduling of pods SchedulingDisabledSchedulerName = "wave.pusher.com/invalid" + // ExtraConfigMapsAnnotation is the key of the annotation that contains additional + // ConfigMaps which Wave should watch + ExtraConfigMapsAnnotation = "wave.pusher.com/extra-configmaps" + + // ExtraSecretsAnnotation is the key of the annotation that contains additional + // Secrets which Wave should watch + ExtraSecretsAnnotation = "wave.pusher.com/extra-secrets" + // RequiredAnnotation is the key of the annotation on the Deployment that Wave // checks for before processing the deployment RequiredAnnotation = "wave.pusher.com/update-on-config-change" diff --git a/test/utils/test_objects.go b/test/utils/test_objects.go index 1429ac6..19dbe6e 100644 --- a/test/utils/test_objects.go +++ b/test/utils/test_objects.go @@ -26,14 +26,20 @@ var labels = map[string]string{ "app": "example", } +var annotations = map[string]string{ + "wave.pusher.com/extra-configmaps": "ns1/test-cm1,ns2/test-cm2,local-cm1", + "wave.pusher.com/extra-secrets": "ns1/test-secret1,ns2/test-secret2,local-secret1", +} + var trueValue = true // ExampleDeployment is an example Deployment object for use within test suites var ExampleDeployment = &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: "example", - Namespace: "default", - Labels: labels, + Name: "example", + Namespace: "default", + Labels: labels, + Annotations: annotations, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{