diff --git a/docs/kustomization.yaml b/docs/kustomization.yaml index 30821c99b..15468dd29 100644 --- a/docs/kustomization.yaml +++ b/docs/kustomization.yaml @@ -247,8 +247,12 @@ vars: # image: nginx:1.7.9 #``` # one can change the tag of myimage to v1 and the tag of nginx to 1.8.0 with the following: +# +# It also supports digests. If digest is present newTag is ignored. imageTags: - name: mycontainerregistry/myimage newTag: v1 - name: nginx newTag: 1.8.0 + - name: alpine + digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 diff --git a/pkg/commands/setimagetag.go b/pkg/commands/setimagetag.go index 5cc6e4f35..f6d82c3b3 100644 --- a/pkg/commands/setimagetag.go +++ b/pkg/commands/setimagetag.go @@ -20,6 +20,7 @@ import ( "errors" "regexp" "sort" + "strings" "github.com/spf13/cobra" @@ -29,7 +30,7 @@ import ( ) type setImageTagOptions struct { - imageTagMap map[string]string + imageTagMap map[string]types.ImageTag } var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$") @@ -40,10 +41,10 @@ func newCmdSetImageTag(fsys fs.FileSystem) *cobra.Command { cmd := &cobra.Command{ Use: "imagetag", - Short: "Sets images and their new tags in the kustomization file", + Short: "Sets images and their new tags or digests in the kustomization file", Example: ` The command - set imagetag nginx:1.8.0 my-app:latest + set imagetag nginx:1.8.0 my-app:latest alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 will add imageTags: @@ -51,9 +52,11 @@ imageTags: newTag: 1.8.0 - name: my-app newTag: latest +- name: alpine + digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 to the kustomization file if it doesn't exist, -and overwrite the previous newTag if the image name exists. +and overwrite the previous ones if the image tag exists. `, RunE: func(cmd *cobra.Command, args []string) error { err := o.Validate(args) @@ -69,15 +72,28 @@ and overwrite the previous newTag if the image name exists. // Validate validates setImageTag command. func (o *setImageTagOptions) Validate(args []string) error { if len(args) == 0 { - return errors.New("no image and newTag specified") + return errors.New("no image specified") } - o.imageTagMap = make(map[string]string) + + o.imageTagMap = make(map[string]types.ImageTag) + for _, arg := range args { - imagetag := pattern.FindStringSubmatch(arg) - if len(imagetag) != 3 { - return errors.New("invalid format of imagetag, must specify it as :") + if s := strings.Split(arg, "@"); len(s) > 1 { + o.imageTagMap[s[0]] = types.ImageTag{ + Name: s[0], + Digest: s[1], + } + continue + } + + s := pattern.FindStringSubmatch(arg) + if len(s) != 3 { + return errors.New("invalid format of imagetag, must specify it as : or @") + } + o.imageTagMap[s[1]] = types.ImageTag{ + Name: s[1], + NewTag: s[2], } - o.imageTagMap[imagetag[1]] = imagetag[2] } return nil } @@ -92,16 +108,18 @@ func (o *setImageTagOptions) RunSetImageTags(fsys fs.FileSystem) error { if err != nil { return err } - imageTagMap := map[string]string{} + for _, it := range m.ImageTags { - imageTagMap[it.Name] = it.NewTag - } - for key, value := range o.imageTagMap { - imageTagMap[key] = value + if _, ok := o.imageTagMap[it.Name]; ok { + continue + } + + o.imageTagMap[it.Name] = it } + var imageTags []types.ImageTag - for key, value := range imageTagMap { - imageTags = append(imageTags, types.ImageTag{Name: key, NewTag: value}) + for _, v := range o.imageTagMap { + imageTags = append(imageTags, v) } sort.Slice(imageTags, func(i, j int) bool { return imageTags[i].Name < imageTags[j].Name diff --git a/pkg/commands/setimagetag_test.go b/pkg/commands/setimagetag_test.go index af337316a..9d23a1117 100644 --- a/pkg/commands/setimagetag_test.go +++ b/pkg/commands/setimagetag_test.go @@ -29,7 +29,8 @@ func TestSetImageTagsHappyPath(t *testing.T) { fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent)) cmd := newCmdSetImageTag(fakeFS) - args := []string{"image1:tag1", "image2:tag2", "localhost:5000/operator:1.0.0"} + args := []string{"image1:tag1", "image2:tag2", "localhost:5000/operator:1.0.0", + "foo.bar.baz:5000/one/two@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"} err := cmd.RunE(cmd, args) if err != nil { t.Errorf("unexpected cmd error: %v", err) @@ -40,6 +41,8 @@ func TestSetImageTagsHappyPath(t *testing.T) { } expected := []byte(` imageTags: +- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 + name: foo.bar.baz:5000/one/two - name: image1 newTag: tag1 - name: image2 @@ -93,7 +96,7 @@ func TestSetImageTagsNoArgs(t *testing.T) { if err == nil { t.Errorf("expected error: %v", err) } - if err.Error() != "no image and newTag specified" { + if err.Error() != "no image specified" { t.Errorf("incorrect error: %v", err.Error()) } } diff --git a/pkg/transformers/imagetag.go b/pkg/transformers/imagetag.go index 9a22070ab..a5c0895fc 100644 --- a/pkg/transformers/imagetag.go +++ b/pkg/transformers/imagetag.go @@ -18,7 +18,6 @@ package transformers import ( "regexp" - "strings" "github.com/kubernetes-sigs/kustomize/pkg/resmap" "github.com/kubernetes-sigs/kustomize/pkg/types" @@ -83,7 +82,12 @@ func (pt *imageTagTransformer) updateContainers(obj map[string]interface{}, path } for _, imagetag := range pt.imageTags { if isImageMatched(image.(string), imagetag.Name) { - container["image"] = strings.Join([]string{imagetag.Name, imagetag.NewTag}, ":") + container["image"] = imagetag.Name + ":" + imagetag.NewTag + + if imagetag.Digest != "" { + container["image"] = imagetag.Name + "@" + imagetag.Digest + } + break } } diff --git a/pkg/transformers/imagetag_test.go b/pkg/transformers/imagetag_test.go index 0c4876088..45c027e02 100644 --- a/pkg/transformers/imagetag_test.go +++ b/pkg/transformers/imagetag_test.go @@ -50,6 +50,10 @@ func TestImageTagTransformer(t *testing.T) { "name": "nginx", "image": "nginx:1.7.9", }, + map[string]interface{}{ + "name": "replaced-with-digest", + "image": "foobar:1", + }, }, }, }, @@ -114,6 +118,10 @@ func TestImageTagTransformer(t *testing.T) { "name": "nginx", "image": "nginx:v2", }, + map[string]interface{}{ + "name": "replaced-with-digest", + "image": "foobar@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3", + }, }, }, }, @@ -160,6 +168,7 @@ func TestImageTagTransformer(t *testing.T) { {Name: "nginx", NewTag: "v2"}, {Name: "my-nginx", NewTag: "previous"}, {Name: "myprivaterepohostname:1234/my/image", NewTag: "v1.0.1"}, + {Name: "foobar", Digest: "sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"}, }) if err != nil { t.Fatalf("unexpected error: %v", err) diff --git a/pkg/types/kustomization.go b/pkg/types/kustomization.go index f9163035e..d693275fd 100644 --- a/pkg/types/kustomization.go +++ b/pkg/types/kustomization.go @@ -176,4 +176,8 @@ type ImageTag struct { // NewTag is the value to use in replacing the original tag. NewTag string `json:"newTag,omitempty" yaml:"newTag,omitempty"` + + // Digest is the value used to replace the original image tag. + // If digest is present NewTag value is ignored. + Digest string `json:"digest,omitempty" yaml:"digest,omitempty"` }