From 7ff089371ba766673125f0d360e47afe6e1e5ee8 Mon Sep 17 00:00:00 2001 From: Joe Bowbeer Date: Tue, 1 Aug 2023 05:13:33 -0700 Subject: [PATCH 1/2] feat: initContainers --- internal/k8s/converter.go | 18 +++++++++ internal/k8s/resource.go | 44 ++++++++++++++++++++++ provider/kubernetes/kubernetes.go | 3 ++ provider/kubernetes/updates.go | 62 +++++++++++++++++++++++++++++++ types/types.go | 3 ++ 5 files changed, 130 insertions(+) diff --git a/internal/k8s/converter.go b/internal/k8s/converter.go index 3b3eae19..6bb17766 100644 --- a/internal/k8s/converter.go +++ b/internal/k8s/converter.go @@ -33,7 +33,12 @@ func updateDeploymentContainer(d *apps_v1.Deployment, index int, image string) { d.Spec.Template.Spec.Containers[index].Image = image } +func updateDeploymentInitContainer(d *apps_v1.Deployment, index int, image string) { + d.Spec.Template.Spec.InitContainers[index].Image = image +} + // stateful sets https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/ + func getStatefulSetIdentifier(ss *apps_v1.StatefulSet) string { return "statefulset/" + ss.Namespace + "/" + ss.Name } @@ -42,6 +47,10 @@ func updateStatefulSetContainer(ss *apps_v1.StatefulSet, index int, image string ss.Spec.Template.Spec.Containers[index].Image = image } +func updateStatefulSetInitContainer(ss *apps_v1.StatefulSet, index int, image string) { + ss.Spec.Template.Spec.InitContainers[index].Image = image +} + // daemonsets func getDaemonsetSetIdentifier(s *apps_v1.DaemonSet) string { @@ -52,6 +61,10 @@ func updateDaemonsetSetContainer(s *apps_v1.DaemonSet, index int, image string) s.Spec.Template.Spec.Containers[index].Image = image } +func updateDaemonsetSetInitContainer(s *apps_v1.DaemonSet, index int, image string) { + s.Spec.Template.Spec.InitContainers[index].Image = image +} + // cron func getCronJobIdentifier(s *batch_v1.CronJob) string { @@ -61,3 +74,8 @@ func getCronJobIdentifier(s *batch_v1.CronJob) string { func updateCronJobContainer(s *batch_v1.CronJob, index int, image string) { s.Spec.JobTemplate.Spec.Template.Spec.Containers[index].Image = image } + +func updateCronJobInitContainer(s *batch_v1.CronJob, index int, image string) { + s.Spec.JobTemplate.Spec.Template.Spec.InitContainers[index].Image = image +} + \ No newline at end of file diff --git a/internal/k8s/resource.go b/internal/k8s/resource.go index 42e2ede0..2cfdd04e 100644 --- a/internal/k8s/resource.go +++ b/internal/k8s/resource.go @@ -275,6 +275,21 @@ func (r *GenericResource) GetImages() (images []string) { return } +// GetInitImages - returns init images used by this resource +func (r *GenericResource) GetInitImages() (images []string) { + switch obj := r.obj.(type) { + case *apps_v1.Deployment: + return getContainerImages(obj.Spec.Template.Spec.InitContainers) + case *apps_v1.StatefulSet: + return getContainerImages(obj.Spec.Template.Spec.InitContainers) + case *apps_v1.DaemonSet: + return getContainerImages(obj.Spec.Template.Spec.InitContainers) + case *batch_v1.CronJob: + return getContainerImages(obj.Spec.JobTemplate.Spec.Template.Spec.InitContainers) + } + return +} + // Containers - returns containers managed by this resource func (r *GenericResource) Containers() (containers []core_v1.Container) { switch obj := r.obj.(type) { @@ -290,6 +305,21 @@ func (r *GenericResource) Containers() (containers []core_v1.Container) { return } +// InitContainers - returns init containers managed by this resource +func (r *GenericResource) InitContainers() (containers []core_v1.Container) { + switch obj := r.obj.(type) { + case *apps_v1.Deployment: + return obj.Spec.Template.Spec.InitContainers + case *apps_v1.StatefulSet: + return obj.Spec.Template.Spec.InitContainers + case *apps_v1.DaemonSet: + return obj.Spec.Template.Spec.InitContainers + case *batch_v1.CronJob: + return obj.Spec.JobTemplate.Spec.Template.Spec.InitContainers + } + return +} + // UpdateContainer - updates container image func (r *GenericResource) UpdateContainer(index int, image string) { switch obj := r.obj.(type) { @@ -304,6 +334,20 @@ func (r *GenericResource) UpdateContainer(index int, image string) { } } +// UpdateInitContainer - updates init container image +func (r *GenericResource) UpdateInitContainer(index int, image string) { + switch obj := r.obj.(type) { + case *apps_v1.Deployment: + updateDeploymentInitContainer(obj, index, image) + case *apps_v1.StatefulSet: + updateStatefulSetInitContainer(obj, index, image) + case *apps_v1.DaemonSet: + updateDaemonsetSetInitContainer(obj, index, image) + case *batch_v1.CronJob: + updateCronJobInitContainer(obj, index, image) + } +} + type Status struct { // Total number of non-terminated pods targeted by this deployment (their labels match the selector). // +optional diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index db6c5c53..0f05a64d 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -187,6 +187,9 @@ func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) { secrets = append(secrets, gr.GetImagePullSecrets()...) images := gr.GetImages() + if schedule, ok := annotations[types.KeelInitContainerAnnotation]; ok && schedule == "true" { + images = append(images, gr.GetInitImages()...) + } for _, img := range images { ref, err := image.Parse(img) if err != nil { diff --git a/provider/kubernetes/updates.go b/provider/kubernetes/updates.go index 965e33d6..44583ddb 100644 --- a/provider/kubernetes/updates.go +++ b/provider/kubernetes/updates.go @@ -27,6 +27,68 @@ func checkForUpdate(plc policy.Policy, repo *types.Repository, resource *k8s.Gen "policy": plc.Name(), }).Debug("provider.kubernetes.checkVersionedDeployment: keel policy found, checking resource...") shouldUpdateDeployment = false + if schedule, ok := resource.GetAnnotations()[types.KeelInitContainerAnnotation]; ok && schedule == "true" { + for idx, c := range resource.InitContainers() { + containerImageRef, err := image.Parse(c.Image) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "image_name": c.Image, + }).Error("provider.kubernetes: failed to parse image name") + continue + } + + log.WithFields(log.Fields{ + "name": resource.Name, + "namespace": resource.Namespace, + "kind": resource.Kind(), + "parsed_image_name": containerImageRef.Remote(), + "target_image_name": repo.Name, + "target_tag": repo.Tag, + "policy": plc.Name(), + "image": c.Image, + }).Debug("provider.kubernetes: checking image") + + if containerImageRef.Repository() != eventRepoRef.Repository() { + log.WithFields(log.Fields{ + "parsed_image_name": containerImageRef.Remote(), + "target_image_name": repo.Name, + }).Debug("provider.kubernetes: images do not match, ignoring") + continue + } + + shouldUpdateContainer, err := plc.ShouldUpdate(containerImageRef.Tag(), eventRepoRef.Tag()) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "parsed_image_name": containerImageRef.Remote(), + "target_image_name": repo.Name, + "policy": plc.Name(), + }).Error("provider.kubernetes: failed to check whether init container should be updated") + continue + } + + if !shouldUpdateContainer { + continue + } + + // updating spec template annotations + setUpdateTime(resource) + + // updating image + if containerImageRef.Registry() == image.DefaultRegistryHostname { + resource.UpdateInitContainer(idx, fmt.Sprintf("%s:%s", containerImageRef.ShortName(), repo.Tag)) + } else { + resource.UpdateInitContainer(idx, fmt.Sprintf("%s:%s", containerImageRef.Repository(), repo.Tag)) + } + + shouldUpdateDeployment = true + + updatePlan.CurrentVersion = containerImageRef.Tag() + updatePlan.NewVersion = repo.Tag + updatePlan.Resource = resource + } + } for idx, c := range resource.Containers() { containerImageRef, err := image.Parse(c.Image) if err != nil { diff --git a/types/types.go b/types/types.go index bd578584..fc986c9d 100644 --- a/types/types.go +++ b/types/types.go @@ -39,6 +39,9 @@ const KeelMatchPreReleaseAnnotation = "keel.sh/matchPreRelease" // KeelPollScheduleAnnotation - optional variable to setup custom schedule for polling, defaults to @every 10m const KeelPollScheduleAnnotation = "keel.sh/pollSchedule" +// KeelInitContainerAnnotation - label or annotation to update init containers, defaults to false for backward compatibility +const KeelInitContainerAnnotation = "keel.sh/initContainers" + // KeelPollDefaultSchedule - defaul polling schedule var KeelPollDefaultSchedule = "@every 1m" From ee938a7b82d8b38b41d5e5d5b9ee103e7012f6aa Mon Sep 17 00:00:00 2001 From: Joe Bowbeer Date: Wed, 9 Aug 2023 03:46:49 +0000 Subject: [PATCH 2/2] add unit tests --- internal/k8s/resource_test.go | 50 +++++ provider/kubernetes/kubernetes.go | 21 +- provider/kubernetes/kubernetes_test.go | 273 +++++++++++++++++++++++++ provider/kubernetes/updates_test.go | 105 ++++++++++ types/types.go | 2 +- 5 files changed, 449 insertions(+), 2 deletions(-) diff --git a/internal/k8s/resource_test.go b/internal/k8s/resource_test.go index 592c2752..18a09b61 100644 --- a/internal/k8s/resource_test.go +++ b/internal/k8s/resource_test.go @@ -48,6 +48,56 @@ func TestDeployment(t *testing.T) { } } +func TestDeploymentInitContainer(t *testing.T) { + d := &apps_v1.Deployment{ + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Annotations: map[string]string{}, + Labels: map[string]string{}, + }, + apps_v1.DeploymentSpec{ + Template: core_v1.PodTemplateSpec{ + Spec: core_v1.PodSpec{ + Containers: []core_v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1.1", + }, + }, + InitContainers: []core_v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1.1", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + } + + gr, err := NewGenericResource(d) + if err != nil { + t.Fatalf("failed to create generic resource: %s", err) + } + + gr.UpdateContainer(0, "hey/there") + gr.UpdateInitContainer(0, "over/here") + + updated, ok := gr.GetResource().(*apps_v1.Deployment) + if !ok { + t.Fatalf("conversion failed") + } + + if updated.Spec.Template.Spec.Containers[0].Image != "hey/there" { + t.Errorf("unexpected image: %s", updated.Spec.Template.Spec.Containers[0].Image) + } + + if updated.Spec.Template.Spec.InitContainers[0].Image != "over/here" { + t.Errorf("unexpected image: %s", updated.Spec.Template.Spec.InitContainers[0].Image) + } +} + func TestDeploymentMultipleContainers(t *testing.T) { d := &apps_v1.Deployment{ meta_v1.TypeMeta{}, diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 0f05a64d..816f309b 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -145,6 +145,25 @@ func getImagePullSecretFromMeta(labels map[string]string, annotations map[string return "" } +func getInitContainerTrackingFromMeta(labels map[string]string, annotations map[string]string) bool { + + searchKey := strings.ToLower(types.KeelInitContainerAnnotation) + + for k, v := range labels { + if strings.ToLower(k) == searchKey { + return v == "true" + } + } + + for k, v := range annotations { + if strings.ToLower(k) == searchKey { + return v == "true" + } + } + + return false +} + // TrackedImages returns a list of tracked images. func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) { var trackedImages []*types.TrackedImage @@ -187,7 +206,7 @@ func (p *Provider) TrackedImages() ([]*types.TrackedImage, error) { secrets = append(secrets, gr.GetImagePullSecrets()...) images := gr.GetImages() - if schedule, ok := annotations[types.KeelInitContainerAnnotation]; ok && schedule == "true" { + if getInitContainerTrackingFromMeta(labels, annotations) { images = append(images, gr.GetInitImages()...) } for _, img := range images { diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 7d686fb3..5586c993 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -296,6 +296,107 @@ func TestGetImpacted(t *testing.T) { } } + +func TestGetImpactedInit(t *testing.T) { + fp := &fakeImplementer{} + fp.namespaces = &v1.NamespaceList{ + Items: []v1.Namespace{ + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{Name: "xxxx"}, + v1.NamespaceSpec{}, + v1.NamespaceStatus{}, + }, + }, + } + + deps := []*apps_v1.Deployment{ + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Annotations: map[string]string{types.KeelInitContainerAnnotation: "true"}, + Labels: map[string]string{types.KeelPolicyLabel: "all"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1.1", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }, + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-2", + Namespace: "xxxx", + Annotations: map[string]string{types.KeelInitContainerAnnotation: "false"}, + Labels: map[string]string{"whatever": "all"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1.1", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }, + } + + grs := MustParseGRS(deps) + grc := &k8s.GenericResourceCache{} + grc.Add(grs...) + + approver, teardown := approver() + defer teardown() + provider, err := NewProvider(fp, &fakeSender{}, approver, grc) + if err != nil { + t.Fatalf("failed to get provider: %s", err) + } + + // creating "new version" event + repo := &types.Repository{ + Name: "gcr.io/v2-namespace/hello-world", + Tag: "1.1.2", + } + + plans, err := provider.createUpdatePlans(repo) + if err != nil { + t.Errorf("failed to get deployments: %s", err) + } + + if len(plans) != 1 { + t.Fatalf("expected to find 1 deployment update plan but found %d", len(plans)) + } + + found := false + for _, c := range plans[0].Resource.InitContainers() { + + containerImageName := versionreg.ReplaceAllString(c.Image, "") + + if containerImageName == repo.Name { + found = true + } + } + + if !found { + t.Errorf("couldn't find expected deployment in impacted deployment list") + } + +} + func TestGetImpactedPolicyAnnotations(t *testing.T) { fp := &fakeImplementer{} fp.namespaces = &v1.NamespaceList{ @@ -1010,6 +1111,107 @@ func TestGetImpactedTwoContainersInSameDeployment(t *testing.T) { } +// Test to check how many deployments are "impacted" if we have two init containers +func TestGetImpactedTwoInitContainersInSameDeployment(t *testing.T) { + fp := &fakeImplementer{} + fp.namespaces = &v1.NamespaceList{ + Items: []v1.Namespace{ + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{Name: "xxxx"}, + v1.NamespaceSpec{}, + v1.NamespaceStatus{}, + }, + }, + } + deps := []*apps_v1.Deployment{ + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Labels: map[string]string{types.KeelPolicyLabel: "all"}, + Annotations: map[string]string{types.KeelInitContainerAnnotation: "true"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1.1", + }, + { + Image: "gcr.io/v2-namespace/greetings-world:1.1.1", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }, + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-2", + Namespace: "xxxx", + Labels: map[string]string{"whatever": "all"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1.1", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }, + } + grs := MustParseGRS(deps) + grc := &k8s.GenericResourceCache{} + grc.Add(grs...) + + approver, teardown := approver() + defer teardown() + provider, err := NewProvider(fp, &fakeSender{}, approver, grc) + if err != nil { + t.Fatalf("failed to get provider: %s", err) + } + + // creating "new version" event + repo := &types.Repository{ + Name: "gcr.io/v2-namespace/hello-world", + Tag: "1.1.2", + } + + plans, err := provider.createUpdatePlans(repo) + if err != nil { + t.Errorf("failed to get deployments: %s", err) + } + + if len(plans) != 1 { + t.Errorf("expected to find 1 deployment but found %d", len(plans)) + } + + found := false + for _, c := range plans[0].Resource.InitContainers() { + + containerImageName := versionreg.ReplaceAllString(c.Image, "") + + if containerImageName == repo.Name { + found = true + } + } + + if !found { + t.Errorf("couldn't find expected deployment in impacted deployment list") + } + +} + func TestGetImpactedTwoSameContainersInSameDeployment(t *testing.T) { fp := &fakeImplementer{} @@ -1445,3 +1647,74 @@ func TestTrackedImagesWithSecrets(t *testing.T) { t.Errorf("expected very-secret, got: %s", imgs[0].Secrets[1]) } } + +func TestTrackedInitImagesWithSecrets(t *testing.T) { + fp := &fakeImplementer{} + fp.namespaces = &v1.NamespaceList{ + Items: []v1.Namespace{ + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{Name: "xxxx"}, + v1.NamespaceSpec{}, + v1.NamespaceStatus{}, + }, + }, + } + deps := []*apps_v1.Deployment{ + { + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Labels: map[string]string{ + types.KeelPolicyLabel: "all", + types.KeelImagePullSecretAnnotation: "foo-bar", + types.KeelInitContainerAnnotation: "true", + }, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + ImagePullSecrets: []v1.LocalObjectReference{ + { + Name: "very-secret", + }, + }, + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:1.1", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }, + } + + grs := MustParseGRS(deps) + grc := &k8s.GenericResourceCache{} + grc.Add(grs...) + + approver, teardown := approver() + defer teardown() + provider, err := NewProvider(fp, &fakeSender{}, approver, grc) + if err != nil { + t.Fatalf("failed to get provider: %s", err) + } + + imgs, err := provider.TrackedImages() + if err != nil { + t.Errorf("failed to get image: %s", err) + } + if len(imgs) != 1 { + t.Errorf("expected to find 1 image, got: %d", len(imgs)) + } + + if imgs[0].Secrets[0] != "foo-bar" { + t.Errorf("expected foo-bar, got: %s", imgs[0].Secrets[0]) + } + if imgs[0].Secrets[1] != "very-secret" { + t.Errorf("expected very-secret, got: %s", imgs[0].Secrets[1]) + } +} diff --git a/provider/kubernetes/updates_test.go b/provider/kubernetes/updates_test.go index 997e83f1..d4b33deb 100644 --- a/provider/kubernetes/updates_test.go +++ b/provider/kubernetes/updates_test.go @@ -627,6 +627,111 @@ func TestProvider_checkForUpdate(t *testing.T) { wantShouldUpdateDeployment: true, wantErr: false, }, + + { + name: "update init container if tracking is enabled", + args: args{ + policy: policy.NewForcePolicy(false), + repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "latest"}, + resource: MustParseGR(&apps_v1.Deployment{ + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Annotations: map[string]string{types.KeelInitContainerAnnotation: "true"}, + Labels: map[string]string{types.KeelPolicyLabel: "all"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "this": "that", + }, + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }), + }, + wantUpdatePlan: &UpdatePlan{ + Resource: MustParseGR(&apps_v1.Deployment{ + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Annotations: map[string]string{types.KeelInitContainerAnnotation: "true"}, + Labels: map[string]string{types.KeelPolicyLabel: "all"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "this": "that", + }, + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world:latest", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }), + NewVersion: "latest", + CurrentVersion: "latest", + }, + wantShouldUpdateDeployment: true, + wantErr: false, + }, + { + name: "do not update init container if tracking is disabled (default)", + args: args{ + policy: policy.NewForcePolicy(false), + repo: &types.Repository{Name: "gcr.io/v2-namespace/hello-world", Tag: "latest"}, + resource: MustParseGR(&apps_v1.Deployment{ + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "dep-1", + Namespace: "xxxx", + Annotations: map[string]string{}, + Labels: map[string]string{types.KeelPolicyLabel: "all"}, + }, + apps_v1.DeploymentSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ + "this": "that", + }, + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Image: "gcr.io/v2-namespace/hello-world", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + }), + }, + wantUpdatePlan: &UpdatePlan{ + // Resource: &k8s.GenericResource{}, + Resource: nil, + }, + wantShouldUpdateDeployment: false, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/types/types.go b/types/types.go index fc986c9d..10595d49 100644 --- a/types/types.go +++ b/types/types.go @@ -39,7 +39,7 @@ const KeelMatchPreReleaseAnnotation = "keel.sh/matchPreRelease" // KeelPollScheduleAnnotation - optional variable to setup custom schedule for polling, defaults to @every 10m const KeelPollScheduleAnnotation = "keel.sh/pollSchedule" -// KeelInitContainerAnnotation - label or annotation to update init containers, defaults to false for backward compatibility +// KeelInitContainerAnnotation - label or annotation to track init containers, defaults to false for backward compatibility const KeelInitContainerAnnotation = "keel.sh/initContainers" // KeelPollDefaultSchedule - defaul polling schedule