diff --git a/pkg/commands/options/tags.go b/pkg/commands/options/tags.go index 22c1d98081..e557878487 100644 --- a/pkg/commands/options/tags.go +++ b/pkg/commands/options/tags.go @@ -20,10 +20,13 @@ import ( // TagsOptions holds the list of tags to tag the built image type TagsOptions struct { - Tags []string + Tags []string + TagOnly bool } func AddTagsArg(cmd *cobra.Command, ta *TagsOptions) { cmd.Flags().StringSliceVarP(&ta.Tags, "tags", "t", []string{"latest"}, "Which tags to use for the produced image instead of the default 'latest' tag.") + cmd.Flags().BoolVar(&ta.TagOnly, "tag-only", false, + "Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.") } diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 773a6d3b86..f3c98419ad 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -134,6 +134,7 @@ func makePublisher(no *options.NameOptions, lo *options.LocalOptions, ta *option publish.WithAuthFromKeychain(authn.DefaultKeychain), publish.WithNamer(namer), publish.WithTags(ta.Tags), + publish.WithTagOnly(ta.TagOnly), publish.Insecure(lo.InsecureRegistry)) }() if err != nil { diff --git a/pkg/publish/default.go b/pkg/publish/default.go index 8d5cf33c83..749aee1b96 100644 --- a/pkg/publish/default.go +++ b/pkg/publish/default.go @@ -15,6 +15,7 @@ package publish import ( + "errors" "fmt" "log" "net/http" @@ -33,6 +34,7 @@ type defalt struct { auth authn.Authenticator namer Namer tags []string + tagOnly bool insecure bool } @@ -45,6 +47,7 @@ type defaultOpener struct { auth authn.Authenticator namer Namer tags []string + tagOnly bool insecure bool } @@ -63,12 +66,22 @@ func identity(in string) string { return in } var defaultTags = []string{"latest"} func (do *defaultOpener) Open() (Interface, error) { + if do.tagOnly { + if len(do.tags) != 1 { + return nil, errors.New("must specify exactly one tag to resolve images into tag-only references") + } + if do.tags[0] == defaultTags[0] { + return nil, errors.New("latest tag cannot be used in tag-only references") + } + } + return &defalt{ base: do.base, t: do.t, auth: do.auth, namer: do.namer, tags: do.tags, + tagOnly: do.tagOnly, insecure: do.insecure, }, nil } @@ -123,6 +136,15 @@ func (d *defalt) Publish(img v1.Image, s string) (name.Reference, error) { } } + if d.tagOnly { + // We already validated that there is a single tag tag (not latest). + tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", d.base, d.namer(s), d.tags[0])) + if err != nil { + return nil, err + } + return &tag, nil + } + h, err := img.Digest() if err != nil { return nil, err diff --git a/pkg/publish/default_test.go b/pkg/publish/default_test.go index aa98c59833..9f2deb3307 100644 --- a/pkg/publish/default_test.go +++ b/pkg/publish/default_test.go @@ -291,4 +291,25 @@ func TestDefaultWithReleaseTag(t *testing.T) { if _, ok := createdTags["v1.2.3"]; !ok { t.Errorf("Tag v1.2.3 was not created.") } + + for tag := range createdTags { + delete(createdTags, tag) + } + + def, err = NewDefault(repoName, WithTags([]string{releaseTag}), WithTagOnly(true)) + if err != nil { + t.Errorf("NewDefault() = %v", err) + } + if d, err := def.Publish(img, importpath); err != nil { + t.Errorf("Publish() = %v", err) + } else if !strings.HasPrefix(d.String(), repoName) { + t.Errorf("Publish() = %v, wanted prefix %v", d, tag.Repository) + } else if !strings.HasSuffix(d.Context().String(), strings.ToLower(importpath)) { + t.Errorf("Publish() = %v, wanted suffix %v", d.Context(), md5Hash(importpath)) + } else if !strings.Contains(d.String(), releaseTag) { + t.Errorf("Publish() = %v, wanted tag included: %v", d.String(), releaseTag) + } else if strings.Contains(d.String(), "@sha256:") { + t.Errorf("Publish() = %v, wanted no digest", d.String()) + } + } diff --git a/pkg/publish/options.go b/pkg/publish/options.go index 49ee6fe1df..7df9a9acac 100644 --- a/pkg/publish/options.go +++ b/pkg/publish/options.go @@ -86,6 +86,14 @@ func WithTags(tags []string) Option { } } +// WithTagOnly is a functional option for resolving images into tag-only references +func WithTagOnly(tagOnly bool) Option { + return func(i *defaultOpener) error { + i.tagOnly = tagOnly + return nil + } +} + func Insecure(b bool) Option { return func(i *defaultOpener) error { i.insecure = b