diff --git a/cmd/smith/app/bundle_controller.go b/cmd/smith/app/bundle_controller.go index 49ec252..123ffeb 100644 --- a/cmd/smith/app/bundle_controller.go +++ b/cmd/smith/app/bundle_controller.go @@ -114,8 +114,9 @@ func (c *BundleControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Contex if err != nil { return nil, err } + crdGVK := apiext_v1b1.SchemeGroupVersion.WithKind("CustomResourceDefinition") crdInf, err := apiExtensionsInformer(config, cctx, apiExtClient, - apiext_v1b1.SchemeGroupVersion.WithKind("CustomResourceDefinition"), + crdGVK, apiext_v1b1inf.NewCustomResourceDefinitionInformer) if err != nil { return nil, err @@ -146,7 +147,7 @@ func (c *BundleControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Contex if err != nil { return nil, err } - resourceInfs[apiext_v1b1.SchemeGroupVersion.WithKind("CustomResourceDefinition")] = crdInf + resourceInfs[crdGVK] = crdInf resourceInfs[smith_v1.BundleGVK] = bundleInf for gvk, inf := range resourceInfs { if err = multiStore.AddInformer(gvk, inf); err != nil { @@ -224,7 +225,10 @@ func (c *BundleControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Contex Broadcaster: broadcaster, Recorder: recorder, } - cntrlr.Prepare(crdInf, resourceInfs) + err = cntrlr.Prepare(crdInf, resourceInfs) + if err != nil { + return nil, err + } return &ctrl.Constructed{ Interface: cntrlr, diff --git a/pkg/controller/bundlec/BUILD.bazel b/pkg/controller/bundlec/BUILD.bazel index 0357f8e..9362baa 100644 --- a/pkg/controller/bundlec/BUILD.bazel +++ b/pkg/controller/bundlec/BUILD.bazel @@ -34,6 +34,7 @@ go_library( "//vendor/github.com/pkg/errors:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/go.uber.org/zap:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", diff --git a/pkg/controller/bundlec/controller.go b/pkg/controller/bundlec/controller.go index ac1fac1..a2cf663 100644 --- a/pkg/controller/bundlec/controller.go +++ b/pkg/controller/bundlec/controller.go @@ -13,8 +13,11 @@ import ( "github.com/atlassian/smith/pkg/plugin" "github.com/atlassian/smith/pkg/statuschecker" "github.com/atlassian/smith/pkg/store" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" + apps_v1 "k8s.io/api/apps/v1" + core_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -24,6 +27,14 @@ import ( "k8s.io/client-go/tools/record" ) +const ( + byConfigMapNamespaceNameIndexName = "ByConfigMap" + bySecretNamespaceNameIndexName = "BySecret" +) + +type byIndexFunc func(indexName, indexKey string) ([]interface{}, error) +type indexKeyFunc func(namespace, name string) string + type Controller struct { // wg.Wait() is called from Run() and first wg.Add() may be called concurrently from CRD listener // to start an Informer. This is a data race. This mutex is used to ensure ordering. @@ -66,23 +77,50 @@ type Controller struct { } // Prepare prepares the controller to be run. -func (c *Controller) Prepare(crdInf cache.SharedIndexInformer, resourceInfs map[schema.GroupVersionKind]cache.SharedIndexInformer) { +func (c *Controller) Prepare(crdInf cache.SharedIndexInformer, resourceInfs map[schema.GroupVersionKind]cache.SharedIndexInformer) error { c.crdContext, c.crdContextCancel = context.WithCancel(context.Background()) crdInf.AddEventHandler(&crdEventHandler{ controller: c, watchers: make(map[string]watchState), }) - + deploymentInf := resourceInfs[apps_v1.SchemeGroupVersion.WithKind("Deployment")] + err := deploymentInf.AddIndexers(cache.Indexers{ + byConfigMapNamespaceNameIndexName: byConfigMapNamespaceNameIndex, + bySecretNamespaceNameIndexName: bySecretNamespaceNameIndex, + }) + if err != nil { + return errors.WithStack(err) + } + deploymentByIndex := deploymentInf.GetIndexer().ByIndex + // ConfigMap -> Deployment -> Bundle event propagation + configMapGVK := core_v1.SchemeGroupVersion.WithKind("ConfigMap") + configMapInf := resourceInfs[configMapGVK] + configMapInf.AddEventHandler(&handlers.LookupHandler{ + Logger: c.Logger, + WorkQueue: c.WorkQueue, + Gvk: configMapGVK, + Lookup: c.lookupBundleByDeploymentByIndex(deploymentByIndex, byConfigMapNamespaceNameIndexName, byConfigMapNamespaceNameIndexKey), + }) + // Secret -> Deployment -> Bundle event propagation + secretGVK := core_v1.SchemeGroupVersion.WithKind("Secret") + secretInf := resourceInfs[secretGVK] + secretInf.AddEventHandler(&handlers.LookupHandler{ + Logger: c.Logger, + WorkQueue: c.WorkQueue, + Gvk: secretGVK, + Lookup: c.lookupBundleByDeploymentByIndex(deploymentByIndex, bySecretNamespaceNameIndexName, bySecretNamespaceNameIndexKey), + }) + // Standard handler for gvk, resourceInf := range resourceInfs { - resourceHandler := &handlers.ControlledResourceHandler{ + resourceInf.AddEventHandler(&handlers.ControlledResourceHandler{ Logger: c.Logger, WorkQueue: c.WorkQueue, ControllerIndex: &controllerIndexAdapter{bundleStore: c.BundleStore}, ControllerGvk: smith_v1.BundleGVK, Gvk: gvk, - } - resourceInf.AddEventHandler(resourceHandler) + }) } + return nil } // Run begins watching and syncing. @@ -110,6 +148,37 @@ func (c *Controller) Run(ctx context.Context) { <-ctx.Done() } +// lookupBundleByDeploymentByIndex returns a function that can be used to perform lookups of Bundles that contain +// Deployment objects that reference ConfigMap/Secret objects. +func (c *Controller) lookupBundleByDeploymentByIndex(byIndex byIndexFunc, indexName string, indexKey indexKeyFunc) func(runtime.Object) ([]runtime.Object, error) { + deploymentGK := schema.GroupKind{ + Group: apps_v1.GroupName, + Kind: "Deployment", + } + return func(obj runtime.Object) ([]runtime.Object /*bundles*/, error) { + // obj is ConfigMap or Secret + objMeta := obj.(meta_v1.Object) + // find all Deployments that reference this obj + deploymentsFromIndex, err := byIndex(indexName, indexKey(objMeta.GetNamespace(), objMeta.GetName())) + if err != nil { + return nil, err + } + var bundles []runtime.Object + for _, deploymentInterface := range deploymentsFromIndex { + deployment := deploymentInterface.(*apps_v1.Deployment) + // find all Bundles that reference this Deployment + bundlesForDeployment, err := c.BundleStore.GetBundlesByObject(deploymentGK, deployment.Namespace, deployment.Name) + if err != nil { + return nil, err + } + for _, bundle := range bundlesForDeployment { + bundles = append(bundles, bundle) + } + } + return bundles, nil + } +} + type controllerIndexAdapter struct { bundleStore BundleStore } @@ -125,3 +194,77 @@ func (c *controllerIndexAdapter) ControllerByObject(gk schema.GroupKind, namespa } return objs, nil } + +func byConfigMapNamespaceNameIndex(obj interface{}) ([]string, error) { + d := obj.(*apps_v1.Deployment) + index := configMapNamespaceNameIndexKeysForContainers(d.Namespace, d.Spec.Template.Spec.Containers) + index = append(index, configMapNamespaceNameIndexKeysForContainers(d.Namespace, d.Spec.Template.Spec.InitContainers)...) + return index, nil +} + +func configMapNamespaceNameIndexKeysForContainers(namespace string, containers []core_v1.Container) []string { + var indexKeys []string + for _, container := range containers { + for _, envFrom := range container.EnvFrom { + configMapRef := envFrom.ConfigMapRef + if configMapRef == nil { + continue + } + indexKeys = append(indexKeys, bySecretNamespaceNameIndexKey(namespace, configMapRef.Name)) + } + for _, env := range container.Env { + valueFrom := env.ValueFrom + if valueFrom == nil { + continue + } + + configMapKeyRef := valueFrom.ConfigMapKeyRef + if configMapKeyRef == nil { + continue + } + indexKeys = append(indexKeys, bySecretNamespaceNameIndexKey(namespace, configMapKeyRef.Name)) + } + } + return indexKeys +} + +func byConfigMapNamespaceNameIndexKey(configMapNamespace, configMapName string) string { + return configMapNamespace + "/" + configMapName +} + +func bySecretNamespaceNameIndex(obj interface{}) ([]string, error) { + d := obj.(*apps_v1.Deployment) + index := secretNamespaceNameIndexKeysForContainers(d.Namespace, d.Spec.Template.Spec.Containers) + index = append(index, secretNamespaceNameIndexKeysForContainers(d.Namespace, d.Spec.Template.Spec.InitContainers)...) + return index, nil +} + +func secretNamespaceNameIndexKeysForContainers(namespace string, containers []core_v1.Container) []string { + var indexKeys []string + for _, container := range containers { + for _, envFrom := range container.EnvFrom { + secretRef := envFrom.SecretRef + if secretRef == nil { + continue + } + indexKeys = append(indexKeys, bySecretNamespaceNameIndexKey(namespace, secretRef.Name)) + } + for _, env := range container.Env { + valueFrom := env.ValueFrom + if valueFrom == nil { + continue + } + + secretKeyRef := valueFrom.SecretKeyRef + if secretKeyRef == nil { + continue + } + indexKeys = append(indexKeys, bySecretNamespaceNameIndexKey(namespace, secretKeyRef.Name)) + } + } + return indexKeys +} + +func bySecretNamespaceNameIndexKey(secretNamespace, secretName string) string { + return secretNamespace + "/" + secretName +} diff --git a/pkg/specchecker/builtin/process_deployment.go b/pkg/specchecker/builtin/process_deployment.go index 2c3bfe2..2b7101c 100644 --- a/pkg/specchecker/builtin/process_deployment.go +++ b/pkg/specchecker/builtin/process_deployment.go @@ -171,11 +171,12 @@ func (deployment) generateHashForContainers(store specchecker.Store, namespace s } } for _, env := range container.Env { - if env.ValueFrom == nil { + valueFrom := env.ValueFrom + if valueFrom == nil { continue } - secretKeyRef := env.ValueFrom.SecretKeyRef + secretKeyRef := valueFrom.SecretKeyRef if secretKeyRef != nil { err := specchecker.HashSecretRef(store, namespace, secretKeyRef.Name, sets.NewString(secretKeyRef.Key), secretKeyRef.Optional, hasher) if err != nil { @@ -183,7 +184,7 @@ func (deployment) generateHashForContainers(store specchecker.Store, namespace s } } - configMapKeyRef := env.ValueFrom.ConfigMapKeyRef + configMapKeyRef := valueFrom.ConfigMapKeyRef if configMapKeyRef != nil { err := specchecker.HashConfigMapRef(store, namespace, configMapKeyRef.Name, sets.NewString(configMapKeyRef.Key), configMapKeyRef.Optional, hasher) if err != nil {