Implement support for digests in imageTags

This commit adds a new field to imageTags transformer
so that digests can be used instead of image tags if needed.

Closes https://github.com/kubernetes-sigs/kustomize/issues/326
This commit is contained in:
Alexandr Burdiyan
2018-09-07 17:45:25 +02:00
parent f7def79764
commit 5401bd367b
6 changed files with 60 additions and 17 deletions

View File

@@ -247,8 +247,12 @@ vars:
# image: nginx:1.7.9 # 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: # 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: imageTags:
- name: mycontainerregistry/myimage - name: mycontainerregistry/myimage
newTag: v1 newTag: v1
- name: nginx - name: nginx
newTag: 1.8.0 newTag: 1.8.0
- name: alpine
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3

View File

@@ -20,6 +20,7 @@ import (
"errors" "errors"
"regexp" "regexp"
"sort" "sort"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -29,7 +30,7 @@ import (
) )
type setImageTagOptions struct { type setImageTagOptions struct {
imageTagMap map[string]string imageTagMap map[string]types.ImageTag
} }
var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$") var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$")
@@ -40,10 +41,10 @@ func newCmdSetImageTag(fsys fs.FileSystem) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "imagetag", 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: ` Example: `
The command 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 will add
imageTags: imageTags:
@@ -51,9 +52,11 @@ imageTags:
newTag: 1.8.0 newTag: 1.8.0
- name: my-app - name: my-app
newTag: latest newTag: latest
- name: alpine
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
to the kustomization file if it doesn't exist, 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 { RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args) err := o.Validate(args)
@@ -71,13 +74,27 @@ func (o *setImageTagOptions) Validate(args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("no image and newTag specified") return errors.New("no image and newTag specified")
} }
o.imageTagMap = make(map[string]string)
o.imageTagMap = make(map[string]types.ImageTag)
for _, arg := range args { for _, arg := range args {
if strings.Contains(arg, "@") {
img := strings.Split(arg, "@")
o.imageTagMap[img[0]] = types.ImageTag{
Name: img[0],
Digest: img[1],
}
continue
}
imagetag := pattern.FindStringSubmatch(arg) imagetag := pattern.FindStringSubmatch(arg)
if len(imagetag) != 3 { if len(imagetag) != 3 {
return errors.New("invalid format of imagetag, must specify it as <image>:<newtag>") return errors.New("invalid format of imagetag, must specify it as <image>:<newtag> or <image>@<digest>")
}
o.imageTagMap[imagetag[1]] = types.ImageTag{
Name: imagetag[1],
NewTag: imagetag[2],
} }
o.imageTagMap[imagetag[1]] = imagetag[2]
} }
return nil return nil
} }
@@ -92,16 +109,18 @@ func (o *setImageTagOptions) RunSetImageTags(fsys fs.FileSystem) error {
if err != nil { if err != nil {
return err return err
} }
imageTagMap := map[string]string{}
for _, it := range m.ImageTags { for _, it := range m.ImageTags {
imageTagMap[it.Name] = it.NewTag if _, ok := o.imageTagMap[it.Name]; ok {
} continue
for key, value := range o.imageTagMap { }
imageTagMap[key] = value
o.imageTagMap[it.Name] = it
} }
var imageTags []types.ImageTag var imageTags []types.ImageTag
for key, value := range imageTagMap { for _, v := range o.imageTagMap {
imageTags = append(imageTags, types.ImageTag{Name: key, NewTag: value}) imageTags = append(imageTags, v)
} }
sort.Slice(imageTags, func(i, j int) bool { sort.Slice(imageTags, func(i, j int) bool {
return imageTags[i].Name < imageTags[j].Name return imageTags[i].Name < imageTags[j].Name

View File

@@ -29,7 +29,8 @@ func TestSetImageTagsHappyPath(t *testing.T) {
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent)) fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetImageTag(fakeFS) 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) err := cmd.RunE(cmd, args)
if err != nil { if err != nil {
t.Errorf("unexpected cmd error: %v", err) t.Errorf("unexpected cmd error: %v", err)
@@ -40,6 +41,8 @@ func TestSetImageTagsHappyPath(t *testing.T) {
} }
expected := []byte(` expected := []byte(`
imageTags: imageTags:
- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
name: foo.bar.baz:5000/one/two
- name: image1 - name: image1
newTag: tag1 newTag: tag1
- name: image2 - name: image2

View File

@@ -18,7 +18,6 @@ package transformers
import ( import (
"regexp" "regexp"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/resmap" "github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types" "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 { for _, imagetag := range pt.imageTags {
if isImageMatched(image.(string), imagetag.Name) { 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 break
} }
} }

View File

@@ -50,6 +50,10 @@ func TestImageTagTransformer(t *testing.T) {
"name": "nginx", "name": "nginx",
"image": "nginx:1.7.9", "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", "name": "nginx",
"image": "nginx:v2", "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: "nginx", NewTag: "v2"},
{Name: "my-nginx", NewTag: "previous"}, {Name: "my-nginx", NewTag: "previous"},
{Name: "myprivaterepohostname:1234/my/image", NewTag: "v1.0.1"}, {Name: "myprivaterepohostname:1234/my/image", NewTag: "v1.0.1"},
{Name: "foobar", Digest: "sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3"},
}) })
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)

View File

@@ -176,4 +176,8 @@ type ImageTag struct {
// NewTag is the value to use in replacing the original tag. // NewTag is the value to use in replacing the original tag.
NewTag string `json:"newTag,omitempty" yaml:"newTag,omitempty"` 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"`
} }