Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle env section in deployment yaml #34

Merged
merged 12 commits into from
Jan 28, 2019
14 changes: 11 additions & 3 deletions pkg/controller/deployment/deployment_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ var _ = Describe("Deployment controller Suite", func() {
var ownerRef metav1.OwnerReference
var cm1 *corev1.ConfigMap
var cm2 *corev1.ConfigMap
var cm3 *corev1.ConfigMap
var s1 *corev1.Secret
var s2 *corev1.Secret
var s3 *corev1.Secret

var waitForDeploymentReconciled = func(obj core.Object) {
request := reconcile.Request{
Expand Down Expand Up @@ -80,17 +82,23 @@ var _ = Describe("Deployment controller Suite", func() {
// Create some configmaps and secrets
cm1 = utils.ExampleConfigMap1.DeepCopy()
cm2 = utils.ExampleConfigMap2.DeepCopy()
cm3 = utils.ExampleConfigMap3.DeepCopy()
s1 = utils.ExampleSecret1.DeepCopy()
s2 = utils.ExampleSecret2.DeepCopy()
s3 = utils.ExampleSecret3.DeepCopy()

m.Create(cm1).Should(Succeed())
m.Create(cm2).Should(Succeed())
m.Create(cm3).Should(Succeed())
m.Create(s1).Should(Succeed())
m.Create(s2).Should(Succeed())
m.Create(s3).Should(Succeed())
m.Get(cm1, timeout).Should(Succeed())
m.Get(cm2, timeout).Should(Succeed())
m.Get(cm3, timeout).Should(Succeed())
m.Get(s1, timeout).Should(Succeed())
m.Get(s2, timeout).Should(Succeed())
m.Get(s3, timeout).Should(Succeed())

deployment = utils.ExampleDeployment.DeepCopy()

Expand Down Expand Up @@ -160,7 +168,7 @@ var _ = Describe("Deployment controller Suite", func() {
})

It("Adds OwnerReferences to all children", func() {
for _, obj := range []core.Object{cm1, cm2, s1, s2} {
for _, obj := range []core.Object{cm1, cm2, cm3, s1, s2, s3} {
m.Eventually(obj, timeout).Should(utils.WithOwnerReferences(ContainElement(ownerRef)))
}
})
Expand All @@ -181,7 +189,7 @@ var _ = Describe("Deployment controller Suite", func() {
return event.Message
}

hashMessage := "Configuration hash updated to 198df8455a4fd702fc0c7fdfa4bdb213363b96240bfd48b7b098d936499315a1"
hashMessage := "Configuration hash updated to ebabf80ef45218b27078a41ca16b35a4f91cb5672f389e520ae9da6ee3df3b1c"
m.Eventually(events, timeout).Should(utils.WithItems(ContainElement(WithTransform(eventMessage, Equal(hashMessage)))))
})

Expand Down Expand Up @@ -212,7 +220,7 @@ var _ = Describe("Deployment controller Suite", func() {
})

It("Updates the config hash in the Pod Template", func() {
m.Eventually(deployment, timeout).ShouldNot(utils.WithAnnotations(HaveKeyWithValue(core.ConfigHashAnnotation, originalHash)))
m.Eventually(deployment, timeout).ShouldNot(utils.WithPodTemplateAnnotations(HaveKeyWithValue(core.ConfigHashAnnotation, originalHash)))
andyedwardsibm marked this conversation as resolved.
Show resolved Hide resolved
})
})

Expand Down
132 changes: 98 additions & 34 deletions pkg/core/children.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,69 +28,88 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

// configMetadata contains information about ConfigMaps/Secrets referenced
// within PodTemplates
//
// maps of configMetadata are return from the getChildNamesByType method
andyedwardsibm marked this conversation as resolved.
Show resolved Hide resolved
// configMetadata is also used to pass info through the getObject methods
type configMetadata struct {
required bool
allKeys bool
keys map[string]struct{}
}

// getResult is returned from the getObject method as a helper struct to be
// passed into a channel
type getResult struct {
err error
obj Object
err error
obj Object
metadata configMetadata
}

// getCurrentChildren returns a list of all Secrets and ConfigMaps that are
// referenced in the Deployment's spec
func (h *Handler) getCurrentChildren(obj *appsv1.Deployment) ([]Object, error) {
// referenced in the Deployment's spec. Any reference to a whole ConfigMap or Secret
// (i.e. via an EnvFrom or a Volume) will result in one entry in the list, irrespective of
// whether individual elements are also references (i.e. via an Env entry).
func (h *Handler) getCurrentChildren(obj *appsv1.Deployment) ([]configObject, error) {
configMaps, secrets := getChildNamesByType(obj)

// get all of ConfigMaps and Secrets
resultsChan := make(chan getResult)
for name := range configMaps {
go func(name string) {
resultsChan <- h.getConfigMap(obj.GetNamespace(), name)
}(name)
for name, metadata := range configMaps {
go func(name string, metadata configMetadata) {
resultsChan <- h.getConfigMap(obj.GetNamespace(), name, metadata)
}(name, metadata)
}
for name := range secrets {
go func(name string) {
resultsChan <- h.getSecret(obj.GetNamespace(), name)
}(name)
for name, metadata := range secrets {
go func(name string, metadata configMetadata) {
resultsChan <- h.getSecret(obj.GetNamespace(), name, metadata)
}(name, metadata)
}

// Range over and collect results from the gets
var errs []string
var children []Object
var children []configObject
for i := 0; i < len(configMaps)+len(secrets); i++ {
result := <-resultsChan
if result.err != nil {
errs = append(errs, result.err.Error())
}
if result.obj != nil {
children = append(children, result.obj)
children = append(children, configObject{
object: result.obj,
required: result.metadata.required,
allKeys: result.metadata.allKeys,
keys: result.metadata.keys,
})
}
}

// If there were any errors, don't return any children
if len(errs) > 0 {
return []Object{}, fmt.Errorf("error(s) encountered when geting children: %s", strings.Join(errs, ", "))
return []configObject{}, fmt.Errorf("error(s) encountered when geting children: %s", strings.Join(errs, ", "))
}

// No errors, return the list of children
return children, nil
}

// getChildNamesByType parses the Deployment object and returns two sets,
// the first containing the names of all referenced ConfigMaps,
// the second containing the names of all referenced Secrets
func getChildNamesByType(obj *appsv1.Deployment) (map[string]struct{}, map[string]struct{}) {
// getChildNamesByType parses the Deployment object and returns two maps,
// the first containing ConfigMap metadata for all referenced ConfigMaps, keyed on the name of the ConfigMap,
// the second containing Secret metadata for all referenced Secrets, keyed on the name of the Secrets
func getChildNamesByType(obj *appsv1.Deployment) (map[string]configMetadata, map[string]configMetadata) {
// Create sets for storing the names fo the ConfigMaps/Secrets
configMaps := make(map[string]struct{})
secrets := make(map[string]struct{})
configMaps := make(map[string]configMetadata)
secrets := make(map[string]configMetadata)

// Range through all Volumes and check the VolumeSources for ConfigMaps
// and Secrets
for _, vol := range obj.Spec.Template.Spec.Volumes {
if cm := vol.VolumeSource.ConfigMap; cm != nil {
configMaps[cm.Name] = struct{}{}
configMaps[cm.Name] = configMetadata{required: true, allKeys: true}
}
if s := vol.VolumeSource.Secret; s != nil {
secrets[s.SecretName] = struct{}{}
secrets[s.SecretName] = configMetadata{required: true, allKeys: true}
}
}

Expand All @@ -99,38 +118,83 @@ func getChildNamesByType(obj *appsv1.Deployment) (map[string]struct{}, map[strin
for _, container := range obj.Spec.Template.Spec.Containers {
for _, env := range container.EnvFrom {
if cm := env.ConfigMapRef; cm != nil {
configMaps[cm.Name] = struct{}{}
configMaps[cm.Name] = configMetadata{required: true, allKeys: true}
}
if s := env.SecretRef; s != nil {
secrets[s.Name] = struct{}{}
secrets[s.Name] = configMetadata{required: true, allKeys: true}
}
}
}

// Range through all Containers and their respective Env
for _, container := range obj.Spec.Template.Spec.Containers {
for _, env := range container.Env {
if valFrom := env.ValueFrom; valFrom != nil {
if cm := valFrom.ConfigMapKeyRef; cm != nil {
JoelSpeed marked this conversation as resolved.
Show resolved Hide resolved
configMaps[cm.Name] = parseConfigMapKeyRef(configMaps[cm.Name], cm)
}
if s := valFrom.SecretKeyRef; s != nil {
secrets[s.Name] = parseSecretKeyRef(secrets[s.Name], s)
}
}
}
}

return configMaps, secrets
}

// parseConfigMapKeyRef updates the metadata for a ConfigMap to include the keys specified in this ConfigMapKeySelector
func parseConfigMapKeyRef(metadata configMetadata, cm *corev1.ConfigMapKeySelector) configMetadata {
if !metadata.allKeys {
if metadata.keys == nil {
metadata.keys = make(map[string]struct{})
}
if cm.Optional == nil || !*cm.Optional {
metadata.required = true
}
metadata.keys[cm.Key] = struct{}{}
}
return metadata
}

// parseSecretKeyRef updates the metadata for a Secret to include the keys specified in this SecretKeySelector
func parseSecretKeyRef(metadata configMetadata, s *corev1.SecretKeySelector) configMetadata {
if !metadata.allKeys {
if metadata.keys == nil {
metadata.keys = make(map[string]struct{})
}
if s.Optional == nil || !*s.Optional {
metadata.required = true
}
metadata.keys[s.Key] = struct{}{}
}
return metadata
}

// getConfigMap gets a ConfigMap with the given name and namespace from the
// API server.
func (h *Handler) getConfigMap(namespace, name string) getResult {
return h.getObject(namespace, name, &corev1.ConfigMap{})
func (h *Handler) getConfigMap(namespace, name string, metadata configMetadata) getResult {
return h.getObject(namespace, name, metadata, &corev1.ConfigMap{})
}

// getSecret gets a Secret with the given name and namespace from the
// API server.
func (h *Handler) getSecret(namespace, name string) getResult {
return h.getObject(namespace, name, &corev1.Secret{})
func (h *Handler) getSecret(namespace, name string, metadata configMetadata) getResult {
return h.getObject(namespace, name, metadata, &corev1.Secret{})
}

// getObject gets the Object with the given name and namespace from the API
// server
func (h *Handler) getObject(namespace, name string, obj Object) getResult {
key := types.NamespacedName{Namespace: namespace, Name: name}
err := h.Get(context.TODO(), key, obj)
func (h *Handler) getObject(namespace, name string, metadata configMetadata, obj Object) getResult {
objectName := types.NamespacedName{Namespace: namespace, Name: name}
err := h.Get(context.TODO(), objectName, obj)
if err != nil {
return getResult{err: err}
if metadata.required {
return getResult{err: err}
}
return getResult{metadata: metadata}
}
return getResult{obj: obj}
return getResult{obj: obj, metadata: metadata}
}

// getExistingChildren returns a list of all Secrets and ConfigMaps that are
Expand Down
Loading