Skip to content

Commit

Permalink
Merge pull request #34 from andyedwardsdfdl/master
Browse files Browse the repository at this point in the history
Handle env section in deployment yaml
  • Loading branch information
JoelSpeed authored Jan 28, 2019
2 parents 1d34554 + f2c8eaf commit 3f82777
Show file tree
Hide file tree
Showing 10 changed files with 779 additions and 92 deletions.
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)))
})
})

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
// 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 {
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

0 comments on commit 3f82777

Please sign in to comment.