diff --git a/internal/directives/kustomize_image_setter.go b/internal/directives/kustomize_image_setter.go index e16515768..b14b01b31 100644 --- a/internal/directives/kustomize_image_setter.go +++ b/internal/directives/kustomize_image_setter.go @@ -91,8 +91,15 @@ func (k *kustomizeImageSetter) runPromotionStep( fmt.Errorf("could not discover kustomization file: %w", err) } - // Discover image origins and collect target images. - targetImages, err := k.buildTargetImages(ctx, stepCtx, cfg.Images) + var targetImages map[string]kustypes.Image + switch { + case len(cfg.Images) > 0: + // Discover image origins and collect target images. + targetImages, err = k.buildTargetImagesFromConfig(ctx, stepCtx, cfg.Images) + default: + // Attempt to automatically set target images based on the Freight references. + targetImages, err = k.buildTargetImagesAutomatically(stepCtx) + } if err != nil { return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}, err } @@ -111,7 +118,7 @@ func (k *kustomizeImageSetter) runPromotionStep( return result, nil } -func (k *kustomizeImageSetter) buildTargetImages( +func (k *kustomizeImageSetter) buildTargetImagesFromConfig( ctx context.Context, stepCtx *PromotionStepContext, images []KustomizeSetImageConfigImage, @@ -159,6 +166,33 @@ func (k *kustomizeImageSetter) buildTargetImages( return targetImages, nil } +func (k *kustomizeImageSetter) buildTargetImagesAutomatically( + stepCtx *PromotionStepContext, +) (map[string]kustypes.Image, error) { + var images = make(map[string]kustypes.Image) + for _, freightRef := range stepCtx.Freight.References() { + if len(freightRef.Images) == 0 { + continue + } + + for _, img := range freightRef.Images { + if _, ok := images[img.RepoURL]; ok { + return nil, fmt.Errorf( + "manual configuration required due to ambiguous result: found multiple images for repository %q", + img.RepoURL, + ) + } + + images[img.RepoURL] = kustypes.Image{ + Name: img.RepoURL, + NewTag: img.Tag, + Digest: img.Digest, + } + } + } + return images, nil +} + func (k *kustomizeImageSetter) generateCommitMessage(path string, images map[string]kustypes.Image) string { if len(images) == 0 { return "" diff --git a/internal/directives/kustomize_image_setter_test.go b/internal/directives/kustomize_image_setter_test.go index 3f0b7fd56..584fdf431 100644 --- a/internal/directives/kustomize_image_setter_test.go +++ b/internal/directives/kustomize_image_setter_test.go @@ -40,22 +40,6 @@ func Test_kustomizeImageSetter_validate(t *testing.T) { "path: String length must be greater than or equal to 1", }, }, - { - name: "images is null", - config: Config{}, - expectedProblems: []string{ - "(root): images is required", - }, - }, - { - name: "images is empty", - config: Config{ - "images": []Config{}, - }, - expectedProblems: []string{ - "images: Array must have at least 1 items", - }, - }, { name: "image not specified", config: Config{ @@ -259,6 +243,51 @@ kind: Kustomization assert.Contains(t, string(b), "newTag: 1.21.0") }, }, + { + name: "automatically sets image", + setupFiles: func(t *testing.T) string { + tempDir := t.TempDir() + kustomizationContent := `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +` + err := os.WriteFile(filepath.Join(tempDir, "kustomization.yaml"), []byte(kustomizationContent), 0o600) + require.NoError(t, err) + return tempDir + }, + cfg: KustomizeSetImageConfig{ + Path: ".", + Images: nil, // Automatically set all images + }, + setupStepCtx: func(_ *testing.T, workDir string) *PromotionStepContext { + return &PromotionStepContext{ + WorkDir: workDir, + Freight: kargoapi.FreightCollection{ + Freight: map[string]kargoapi.FreightReference{ + "Warehouse/warehouse1": { + Images: []kargoapi.Image{{RepoURL: "nginx", Digest: "sha256:123"}}, + }, + "Warehouse/warehouse2": { + Images: []kargoapi.Image{{RepoURL: "redis", Tag: "6.2.5"}}, + }, + }, + }, + } + }, + assertions: func(t *testing.T, workDir string, result PromotionStepResult, err error) { + require.NoError(t, err) + assert.Equal(t, PromotionStepResult{ + Status: kargoapi.PromotionPhaseSucceeded, + Output: map[string]any{ + "commitMessage": "Updated . to use new images\n\n- nginx@sha256:123\n- redis:6.2.5", + }, + }, result) + + b, err := os.ReadFile(filepath.Join(workDir, "kustomization.yaml")) + require.NoError(t, err) + assert.Contains(t, string(b), "newTag: 6.2.5") + assert.Contains(t, string(b), "digest: sha256:123") + }, + }, { name: "Kustomization file not found", setupFiles: func(t *testing.T) string { @@ -555,7 +584,77 @@ func Test_kustomizeImageSetter_buildTargetImages(t *testing.T) { }, } - result, err := runner.buildTargetImages(context.Background(), stepCtx, tt.images) + result, err := runner.buildTargetImagesFromConfig(context.Background(), stepCtx, tt.images) + tt.assertions(t, result, err) + }) + } +} + +func Test_kustomizeImageSetter_buildTargetImagesAutomatically(t *testing.T) { + tests := []struct { + name string + freightReferences map[string]kargoapi.FreightReference + assertions func(*testing.T, map[string]kustypes.Image, error) + }{ + { + name: "successfully builds target images", + freightReferences: map[string]kargoapi.FreightReference{ + "Warehouse/warehouse1": { + Images: []kargoapi.Image{ + {RepoURL: "nginx", Tag: "1.21.0", Digest: "sha256:abcdef1234567890"}, + }, + }, + "Warehouse/warehouse2": { + Images: []kargoapi.Image{ + {RepoURL: "redis", Tag: "6.2.5"}, + }, + }, + "Warehouse/warehouse3": { + Images: []kargoapi.Image{ + {RepoURL: "postgres", Digest: "sha256:abcdef1234567890"}, + }, + }, + }, + assertions: func(t *testing.T, result map[string]kustypes.Image, err error) { + require.NoError(t, err) + assert.Equal(t, map[string]kustypes.Image{ + "nginx": {Name: "nginx", NewTag: "1.21.0", Digest: "sha256:abcdef1234567890"}, + "redis": {Name: "redis", NewTag: "6.2.5"}, + "postgres": {Name: "postgres", Digest: "sha256:abcdef1234567890"}, + }, result) + }, + }, + { + name: "error on ambiguous image match", + freightReferences: map[string]kargoapi.FreightReference{ + "Warehouse/warehouse1": { + Images: []kargoapi.Image{ + {RepoURL: "nginx", Tag: "1.21.0", Digest: "sha256:abcdef1234567890"}, + }, + }, + "Warehouse/warehouse2": { + Images: []kargoapi.Image{ + {RepoURL: "nginx", Tag: "1.21.0", Digest: "sha256:abcdef1234567890"}, + }, + }, + }, + assertions: func(t *testing.T, _ map[string]kustypes.Image, err error) { + require.ErrorContains(t, err, "manual configuration required due to ambiguous result") + }, + }, + } + + runner := &kustomizeImageSetter{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stepCtx := &PromotionStepContext{ + Freight: kargoapi.FreightCollection{ + Freight: tt.freightReferences, + }, + } + + result, err := runner.buildTargetImagesAutomatically(stepCtx) tt.assertions(t, result, err) }) }