diff --git a/pkg/commands/options/publish.go b/pkg/commands/options/publish.go index 928f3aabc4..c010ede494 100644 --- a/pkg/commands/options/publish.go +++ b/pkg/commands/options/publish.go @@ -26,6 +26,8 @@ import ( // PublishOptions encapsulates options when publishing. type PublishOptions struct { Tags []string + // TagOnly resolves images into tag-only references. + TagOnly bool // Push publishes images to a registry. Push bool @@ -49,6 +51,8 @@ func AddPublishArg(cmd *cobra.Command, po *PublishOptions) { cmd.Flags().StringSliceVarP(&po.Tags, "tags", "t", []string{"latest"}, "Which tags to use for the produced image instead of the default 'latest' tag "+ "(may not work properly with --base-import-paths or --bare).") + cmd.Flags().BoolVar(&po.TagOnly, "tag-only", false, + "Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.") cmd.Flags().BoolVar(&po.Push, "push", true, "Push images to KO_DOCKER_REPO") diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 7e9acc1f8e..dd5a9bd493 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -170,6 +170,7 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) { publish.WithAuthFromKeychain(authn.DefaultKeychain), publish.WithNamer(namer), publish.WithTags(po.Tags), + publish.WithTagOnly(po.TagOnly), publish.Insecure(po.InsecureRegistry)) if err != nil { return nil, err diff --git a/pkg/publish/default.go b/pkg/publish/default.go index 44f973019d..8b73c867fa 100644 --- a/pkg/publish/default.go +++ b/pkg/publish/default.go @@ -16,6 +16,7 @@ package publish import ( "context" + "errors" "fmt" "log" "net/http" @@ -38,6 +39,7 @@ type defalt struct { auth authn.Authenticator namer Namer tags []string + tagOnly bool insecure bool } @@ -51,6 +53,7 @@ type defaultOpener struct { auth authn.Authenticator namer Namer tags []string + tagOnly bool insecure bool } @@ -69,6 +72,15 @@ func identity(base, in string) string { return path.Join(base, 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, @@ -76,6 +88,7 @@ func (do *defaultOpener) Open() (Interface, error) { auth: do.auth, namer: do.namer, tags: do.tags, + tagOnly: do.tagOnly, insecure: do.insecure, }, nil } @@ -156,6 +169,15 @@ func (d *defalt) Publish(ctx context.Context, br build.Result, s string) (name.R } } + if d.tagOnly { + // We have already validated that there is a single tag (not latest). + tag, err := name.NewTag(fmt.Sprintf("%s:%s", d.namer(d.base, s), d.tags[0])) + if err != nil { + return nil, err + } + return &tag, nil + } + h, err := br.Digest() if err != nil { return nil, err diff --git a/pkg/publish/default_test.go b/pkg/publish/default_test.go index 597165fcd4..37dc7ec042 100644 --- a/pkg/publish/default_test.go +++ b/pkg/publish/default_test.go @@ -224,4 +224,21 @@ func TestDefaultWithReleaseTag(t *testing.T) { if _, ok := createdTags["v1.2.3"]; !ok { t.Errorf("Tag v1.2.3 was not created.") } + + def, err = NewDefault(repoName, WithTags([]string{releaseTag}), WithTagOnly(true)) + if err != nil { + t.Errorf("NewDefault() = %v", err) + } + if d, err := def.Publish(context.Background(), img, build.StrictScheme+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 4f8cceeb34..8c95f39789 100644 --- a/pkg/publish/options.go +++ b/pkg/publish/options.go @@ -94,6 +94,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