From 2751e64f98a5cd32f9b544a40df108aad22bb30a Mon Sep 17 00:00:00 2001 From: Sophie Wigmore Date: Thu, 8 Apr 2021 19:59:59 +0000 Subject: [PATCH] Use buildpackage metadata for image ID in builder/buildpack updates Signed-off-by: Frankie Gallina-Jones --- cargo/jam/commands/update_builder.go | 7 +- cargo/jam/commands/update_buildpack.go | 7 +- cargo/jam/internal/image.go | 35 ++++++ cargo/jam/internal/image_test.go | 29 +++++ cargo/jam/update_builder_test.go | 160 +++++++++++++++++++++--- cargo/jam/update_buildpack_test.go | 162 ++++++++++++++++++++++--- 6 files changed, 364 insertions(+), 36 deletions(-) diff --git a/cargo/jam/commands/update_builder.go b/cargo/jam/commands/update_builder.go index 02868b88..cc428046 100644 --- a/cargo/jam/commands/update_builder.go +++ b/cargo/jam/commands/update_builder.go @@ -51,9 +51,14 @@ func updateBuilderRun(flags updateBuilderFlags) error { builder.Buildpacks[i].Version = image.Version builder.Buildpacks[i].URI = fmt.Sprintf("%s:%s", image.Name, image.Version) + buildpackageID, err := internal.GetBuildpackageID(buildpack.URI) + if err != nil { + return fmt.Errorf("failed to get buildpackage ID for %s: %w", buildpack.URI, err) + } + for j, order := range builder.Order { for k, group := range order.Group { - if group.ID == image.Path { + if group.ID == buildpackageID { if builder.Order[j].Group[k].Version != "" { builder.Order[j].Group[k].Version = image.Version } diff --git a/cargo/jam/commands/update_buildpack.go b/cargo/jam/commands/update_buildpack.go index 51742ba8..4db2f3fe 100644 --- a/cargo/jam/commands/update_buildpack.go +++ b/cargo/jam/commands/update_buildpack.go @@ -59,9 +59,14 @@ func updateBuildpackRun(flags updateBuildpackFlags) error { pkg.Dependencies[i].URI = fmt.Sprintf("%s:%s", image.Name, image.Version) + buildpackageID, err := internal.GetBuildpackageID(dependency.URI) + if err != nil { + return fmt.Errorf("failed to get buildpackage ID for %s: %w", dependency.URI, err) + } + for j, order := range bp.Order { for k, group := range order.Group { - if group.ID == image.Path { + if group.ID == buildpackageID { bp.Order[j].Group[k].Version = image.Version } } diff --git a/cargo/jam/internal/image.go b/cargo/jam/internal/image.go index ef699c52..c18dfd38 100644 --- a/cargo/jam/internal/image.go +++ b/cargo/jam/internal/image.go @@ -1,6 +1,7 @@ package internal import ( + "encoding/json" "fmt" "sort" "strings" @@ -116,3 +117,37 @@ func FindLatestBuildImage(runURI, buildURI string) (Image, error) { Version: fmt.Sprintf("%s%s", versions[len(versions)-1].String(), suffix), }, nil } + +func GetBuildpackageID(uri string) (string, error) { + ref, err := name.ParseReference(uri) + if err != nil { + return "", err + } + + image, err := remote.Image(ref) + if err != nil { + return "", err + } + + cfg, err := image.ConfigFile() + if err != nil { + return "", err + } + + type BuildpackageMetadata struct { + BuildpackageID string `json:"id"` + } + var metadataString string + var ok bool + if metadataString, ok = cfg.Config.Labels["io.buildpacks.buildpackage.metadata"]; !ok { + return "", fmt.Errorf("could not get buildpackage id: image %s has no label 'io.buildpacks.buildpackage.metadata'", uri) + } + + metadata := BuildpackageMetadata{} + + err = json.Unmarshal([]byte(metadataString), &metadata) + if err != nil { + return "", fmt.Errorf("could not unmarshal buildpackage metadata") + } + return metadata.BuildpackageID, nil +} diff --git a/cargo/jam/internal/image_test.go b/cargo/jam/internal/image_test.go index 32c8333e..bedc2405 100644 --- a/cargo/jam/internal/image_test.go +++ b/cargo/jam/internal/image_test.go @@ -240,4 +240,33 @@ func testImage(t *testing.T, context spec.G, it spec.S) { }) }) }, spec.Sequential()) + + context("GetBuildpackageID", func() { + it("returns the buildpackage ID from the io.buildpacks.buildpackage.metadata image label", func() { + id, err := internal.GetBuildpackageID("gcr.io/paketo-buildpacks/go") + Expect(err).NotTo(HaveOccurred()) + Expect(id).To(Equal("paketo-buildpacks/go")) + }) + + context("failure cases", func() { + context("uri cannot be parsed", func() { + it("returns an error", func() { + _, err := internal.GetBuildpackageID("some garbage uri") + Expect(err).To(MatchError(ContainSubstring("could not parse reference"))) + }) + }) + context("image cannot be created from ref", func() { + it("returns an error", func() { + _, err := internal.GetBuildpackageID("gcr.io/does-not-exist/go:0.5.0") + Expect(err).To(MatchError(ContainSubstring("Project 'project:does-not-exist' not found or deleted"))) + }) + }) + context("image has no buildpackage metadata label", func() { + it("returns an error", func() { + _, err := internal.GetBuildpackageID("gcr.io/paketo-buildpacks/builder:base") + Expect(err).To(MatchError(ContainSubstring("could not get buildpackage id: image gcr.io/paketo-buildpacks/builder:base has no label 'io.buildpacks.buildpackage.metadata'"))) + }) + }) + }) + }) } diff --git a/cargo/jam/update_builder_test.go b/cargo/jam/update_builder_test.go index fa24d7f5..d89f07a4 100644 --- a/cargo/jam/update_builder_test.go +++ b/cargo/jam/update_builder_test.go @@ -11,6 +11,9 @@ import ( "strings" "testing" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "github.com/sclevine/spec" @@ -19,6 +22,30 @@ import ( . "github.com/paketo-buildpacks/packit/matchers" ) +func mustConfigName(t *testing.T, img v1.Image) v1.Hash { + h, err := img.ConfigName() + if err != nil { + t.Fatalf("ConfigName() = %v", err) + } + return h +} + +func mustRawManifest(t *testing.T, img remote.Taggable) []byte { + m, err := img.RawManifest() + if err != nil { + t.Fatalf("RawManifest() = %v", err) + } + return m +} + +func mustRawConfigFile(t *testing.T, img v1.Image) []byte { + c, err := img.RawConfigFile() + if err != nil { + t.Fatalf("RawConfigFile() = %v", err) + } + return c +} + func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { var ( withT = NewWithT(t) @@ -30,7 +57,24 @@ func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { ) it.Before(func() { + goRef, err := name.ParseReference("gcr.io/paketo-buildpacks/go") + Expect(err).ToNot(HaveOccurred()) + goImg, err := remote.Image(goRef) + Expect(err).ToNot(HaveOccurred()) + + nodeRef, err := name.ParseReference("paketobuildpacks/nodejs") + Expect(err).ToNot(HaveOccurred()) + nodeImg, err := remote.Image(nodeRef) + Expect(err).ToNot(HaveOccurred()) + + goManifestPath := "/v2/paketo-buildpacks/go/manifests/0.0.10" + goConfigPath := fmt.Sprintf("/v2/paketo-buildpacks/go/blobs/%s", mustConfigName(t, goImg)) + goManifestReqCount := 0 + nodeManifestPath := "/v2/paketobuildpacks/nodejs/manifests/0.20.22" + nodeConfigPath := fmt.Sprintf("/v2/paketobuildpacks/nodejs/blobs/%s", mustConfigName(t, nodeImg)) + nodeManifestReqCount := 0 server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodHead { http.Error(w, "NotFound", http.StatusNotFound) @@ -41,7 +85,7 @@ func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { case "/v2/": w.WriteHeader(http.StatusOK) - case "/v2/some-repository/some-buildpack-id/tags/list": + case "/v2/paketo-buildpacks/go/tags/list": w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{ "tags": [ @@ -52,7 +96,7 @@ func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { ] }`) - case "/v2/some-repository/other-buildpack-id/tags/list": + case "/v2/paketobuildpacks/nodejs/tags/list": w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{ "tags": [ @@ -86,6 +130,32 @@ func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { ] }`) + case goConfigPath: + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawConfigFile(t, goImg)) + + case goManifestPath: + goManifestReqCount++ + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawManifest(t, goImg)) + + case nodeConfigPath: + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawConfigFile(t, nodeImg)) + + case nodeManifestPath: + nodeManifestReqCount++ + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawManifest(t, nodeImg)) + case "/v2/some-repository/error-buildpack-id/tags/list": w.WriteHeader(http.StatusTeapot) @@ -95,12 +165,24 @@ func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { case "/v2/somerepository/error-build/tags/list": w.WriteHeader(http.StatusTeapot) + case "/v2/some-repository/nonexistent-labels-id/tags/list": + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, `{ + "tags": [ + "0.1.0", + "0.2.0", + "latest" + ] + }`) + + case "/v2/some-repository/nonexistent-labels-id/manifests/0.2.0": + w.WriteHeader(http.StatusBadRequest) + default: t.Fatal(fmt.Sprintf("unknown path: %s", req.URL.Path)) } })) - var err error builderDir, err = os.MkdirTemp("", "") Expect(err).NotTo(HaveOccurred()) @@ -108,11 +190,11 @@ func testUpdateBuilder(t *testing.T, context spec.G, it spec.S) { description = "Some description" [[buildpacks]] - uri = "docker://REGISTRY-URI/some-repository/some-buildpack-id:0.0.10" + uri = "docker://REGISTRY-URI/paketo-buildpacks/go:0.0.10" version = "0.0.10" [[buildpacks]] - uri = "docker://REGISTRY-URI/some-repository/other-buildpack-id:0.20.22" + uri = "docker://REGISTRY-URI/paketobuildpacks/nodejs:0.20.22" version = "0.20.22" [lifecycle] @@ -121,12 +203,12 @@ description = "Some description" [[order]] [[order.group]] - id = "some-repository/other-buildpack-id" + id = "paketo-buildpacks/nodejs" [[order]] [[order.group]] - id = "some-repository/some-buildpack-id" + id = "paketo-buildpacks/go" version = "0.0.10" optional = true @@ -163,11 +245,11 @@ description = "Some description" description = "Some description" [[buildpacks]] - uri = "docker://REGISTRY-URI/some-repository/some-buildpack-id:0.20.12" + uri = "docker://REGISTRY-URI/paketo-buildpacks/go:0.20.12" version = "0.20.12" [[buildpacks]] - uri = "docker://REGISTRY-URI/some-repository/other-buildpack-id:0.20.22" + uri = "docker://REGISTRY-URI/paketobuildpacks/nodejs:0.20.22" version = "0.20.22" [lifecycle] @@ -176,13 +258,13 @@ description = "Some description" [[order]] [[order.group]] - id = "some-repository/other-buildpack-id" + id = "paketo-buildpacks/nodejs" [[order]] [[order.group]] - id = "some-repository/some-buildpack-id" - version = "0.20.12" + id = "paketo-buildpacks/go" + version = "0.20.12" optional = true [stack] @@ -274,17 +356,63 @@ description = "Some description" }) }) + context("when the buildpackage ID cannot be retrieved", func() { + it.Before(func() { + err := os.WriteFile(filepath.Join(builderDir, "builder.toml"), bytes.ReplaceAll([]byte(` +description = "Some description" + +[[buildpacks]] + uri = "docker://REGISTRY-URI/some-repository/nonexistent-labels-id:0.2.0" + version = "0.2.0" + +[lifecycle] + version = "0.10.2" + +[[order]] + + [[order.group]] + id = "some-repository/nonexistent-labels-id" + version = "0.2.0" + +[stack] + id = "io.paketo.stacks.some-stack" + build-image = "REGISTRY-URI/somerepository/error-build:0.0.10-some-cnb" + run-image = "REGISTRY-URI/somerepository/run:some-cnb" + run-image-mirrors = ["REGISTRY-URI/some-repository/run:some-cnb"] + `), []byte(`REGISTRY-URI`), []byte(strings.TrimPrefix(server.URL, "http://"))), 0600) + Expect(err).NotTo(HaveOccurred()) + + }) + + it("prints an error and exits non-zero", func() { + command := exec.Command( + path, + "update-builder", + "--builder-file", filepath.Join(builderDir, "builder.toml"), + "--lifecycle-uri", fmt.Sprintf("%s/some-repository/lifecycle", strings.TrimPrefix(server.URL, "http://")), + ) + + buffer := gbytes.NewBuffer() + session, err := gexec.Start(command, buffer, buffer) + Expect(err).NotTo(HaveOccurred()) + + Eventually(session).Should(gexec.Exit(1), func() string { return string(buffer.Contents()) }) + Expect(string(buffer.Contents())).To(MatchRegexp(`failed to get buildpackage ID for \d+\.\d+\.\d+\.\d+\:\d+\/some\-repository\/nonexistent\-labels\-id\:0\.2\.0\:`)) + Expect(string(buffer.Contents())).To(ContainSubstring("unexpected status code 400 Bad Request")) + }) + }) + context("when the latest build image cannot be found", func() { it.Before(func() { err := os.WriteFile(filepath.Join(builderDir, "builder.toml"), bytes.ReplaceAll([]byte(` description = "Some description" [[buildpacks]] - uri = "docker://REGISTRY-URI/some-repository/some-buildpack-id:0.0.10" + uri = "docker://REGISTRY-URI/paketo-buildpacks/go:0.0.10" version = "0.0.10" [[buildpacks]] - uri = "docker://REGISTRY-URI/some-repository/other-buildpack-id:0.20.22" + uri = "docker://REGISTRY-URI/paketobuildpacks/nodejs:0.20.22" version = "0.20.22" [lifecycle] @@ -293,13 +421,13 @@ description = "Some description" [[order]] [[order.group]] - id = "some-repository/other-buildpack-id" + id = "paketo-buildpacks/nodejs" version = "0.20.22" [[order]] [[order.group]] - id = "some-repository/some-buildpack-id" + id = "paketo-buildpacks/go" version = "0.0.10" [stack] diff --git a/cargo/jam/update_buildpack_test.go b/cargo/jam/update_buildpack_test.go index d2e9fe9c..728c89d8 100644 --- a/cargo/jam/update_buildpack_test.go +++ b/cargo/jam/update_buildpack_test.go @@ -11,6 +11,8 @@ import ( "strings" "testing" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "github.com/sclevine/spec" @@ -30,6 +32,31 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { ) it.Before(func() { + goRef, err := name.ParseReference("gcr.io/paketo-buildpacks/go-dist") + Expect(err).ToNot(HaveOccurred()) + goImg, err := remote.Image(goRef) + Expect(err).ToNot(HaveOccurred()) + + nodeRef, err := name.ParseReference("paketobuildpacks/node-engine") + Expect(err).ToNot(HaveOccurred()) + nodeImg, err := remote.Image(nodeRef) + Expect(err).ToNot(HaveOccurred()) + + rubyRef, err := name.ParseReference("paketobuildpacks/mri") + Expect(err).ToNot(HaveOccurred()) + rubyImg, err := remote.Image(rubyRef) + Expect(err).ToNot(HaveOccurred()) + + goManifestPath := "/v2/paketo-buildpacks/go-dist/manifests/0.20.1" + goConfigPath := fmt.Sprintf("/v2/paketo-buildpacks/go-dist/blobs/%s", mustConfigName(t, goImg)) + goManifestReqCount := 0 + nodeManifestPath := "/v2/paketobuildpacks/node-engine/manifests/0.1.0" + nodeConfigPath := fmt.Sprintf("/v2/paketobuildpacks/node-engine/blobs/%s", mustConfigName(t, nodeImg)) + nodeManifestReqCount := 0 + rubyManifestPath := "/v2/paketobuildpacks/mri/manifests/0.2.0" + rubyConfigPath := fmt.Sprintf("/v2/paketobuildpacks/mri/blobs/%s", mustConfigName(t, rubyImg)) + rubyManifestReqCount := 0 + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.Method == http.MethodHead { http.Error(w, "NotFound", http.StatusNotFound) @@ -41,7 +68,7 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { case "/v2/": w.WriteHeader(http.StatusOK) - case "/v2/some-repository/some-buildpack-id/tags/list": + case "/v2/paketo-buildpacks/go-dist/tags/list": w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{ "tags": [ @@ -52,7 +79,7 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { ] }`) - case "/v2/some-repository/other-buildpack-id/tags/list": + case "/v2/paketobuildpacks/node-engine/tags/list": w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{ "tags": [ @@ -63,7 +90,7 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { ] }`) - case "/v2/some-repository/last-buildpack-id/tags/list": + case "/v2/paketobuildpacks/mri/tags/list": w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{ "tags": [ @@ -74,15 +101,66 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { ] }`) + case goConfigPath: + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawConfigFile(t, goImg)) + + case goManifestPath: + goManifestReqCount++ + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawManifest(t, goImg)) + + case nodeConfigPath: + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawConfigFile(t, nodeImg)) + + case nodeManifestPath: + nodeManifestReqCount++ + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawManifest(t, nodeImg)) + + case rubyConfigPath: + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawConfigFile(t, rubyImg)) + + case rubyManifestPath: + rubyManifestReqCount++ + if req.Method != http.MethodGet { + t.Errorf("Method; got %v, want %v", req.Method, http.MethodGet) + } + _, _ = w.Write(mustRawManifest(t, rubyImg)) + case "/v2/some-repository/error-buildpack-id/tags/list": w.WriteHeader(http.StatusTeapot) + case "/v2/some-repository/nonexistent-labels-id/tags/list": + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, `{ + "tags": [ + "0.1.0", + "0.2.0", + "latest" + ] + }`) + + case "/v2/some-repository/nonexistent-labels-id/manifests/0.2.0": + w.WriteHeader(http.StatusBadRequest) + default: t.Fatal(fmt.Sprintf("unknown path: %s", req.URL.Path)) } })) - var err error buildpackDir, err = os.MkdirTemp("", "") Expect(err).NotTo(HaveOccurred()) @@ -99,21 +177,21 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { [[order]] [[order.group]] - id = "some-repository/some-buildpack-id" + id = "paketo-buildpacks/go-dist" version = "0.20.1" [[order.group]] - id = "some-repository/last-buildpack-id" + id = "paketo-buildpacks/mri" version = "0.2.0" [[order]] [[order.group]] - id = "some-repository/other-buildpack-id" + id = "paketo-buildpacks/node-engine" version = "0.1.0" optional = true [[order.group]] - id = "some-repository/some-buildpack-id" + id = "paketo-buildpacks/go-dist" version = "0.20.1" `), 0600) Expect(err).NotTo(HaveOccurred()) @@ -123,13 +201,13 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { uri = "build/buildpack.tgz" [[dependencies]] - uri = "docker://REGISTRY-URI/some-repository/last-buildpack-id:0.2.0" + uri = "docker://REGISTRY-URI/paketobuildpacks/mri:0.2.0" [[dependencies]] - uri = "docker://REGISTRY-URI/some-repository/some-buildpack-id:0.20.1" + uri = "docker://REGISTRY-URI/paketo-buildpacks/go-dist:0.20.1" [[dependencies]] - uri = "docker://REGISTRY-URI/some-repository/other-buildpack-id:0.1.0" + uri = "docker://REGISTRY-URI/paketobuildpacks/node-engine:0.1.0" `), []byte(`REGISTRY-URI`), []byte(strings.TrimPrefix(server.URL, "http://"))), 0600) Expect(err).NotTo(HaveOccurred()) }) @@ -167,21 +245,21 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { [[order]] [[order.group]] - id = "some-repository/some-buildpack-id" + id = "paketo-buildpacks/go-dist" version = "0.20.12" [[order.group]] - id = "some-repository/last-buildpack-id" + id = "paketo-buildpacks/mri" version = "0.2.0" [[order]] [[order.group]] - id = "some-repository/other-buildpack-id" + id = "paketo-buildpacks/node-engine" version = "0.20.22" optional = true [[order.group]] - id = "some-repository/some-buildpack-id" + id = "paketo-buildpacks/go-dist" version = "0.20.12" `)) @@ -192,13 +270,13 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { uri = "build/buildpack.tgz" [[dependencies]] - uri = "docker://REGISTRY-URI/some-repository/last-buildpack-id:0.2.0" + uri = "docker://REGISTRY-URI/paketobuildpacks/mri:0.2.0" [[dependencies]] - uri = "docker://REGISTRY-URI/some-repository/some-buildpack-id:0.20.12" + uri = "docker://REGISTRY-URI/paketo-buildpacks/go-dist:0.20.12" [[dependencies]] - uri = "docker://REGISTRY-URI/some-repository/other-buildpack-id:0.20.22" + uri = "docker://REGISTRY-URI/paketobuildpacks/node-engine:0.20.22" `, "REGISTRY-URI", strings.TrimPrefix(server.URL, "http://")))) }) @@ -320,6 +398,54 @@ func testUpdateBuildpack(t *testing.T, context spec.G, it spec.S) { }) }) + context("when the buildpackage ID cannot be retrieved", func() { + it.Before(func() { + err := os.WriteFile(filepath.Join(buildpackDir, "buildpack.toml"), []byte(` + api = "0.2" + + [buildpack] + id = "some-composite-buildpack" + name = "Some Composite Buildpack" + version = "some-composite-buildpack-version" + + [metadata] + include-files = ["buildpack.toml"] + + [[order]] + [[order.group]] + id = "some-repository/nonexistent-labels-id" + version = "0.2.0" + `), 0600) + Expect(err).NotTo(HaveOccurred()) + + err = os.WriteFile(filepath.Join(buildpackDir, "package.toml"), bytes.ReplaceAll([]byte(` + [buildpack] + uri = "build/buildpack.tgz" + + [[dependencies]] + uri = "docker://REGISTRY-URI/some-repository/nonexistent-labels-id:0.2.0" + `), []byte(`REGISTRY-URI`), []byte(strings.TrimPrefix(server.URL, "http://"))), 0600) + Expect(err).NotTo(HaveOccurred()) + }) + + it("prints an error and exits non-zero", func() { + command := exec.Command( + path, + "update-buildpack", + "--buildpack-file", filepath.Join(buildpackDir, "buildpack.toml"), + "--package-file", filepath.Join(buildpackDir, "package.toml"), + ) + + buffer := gbytes.NewBuffer() + session, err := gexec.Start(command, buffer, buffer) + Expect(err).NotTo(HaveOccurred()) + + Eventually(session).Should(gexec.Exit(1), func() string { return string(buffer.Contents()) }) + Expect(string(buffer.Contents())).To(MatchRegexp(`failed to get buildpackage ID for \d+\.\d+\.\d+\.\d+\:\d+\/some\-repository\/nonexistent\-labels\-id\:0\.2\.0\:`)) + Expect(string(buffer.Contents())).To(ContainSubstring("unexpected status code 400 Bad Request")) + }) + }) + context("when the buildpack file cannot be overwritten", func() { it.Before(func() { Expect(os.Chmod(filepath.Join(buildpackDir, "buildpack.toml"), 0400)).To(Succeed())