diff --git a/it/BUILD.bazel b/it/BUILD.bazel index 7728f87..b83390e 100644 --- a/it/BUILD.bazel +++ b/it/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/go.uber.org/zap:go_default_library", "//vendor/go.uber.org/zap/zaptest: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/client/clientset/clientset:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1:go_default_library", @@ -34,6 +35,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", @@ -51,6 +53,7 @@ go_test( srcs = [ "adoption_test.go", "crd_attribute_test.go", + "deployment_dependencies_test.go", "deployment_ready_test.go", "main_test.go", "resource_deletion_test.go", @@ -67,6 +70,7 @@ go_test( "//examples/sleeper:go_default_library", "//examples/sleeper/pkg/apis/sleeper/v1:go_default_library", "//pkg/apis/smith/v1:go_default_library", + "//pkg/specchecker/builtin:go_default_library", "//pkg/util/testing:go_default_library", "//vendor/github.com/ash2k/stager:go_default_library", "//vendor/github.com/atlassian/ctrl/apis/condition/v1:go_default_library", @@ -76,7 +80,10 @@ go_test( "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", + "//vendor/k8s.io/client-go/tools/cache:go_default_library", + "//vendor/k8s.io/client-go/tools/watch:go_default_library", ], ) diff --git a/it/deployment_dependencies_test.go b/it/deployment_dependencies_test.go new file mode 100644 index 0000000..0cd6704 --- /dev/null +++ b/it/deployment_dependencies_test.go @@ -0,0 +1,196 @@ +package it + +import ( + "context" + "testing" + + smith_v1 "github.com/atlassian/smith/pkg/apis/smith/v1" + "github.com/atlassian/smith/pkg/specchecker/builtin" + "github.com/stretchr/testify/require" + 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/fields" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/tools/cache" + toolswatch "k8s.io/client-go/tools/watch" +) + +const ( + deploymentDependenciesConfigMapName = "map1" + deploymentDependenciesSecretName = "secret1" +) + +func TestConfigMapAndSecretToDeploymentToBundleIndex(t *testing.T) { + t.Parallel() + + bundle := constructDeploymentDependenciesBundle() + SetupApp(t, bundle, false, true, assertConfigMapAndSecretToDeploymentToBundleIndex) +} + +func assertConfigMapAndSecretToDeploymentToBundleIndex(ctx context.Context, t *testing.T, cfg *Config, args ...interface{}) { + // initial create of ConfigMap and Secret + cm, err := cfg.MainClient.CoreV1().ConfigMaps(cfg.Namespace).Create(deploymentDependenciesConfigMap()) + require.NoError(cfg.T, err) + secret, err := cfg.MainClient.CoreV1().Secrets(cfg.Namespace).Create(deploymentDependenciesSecret()) + require.NoError(cfg.T, err) + + // Wait for stable state + deploymentName := cfg.Bundle.Spec.Resources[0].Spec.Object.(meta_v1.Object).GetName() + lw := cache.NewListWatchFromClient(cfg.MainClient.AppsV1().RESTClient(), "deployments", cfg.Namespace, fields.Everything()) + cond := IsPodSpecAnnotationCond(t, cfg.Namespace, deploymentName, builtin.EnvRefHashAnnotation, "97b5461821487e047e760880764dd9b31a5b7f39e8961b98b809c6d3f5b3cf3a") + _, err = toolswatch.UntilWithSync(ctx, lw, &apps_v1.Deployment{}, nil, cond) + require.NoError(cfg.T, err) + + // Update ConfigMap + cm.Data["b"] = "a" + _, err = cfg.MainClient.CoreV1().ConfigMaps(cfg.Namespace).Update(cm) + require.NoError(cfg.T, err) + + // Wait for updated annotation + cond = IsPodSpecAnnotationCond(t, cfg.Namespace, deploymentName, builtin.EnvRefHashAnnotation, "5fb60505d125dd42160a9ef237873345020a90e8d607861ced50a641c3a800a9") + _, err = toolswatch.UntilWithSync(ctx, lw, &apps_v1.Deployment{}, nil, cond) + require.NoError(cfg.T, err) + + // Update Secret + secret.StringData = map[string]string{ + "z": "c", + } + _, err = cfg.MainClient.CoreV1().Secrets(cfg.Namespace).Update(secret) + require.NoError(cfg.T, err) + + // Wait for updated annotation + cond = IsPodSpecAnnotationCond(t, cfg.Namespace, deploymentName, builtin.EnvRefHashAnnotation, "20ed86a5106aab7e892f498f38dc1b713405d864878e6a074377fbdcba129599") + _, err = toolswatch.UntilWithSync(ctx, lw, &apps_v1.Deployment{}, nil, cond) + require.NoError(cfg.T, err) +} + +func constructDeploymentDependenciesBundle() *smith_v1.Bundle { + var ( + replicas int32 = 1 + minReadySeconds int32 = 1 + revisionHistoryLimit int32 = 10 + progressDeadlineSeconds int32 = 5 + terminationGracePeriodSeconds int64 = 30 + labelMap = map[string]string{ + "name": string(deploymentResourceName), + } + ) + return &smith_v1.Bundle{ + TypeMeta: meta_v1.TypeMeta{ + Kind: smith_v1.BundleResourceKind, + APIVersion: smith_v1.BundleResourceGroupVersion, + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: "bundle-dt", + }, + Spec: smith_v1.BundleSpec{ + Resources: []smith_v1.Resource{ + { + Name: deploymentResourceName, + Spec: smith_v1.ResourceSpec{ + Object: &apps_v1.Deployment{ + TypeMeta: meta_v1.TypeMeta{ + Kind: "Deployment", + APIVersion: apps_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: string(deploymentResourceName), + }, + Spec: apps_v1.DeploymentSpec{ + Selector: &meta_v1.LabelSelector{ + MatchLabels: labelMap, + }, + Replicas: &replicas, + Template: core_v1.PodTemplateSpec{ + ObjectMeta: meta_v1.ObjectMeta{ + Labels: labelMap, + }, + Spec: core_v1.PodSpec{ + Containers: []core_v1.Container{ + { + Name: "container1", + Image: "roaanv/k8sti", + EnvFrom: []core_v1.EnvFromSource{ + { + ConfigMapRef: &core_v1.ConfigMapEnvSource{ + LocalObjectReference: core_v1.LocalObjectReference{ + Name: deploymentDependenciesConfigMapName, + }, + }, + }, + { + SecretRef: &core_v1.SecretEnvSource{ + LocalObjectReference: core_v1.LocalObjectReference{ + Name: deploymentDependenciesSecretName, + }, + }, + }, + }, + ImagePullPolicy: core_v1.PullAlways, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: core_v1.TerminationMessageReadFile, + }, + }, + DNSPolicy: core_v1.DNSClusterFirst, + RestartPolicy: core_v1.RestartPolicyAlways, + SchedulerName: "default-scheduler", + SecurityContext: &core_v1.PodSecurityContext{}, + TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, + }, + }, + MinReadySeconds: minReadySeconds, + ProgressDeadlineSeconds: &progressDeadlineSeconds, + RevisionHistoryLimit: &revisionHistoryLimit, + + Strategy: apps_v1.DeploymentStrategy{ + Type: apps_v1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &apps_v1.RollingUpdateDeployment{ + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.String, + StrVal: "25%", + }, + MaxSurge: &intstr.IntOrString{ + Type: intstr.String, + StrVal: "25%", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func deploymentDependenciesConfigMap() *core_v1.ConfigMap { + return &core_v1.ConfigMap{ + TypeMeta: meta_v1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: core_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: deploymentDependenciesConfigMapName, + }, + Data: map[string]string{ + "a": "b", + }, + } +} + +func deploymentDependenciesSecret() *core_v1.Secret { + return &core_v1.Secret{ + TypeMeta: meta_v1.TypeMeta{ + Kind: "Secret", + APIVersion: core_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: deploymentDependenciesSecretName, + }, + StringData: map[string]string{ + "x": "b", + }, + } +} diff --git a/it/utils_for_tests.go b/it/utils_for_tests.go index 201f5f6..4dd3685 100644 --- a/it/utils_for_tests.go +++ b/it/utils_for_tests.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest" + apps_v1 "k8s.io/api/apps/v1" core_v1 "k8s.io/api/core/v1" apiExtClientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apiext_v1b1inf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1" @@ -36,6 +37,7 @@ import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -45,6 +47,14 @@ import ( toolswatch "k8s.io/client-go/tools/watch" ) +var ( + appsV1Scheme = runtime.NewScheme() +) + +func init() { + utilruntime.Must(apps_v1.SchemeBuilder.AddToScheme(appsV1Scheme)) +} + type TestFunc func(context.Context, *testing.T, *Config, ...interface{}) type Config struct { @@ -161,6 +171,31 @@ func IsBundleObservedGenerationCond(namespace, name string) toolswatch.Condition } } +func IsPodSpecAnnotationCond(t *testing.T, namespace, name, annotation, value string) toolswatch.ConditionFunc { + return func(event watch.Event) (bool, error) { + metaObj := event.Object.(meta_v1.Object) + if metaObj.GetNamespace() != namespace || metaObj.GetName() != name { + return false, nil + } + deployment := &apps_v1.Deployment{} + err := util.ConvertType(appsV1Scheme, event.Object, deployment) + if err != nil { + return false, err + } + annotations := deployment.Spec.Template.Annotations + actual, ok := annotations[annotation] + if !ok { + t.Logf("Pod Spec annotation %s is not set", annotation) + return false, nil + } + if actual != value { + t.Logf("Ignoring Pod Spec annotation %s value %s. Expecting %s", annotation, actual, value) + return false, nil + } + return true, nil + } +} + func TestSetup(t *testing.T) (*rest.Config, *kubernetes.Clientset, *smithClientset.Clientset) { config, err := options.LoadRestClientConfig("voyager-test", options.RestClientOptions{ APIQPS: 10,