Skip to content

Commit

Permalink
Use buildpackage metadata for image ID in builder/buildpack updates
Browse files Browse the repository at this point in the history
Signed-off-by: Frankie Gallina-Jones <frankieg@vmware.com>
  • Loading branch information
Sophie Wigmore authored and ryanmoran committed Apr 9, 2021
1 parent 95532b2 commit 2751e64
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 36 deletions.
7 changes: 6 additions & 1 deletion cargo/jam/commands/update_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
7 changes: 6 additions & 1 deletion cargo/jam/commands/update_buildpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
35 changes: 35 additions & 0 deletions cargo/jam/internal/image.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package internal

import (
"encoding/json"
"fmt"
"sort"
"strings"
Expand Down Expand Up @@ -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
}
29 changes: 29 additions & 0 deletions cargo/jam/internal/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'")))
})
})
})
})
}
160 changes: 144 additions & 16 deletions cargo/jam/update_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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": [
Expand All @@ -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": [
Expand Down Expand Up @@ -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)

Expand All @@ -95,24 +165,36 @@ 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())

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]
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down
Loading

0 comments on commit 2751e64

Please sign in to comment.