diff --git a/pkg/publish/default.go b/pkg/publish/default.go index fcea42179d..3eedb22858 100644 --- a/pkg/publish/default.go +++ b/pkg/publish/default.go @@ -97,22 +97,28 @@ func (d *defalt) Publish(img v1.Image, s string) (name.Reference, error) { // https://github.com/google/go-containerregistry/issues/212 s = strings.ToLower(s) - for _, tagName := range d.tags { + ro := []remote.Option{remote.WithAuth(d.auth), remote.WithTransport(d.t)} + no := []name.Option{} + if d.insecure { + no = append(no, name.Insecure) + } - var os []name.Option - if d.insecure { - os = []name.Option{name.Insecure} - } - tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", d.base, d.namer(s), tagName), os...) + for i, tagName := range d.tags { + tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", d.base, d.namer(s), tagName), no...) if err != nil { return nil, err } - log.Printf("Publishing %v", tag) - // TODO: This is slow because we have to load the image multiple times. - // Figure out some way to publish the manifest with another tag. - if err := remote.Write(tag, img, remote.WithAuth(d.auth), remote.WithTransport(d.t)); err != nil { - return nil, err + if i == 0 { + log.Printf("Publishing %v", tag) + if err := remote.Write(tag, img, ro...); err != nil { + return nil, err + } + } else { + log.Printf("Tagging %v", tag) + if err := remote.Tag(tag, img, ro...); err != nil { + return nil, err + } } } @@ -120,7 +126,13 @@ func (d *defalt) Publish(img v1.Image, s string) (name.Reference, error) { if err != nil { return nil, err } - dig, err := name.NewDigest(fmt.Sprintf("%s/%s@%s", d.base, d.namer(s), h)) + ref := fmt.Sprintf("%s/%s@%s", d.base, d.namer(s), h) + if len(d.tags) == 1 && d.tags[0] != defaultTags[0] { + // If a single tag is explicitly set (not latest), then this + // is probably a release, so include the tag in the reference. + ref = fmt.Sprintf("%s/%s:%s@%s", d.base, d.namer(s), d.tags[0], h) + } + dig, err := name.NewDigest(ref) if err != nil { return nil, err } diff --git a/pkg/publish/default_test.go b/pkg/publish/default_test.go index 9f55803b2d..aa98c59833 100644 --- a/pkg/publish/default_test.go +++ b/pkg/publish/default_test.go @@ -221,3 +221,74 @@ func TestDefaultWithTags(t *testing.T) { t.Errorf("Tag v1.2.3 was not created.") } } + +func TestDefaultWithReleaseTag(t *testing.T) { + img, err := random.Image(1024, 1) + if err != nil { + t.Fatalf("random.Image() = %v", err) + } + base := "blah" + releaseTag := "v1.2.3" + importpath := "github.com/Google/go-containerregistry/cmd/crane" + expectedRepo := fmt.Sprintf("%s/%s", base, strings.ToLower(importpath)) + headPathPrefix := fmt.Sprintf("/v2/%s/blobs/", expectedRepo) + initiatePath := fmt.Sprintf("/v2/%s/blobs/uploads/", expectedRepo) + manifestPath := fmt.Sprintf("/v2/%s/manifests/", expectedRepo) + + createdTags := make(map[string]struct{}) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, headPathPrefix) && r.URL.Path != initiatePath { + http.Error(w, "NotFound", http.StatusNotFound) + return + } + switch { + case r.URL.Path == "/v2/": + w.WriteHeader(http.StatusOK) + case r.URL.Path == initiatePath: + if r.Method != http.MethodPost { + t.Errorf("Method; got %v, want %v", r.Method, http.MethodPost) + } + http.Error(w, "Mounted", http.StatusCreated) + case strings.HasPrefix(r.URL.Path, manifestPath): + if r.Method != http.MethodPut { + t.Errorf("Method; got %v, want %v", r.Method, http.MethodPut) + } + + createdTags[strings.TrimPrefix(r.URL.Path, manifestPath)] = struct{}{} + + http.Error(w, "Created", http.StatusCreated) + default: + t.Fatalf("Unexpected path: %v", r.URL.Path) + } + })) + defer server.Close() + u, err := url.Parse(server.URL) + if err != nil { + t.Fatalf("url.Parse(%v) = %v", server.URL, err) + } + tag, err := name.NewTag(fmt.Sprintf("%s/%s:notLatest", u.Host, expectedRepo)) + if err != nil { + t.Fatalf("NewTag() = %v", err) + } + + repoName := fmt.Sprintf("%s/%s", u.Host, base) + + def, err := NewDefault(repoName, WithTags([]string{releaseTag})) + 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) + } + + if _, ok := createdTags["v1.2.3"]; !ok { + t.Errorf("Tag v1.2.3 was not created.") + } +}