From a27f11b1e14bb5936cb861230ebde648b5576111 Mon Sep 17 00:00:00 2001 From: Andrew Meyer Date: Mon, 12 Aug 2019 16:47:20 -0400 Subject: [PATCH 1/3] Adds basic compatibility with buildpack/lifecycle#149 Resolves #243 Signed-off-by: Andrew Meyer Signed-off-by: Javier Romero --- Makefile | 6 +- acceptance/acceptance_test.go | 96 ++-- .../testdata/inspect_builder_output.txt | 16 +- .../mock_buildpacks/noop-buildpack/bin/build | 3 - .../mock_buildpacks/{ => v1}/builder.toml | 18 +- .../v1/noop-buildpack/bin/build | 3 + .../{ => v1}/noop-buildpack/bin/detect | 0 .../{ => v1}/noop-buildpack/buildpack.toml | 0 .../not-in-builder-buildpack/bin/build | 0 .../not-in-builder-buildpack/bin/detect | 0 .../not-in-builder-buildpack/buildpack.toml | 0 .../other-stack-buildpack/buildpack.toml | 0 .../{ => v1}/read-env-buildpack/bin/build | 2 - .../{ => v1}/read-env-buildpack/bin/detect | 0 .../read-env-buildpack/buildpack.toml | 0 .../simple-layers-buildpack/bin/build | 0 .../simple-layers-buildpack/bin/detect | 0 .../simple-layers-buildpack/buildpack.toml | 0 .../testdata/mock_buildpacks/v2/builder.toml | 30 ++ .../v2/noop-buildpack/bin/build | 3 + .../v2/noop-buildpack/bin/detect | 3 + .../v2/noop-buildpack/buildpack.toml | 7 + .../v2/not-in-builder-buildpack/bin/build | 21 + .../v2/not-in-builder-buildpack/bin/detect | 3 + .../not-in-builder-buildpack/buildpack.toml | 7 + .../v2/other-stack-buildpack/buildpack.toml | 7 + .../v2/read-env-buildpack/bin/build | 32 ++ .../v2/read-env-buildpack/bin/detect | 12 + .../v2/read-env-buildpack/buildpack.toml | 7 + .../v2/simple-layers-buildpack/bin/build | 34 ++ .../v2/simple-layers-buildpack/bin/detect | 3 + .../v2/simple-layers-buildpack/buildpack.toml | 7 + build.go | 22 +- build/phase_test.go | 2 +- build_test.go | 25 +- builder/builder.go | 153 ++++--- builder/builder_test.go | 429 ++++++++++-------- builder/compat.go | 37 ++ builder/compat_test.go | 46 ++ builder/config.go | 35 +- builder/metadata.go | 100 +++- builder/metadata_test.go | 153 +++++++ buildpack/buildpack.go | 13 +- buildpack/fetcher.go | 2 + buildpack/fetcher_test.go | 8 +- buildpack/testdata/buildpack/buildpack.toml | 11 +- commands/inspect_builder.go | 5 +- commands/inspect_builder_test.go | 30 +- create_builder.go | 10 +- create_builder_test.go | 8 +- inspect_builder_test.go | 2 +- internal/mocks/images.go | 7 +- testdata/builder.toml | 21 +- testhelpers/tar_assertions.go | 13 +- testhelpers/testhelpers.go | 1 + 55 files changed, 1053 insertions(+), 400 deletions(-) delete mode 100755 acceptance/testdata/mock_buildpacks/noop-buildpack/bin/build rename acceptance/testdata/mock_buildpacks/{ => v1}/builder.toml (69%) create mode 100755 acceptance/testdata/mock_buildpacks/v1/noop-buildpack/bin/build rename acceptance/testdata/mock_buildpacks/{ => v1}/noop-buildpack/bin/detect (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/noop-buildpack/buildpack.toml (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/not-in-builder-buildpack/bin/build (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/not-in-builder-buildpack/bin/detect (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/not-in-builder-buildpack/buildpack.toml (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/other-stack-buildpack/buildpack.toml (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/read-env-buildpack/bin/build (97%) rename acceptance/testdata/mock_buildpacks/{ => v1}/read-env-buildpack/bin/detect (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/read-env-buildpack/buildpack.toml (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/simple-layers-buildpack/bin/build (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/simple-layers-buildpack/bin/detect (100%) rename acceptance/testdata/mock_buildpacks/{ => v1}/simple-layers-buildpack/buildpack.toml (100%) create mode 100644 acceptance/testdata/mock_buildpacks/v2/builder.toml create mode 100755 acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/build create mode 100755 acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/detect create mode 100644 acceptance/testdata/mock_buildpacks/v2/noop-buildpack/buildpack.toml create mode 100755 acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/build create mode 100755 acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/detect create mode 100644 acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/buildpack.toml create mode 100644 acceptance/testdata/mock_buildpacks/v2/other-stack-buildpack/buildpack.toml create mode 100755 acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/build create mode 100755 acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/detect create mode 100644 acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/buildpack.toml create mode 100755 acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/build create mode 100755 acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/detect create mode 100644 acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/buildpack.toml create mode 100644 builder/compat.go create mode 100644 builder/compat_test.go create mode 100644 builder/metadata_test.go diff --git a/Makefile b/Makefile index 5034af607..822b2368d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ PACK_BIN?=pack PACKAGE_BASE=github.com/buildpack/pack PACKAGES:=$(shell $(GOCMD) list -mod=vendor ./... | grep -v /testdata/) SRC:=$(shell find . -type f -name '*.go' -not -path "./vendor/*") +ARCHIVE_NAME=pack-$(PACK_VERSION) all: clean verify test build @@ -14,6 +15,9 @@ build: mkdir -p ./out $(GOENV) $(GOCMD) build -mod=vendor -ldflags "-X 'main.Version=${PACK_VERSION}'" -o ./out/$(PACK_BIN) -a ./cmd/pack +package: + tar czf ./out/$(ARCHIVE_NAME).tgz -C out/ pack + install-goimports: @echo "> Installing goimports..." $(GOCMD) install -mod=vendor golang.org/x/tools/cmd/goimports @@ -31,7 +35,7 @@ test: unit acceptance unit: format vet @echo "> Running unit/integration tests..." $(GOCMD) test -mod=vendor -v -count=1 -parallel=1 -timeout=0 ./... - + acceptance: format vet @echo "> Running acceptance tests..." $(GOCMD) test -mod=vendor -v -count=1 -parallel=1 -timeout=0 -tags=acceptance ./acceptance diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 5e79ec082..42f5f585b 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -265,7 +265,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { var notBuilderTgz string it.Before(func() { - notBuilderTgz = h.CreateTgz(t, filepath.Join("testdata", "mock_buildpacks", "not-in-builder-buildpack"), "./", 0766) + notBuilderTgz = h.CreateTgz(t, filepath.Join(testBuildpacksDir(), "not-in-builder-buildpack"), "./", 0766) }) it.After(func() { @@ -276,13 +276,25 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { cmd := packCmd( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), + + // tgz not in builder "--buildpack", notBuilderTgz, + + // with version "--buildpack", "simple/layers@simple-layers-version", + + // without version "--buildpack", "noop.buildpack", + + // latest (for backwards compatibility) + "--buildpack", "read/env@latest", + "--env", "DETECT_ENV_BUILDPACK=true", ) output := h.Run(t, cmd) h.AssertContains(t, output, "NOOP Buildpack") + h.AssertContains(t, output, "Read Env Buildpack") h.AssertContains(t, output, fmt.Sprintf("Successfully built image '%s'", repoName)) + t.Log("app is runnable") assertMockAppRunsWithOutput(t, repoName, "Local Buildpack Dep Contents", @@ -290,7 +302,6 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { "Cached Dep Contents", ) }) - }) when("the argument is directory", func() { @@ -300,7 +311,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { cmd := packCmd( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", filepath.Join("testdata", "mock_buildpacks", "not-in-builder-buildpack"), + "--buildpack", filepath.Join(testBuildpacksDir(), "not-in-builder-buildpack"), ) output := h.Run(t, cmd) h.AssertContains(t, output, fmt.Sprintf("Successfully built image '%s'", repoName)) @@ -313,7 +324,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { var otherStackBuilderTgz string it.Before(func() { - otherStackBuilderTgz = h.CreateTgz(t, filepath.Join("testdata", "mock_buildpacks", "other-stack-buildpack"), "./", 0766) + otherStackBuilderTgz = h.CreateTgz(t, filepath.Join(testBuildpacksDir(), "other-stack-buildpack"), "./", 0766) }) it.After(func() { @@ -328,7 +339,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { ) txt, err := h.RunE(cmd) h.AssertNotNil(t, err) - h.AssertContains(t, txt, "buildpack 'other/stack/bp' version 'other-stack-version' does not support stack 'pack.test.stack'") + h.AssertContains(t, txt, "buildpack 'other/stack/bp@other-stack-version' does not support stack 'pack.test.stack'") }) }) }) @@ -780,57 +791,84 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { }) } +func testBuildpacksDir() string { + d := "v1" + if lifecycleVersion.GreaterThan(lifecycleV030) { + d = "v2" + } + return filepath.Join("testdata", "mock_buildpacks", d) +} + func createBuilder(t *testing.T, runImageMirror string) string { - t.Log("create builder image") + t.Log("Creating builder image...") + // CREATE TEMP WORKING DIR tmpDir, err := ioutil.TempDir("", "create-test-builder") h.AssertNil(t, err) defer os.RemoveAll(tmpDir) - h.RecursiveCopy(t, filepath.Join("testdata", "mock_buildpacks"), tmpDir) + // DETERMINE LIFECYCLE + lifecyclePath, hasLifecyclePath := os.LookupEnv("LIFECYCLE_PATH") + if hasLifecyclePath { + lifecycleVersion = semver.MustParse("0.0.0") - buildpacks := []string{ - "noop-buildpack", - "not-in-builder-buildpack", - "other-stack-buildpack", - "read-env-buildpack", - "simple-layers-buildpack", + if !filepath.IsAbs(lifecyclePath) { + lifecyclePath, err = filepath.Abs(lifecyclePath) + h.AssertNil(t, err) + } } - for _, v := range buildpacks { - tgz := h.CreateTgz(t, filepath.Join("testdata", "mock_buildpacks", v), "./", 0766) - err := os.Rename(tgz, filepath.Join(tmpDir, v+".tgz")) - h.AssertNil(t, err) + if v, ok := os.LookupEnv("LIFECYCLE_VERSION"); ok { + lifecycleVersion = semver.MustParse(v) } + // DETERMINE TEST DATA + t.Log("Using buildpacks from: ", testBuildpacksDir()) + h.RecursiveCopy(t, testBuildpacksDir(), tmpDir) + + // COPY builder.toml builderConfigFile, err := os.OpenFile(filepath.Join(tmpDir, "builder.toml"), os.O_RDWR|os.O_APPEND, 0666) h.AssertNil(t, err) + // ADD run-image-mirrors _, err = builderConfigFile.Write([]byte(fmt.Sprintf("run-image-mirrors = [\"%s\"]\n", runImageMirror))) h.AssertNil(t, err) + // ADD lifecycle _, err = builderConfigFile.Write([]byte("[lifecycle]\n")) h.AssertNil(t, err) - if lifecyclePath, ok := os.LookupEnv("LIFECYCLE_PATH"); ok { - lifecycleVersion = semver.MustParse("0.0.0") - if !filepath.IsAbs(lifecyclePath) { - t.Fatal("LIFECYCLE_PATH must be an absolute path") - } + + if hasLifecyclePath { t.Logf("Adding lifecycle path '%s' to builder config", lifecyclePath) _, err = builderConfigFile.Write([]byte(fmt.Sprintf("uri = \"%s\"\n", strings.ReplaceAll(lifecyclePath, `\`, `\\`)))) h.AssertNil(t, err) } - if lcver, ok := os.LookupEnv("LIFECYCLE_VERSION"); ok { - lifecycleVersion = semver.MustParse(lcver) - t.Logf("Adding lifecycle version '%s' to builder config", lifecycleVersion) - _, err = builderConfigFile.Write([]byte(fmt.Sprintf("version = \"%s\"\n", lifecycleVersion.String()))) - h.AssertNil(t, err) - } + + t.Logf("Adding lifecycle version '%s' to builder config", lifecycleVersion) + _, err = builderConfigFile.Write([]byte(fmt.Sprintf("version = \"%s\"\n", lifecycleVersion.String()))) + h.AssertNil(t, err) builderConfigFile.Close() + // PACKAGE BUILDPACKS + buildpacks := []string{ + "noop-buildpack", + "not-in-builder-buildpack", + "other-stack-buildpack", + "read-env-buildpack", + "simple-layers-buildpack", + } + + for _, v := range buildpacks { + tgz := h.CreateTgz(t, filepath.Join(testBuildpacksDir(), v), "./", 0766) + err := os.Rename(tgz, filepath.Join(tmpDir, v+".tgz")) + h.AssertNil(t, err) + } + + // NAME BUILDER builder := registryConfig.RepoName("some-org/" + h.RandString(10)) + // CREATE BUILDER t.Logf("Creating builder. Lifecycle version '%s' will be used.", lifecycleVersion) cmd := exec.Command(packPath, "create-builder", "--no-color", builder, "-b", filepath.Join(tmpDir, "builder.toml")) output := h.Run(t, cmd) @@ -841,7 +879,7 @@ func createBuilder(t *testing.T, runImageMirror string) string { } func createStack(t *testing.T, dockerCli *client.Client) { - t.Log("create stack images") + t.Log("Creating stack images...") createStackImage(t, dockerCli, runImage, filepath.Join("testdata", "mock_stack")) h.AssertNil(t, dockerCli.ImageTag(context.Background(), runImage, buildImage)) h.AssertNil(t, dockerCli.ImageTag(context.Background(), runImage, runImageMirror)) diff --git a/acceptance/testdata/inspect_builder_output.txt b/acceptance/testdata/inspect_builder_output.txt index bd28b034f..d0bd3c3bf 100644 --- a/acceptance/testdata/inspect_builder_output.txt +++ b/acceptance/testdata/inspect_builder_output.txt @@ -13,10 +13,10 @@ Run Images: %s Buildpacks: - ID VERSION LATEST - simple/layers simple-layers-version false - read/env read-env-version false - noop.buildpack noop.buildpack.version true + ID VERSION + simple/layers simple-layers-version + read/env read-env-version + noop.buildpack noop.buildpack.version Detection Order: Group #1: @@ -36,10 +36,10 @@ Run Images: %s Buildpacks: - ID VERSION LATEST - simple/layers simple-layers-version false - read/env read-env-version false - noop.buildpack noop.buildpack.version true + ID VERSION + simple/layers simple-layers-version + read/env read-env-version + noop.buildpack noop.buildpack.version Detection Order: Group #1: diff --git a/acceptance/testdata/mock_buildpacks/noop-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/noop-buildpack/bin/build deleted file mode 100755 index e5a56171d..000000000 --- a/acceptance/testdata/mock_buildpacks/noop-buildpack/bin/build +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -echo "---> NOOP buildpack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/builder.toml b/acceptance/testdata/mock_buildpacks/v1/builder.toml similarity index 69% rename from acceptance/testdata/mock_buildpacks/builder.toml rename to acceptance/testdata/mock_buildpacks/v1/builder.toml index e266f0861..907342f4f 100644 --- a/acceptance/testdata/mock_buildpacks/builder.toml +++ b/acceptance/testdata/mock_buildpacks/v1/builder.toml @@ -12,16 +12,16 @@ id = "noop.buildpack" version = "noop.buildpack.version" uri = "noop-buildpack.tgz" - latest = true -[[groups]] - [[groups.buildpacks]] - id = "simple/layers" - version = "simple-layers-version" - [[groups.buildpacks]] - id = "read/env" - version = "read-env-version" - optional = true +[[order]] +[[order.group]] + id = "simple/layers" + # intentionlly missing version to test support + +[[order.group]] + id = "read/env" + version = "read-env-version" + optional = true [stack] id = "pack.test.stack" diff --git a/acceptance/testdata/mock_buildpacks/v1/noop-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v1/noop-buildpack/bin/build new file mode 100755 index 000000000..a39147a6c --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v1/noop-buildpack/bin/build @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "---> NOOP Buildpack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/noop-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v1/noop-buildpack/bin/detect similarity index 100% rename from acceptance/testdata/mock_buildpacks/noop-buildpack/bin/detect rename to acceptance/testdata/mock_buildpacks/v1/noop-buildpack/bin/detect diff --git a/acceptance/testdata/mock_buildpacks/noop-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v1/noop-buildpack/buildpack.toml similarity index 100% rename from acceptance/testdata/mock_buildpacks/noop-buildpack/buildpack.toml rename to acceptance/testdata/mock_buildpacks/v1/noop-buildpack/buildpack.toml diff --git a/acceptance/testdata/mock_buildpacks/not-in-builder-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v1/not-in-builder-buildpack/bin/build similarity index 100% rename from acceptance/testdata/mock_buildpacks/not-in-builder-buildpack/bin/build rename to acceptance/testdata/mock_buildpacks/v1/not-in-builder-buildpack/bin/build diff --git a/acceptance/testdata/mock_buildpacks/not-in-builder-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v1/not-in-builder-buildpack/bin/detect similarity index 100% rename from acceptance/testdata/mock_buildpacks/not-in-builder-buildpack/bin/detect rename to acceptance/testdata/mock_buildpacks/v1/not-in-builder-buildpack/bin/detect diff --git a/acceptance/testdata/mock_buildpacks/not-in-builder-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v1/not-in-builder-buildpack/buildpack.toml similarity index 100% rename from acceptance/testdata/mock_buildpacks/not-in-builder-buildpack/buildpack.toml rename to acceptance/testdata/mock_buildpacks/v1/not-in-builder-buildpack/buildpack.toml diff --git a/acceptance/testdata/mock_buildpacks/other-stack-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v1/other-stack-buildpack/buildpack.toml similarity index 100% rename from acceptance/testdata/mock_buildpacks/other-stack-buildpack/buildpack.toml rename to acceptance/testdata/mock_buildpacks/v1/other-stack-buildpack/buildpack.toml diff --git a/acceptance/testdata/mock_buildpacks/read-env-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/bin/build similarity index 97% rename from acceptance/testdata/mock_buildpacks/read-env-buildpack/bin/build rename to acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/bin/build index 7d0744375..4ebe895eb 100755 --- a/acceptance/testdata/mock_buildpacks/read-env-buildpack/bin/build +++ b/acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/bin/build @@ -1,7 +1,5 @@ #!/usr/bin/env bash -#!/usr/bin/env bash - echo "---> Read Env Buildpack" set -o errexit diff --git a/acceptance/testdata/mock_buildpacks/read-env-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/bin/detect similarity index 100% rename from acceptance/testdata/mock_buildpacks/read-env-buildpack/bin/detect rename to acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/bin/detect diff --git a/acceptance/testdata/mock_buildpacks/read-env-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/buildpack.toml similarity index 100% rename from acceptance/testdata/mock_buildpacks/read-env-buildpack/buildpack.toml rename to acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/buildpack.toml diff --git a/acceptance/testdata/mock_buildpacks/simple-layers-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v1/simple-layers-buildpack/bin/build similarity index 100% rename from acceptance/testdata/mock_buildpacks/simple-layers-buildpack/bin/build rename to acceptance/testdata/mock_buildpacks/v1/simple-layers-buildpack/bin/build diff --git a/acceptance/testdata/mock_buildpacks/simple-layers-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v1/simple-layers-buildpack/bin/detect similarity index 100% rename from acceptance/testdata/mock_buildpacks/simple-layers-buildpack/bin/detect rename to acceptance/testdata/mock_buildpacks/v1/simple-layers-buildpack/bin/detect diff --git a/acceptance/testdata/mock_buildpacks/simple-layers-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v1/simple-layers-buildpack/buildpack.toml similarity index 100% rename from acceptance/testdata/mock_buildpacks/simple-layers-buildpack/buildpack.toml rename to acceptance/testdata/mock_buildpacks/v1/simple-layers-buildpack/buildpack.toml diff --git a/acceptance/testdata/mock_buildpacks/v2/builder.toml b/acceptance/testdata/mock_buildpacks/v2/builder.toml new file mode 100644 index 000000000..1abe1c3b3 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/builder.toml @@ -0,0 +1,30 @@ +[[buildpacks]] + id = "simple/layers" + version = "simple-layers-version" + uri = "simple-layers-buildpack.tgz" + +[[buildpacks]] + id = "read/env" + version = "read-env-version" + uri = "read-env-buildpack.tgz" + +[[buildpacks]] + # intentionally missing id/version as they are optional + uri = "noop-buildpack.tgz" + +[[order]] +[[order.group]] + id = "simple/layers" + # intentionlly missing version to test support + +[[order.group]] + id = "read/env" + version = "read-env-version" + optional = true + +[stack] + id = "pack.test.stack" + build-image = "pack-test/build" + run-image = "pack-test/run" + +# run-image-mirror and lifecycle are appended by acceptance tests diff --git a/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/build new file mode 100755 index 000000000..a39147a6c --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/build @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "---> NOOP Buildpack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/detect new file mode 100755 index 000000000..d1813055a --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/bin/detect @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +## always detect \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/buildpack.toml new file mode 100644 index 000000000..47eb4aecf --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/noop-buildpack/buildpack.toml @@ -0,0 +1,7 @@ +[buildpack] + id = "noop.buildpack" + version = "noop.buildpack.version" + name = "NOOP Buildpack" + +[[stacks]] + id = "pack.test.stack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/build new file mode 100755 index 000000000..2bd1e271c --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/build @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +echo "---> Simple mock buildpack" + +set -o errexit +set -o nounset +set -o pipefail + +launch_dir=$1 + +## makes a launch layer +echo "making launch layer" +mkdir "$launch_dir/launch-layer" +echo "Local Buildpack Dep Contents" > "$launch_dir/launch-layer/local-dep" +ln -snf "$launch_dir/launch-layer/local-dep" local-dep +echo "launch = true" > "$launch_dir/launch-layer.toml" + +## adds a process +echo 'processes = [{ type = "web", command = "./run"}]' > "$launch_dir/launch.toml" + +echo "---> Done" diff --git a/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/detect new file mode 100755 index 000000000..d1813055a --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/bin/detect @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +## always detect \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/buildpack.toml new file mode 100644 index 000000000..7e0876737 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/not-in-builder-buildpack/buildpack.toml @@ -0,0 +1,7 @@ +[buildpack] + id = "local/bp" + version = "local-bp-version" + name = "Not in Builder Buildpack" + +[[stacks]] + id = "pack.test.stack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/other-stack-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v2/other-stack-buildpack/buildpack.toml new file mode 100644 index 000000000..09c85a273 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/other-stack-buildpack/buildpack.toml @@ -0,0 +1,7 @@ +[buildpack] + id = "other/stack/bp" + version = "other-stack-version" + name = "Other Stack Buildpack" + +[[stacks]] + id = "other.stack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/build new file mode 100755 index 000000000..4ebe895eb --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/build @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +echo "---> Read Env Buildpack" + +set -o errexit +set -o nounset +set -o pipefail + +launch_dir=$1 +platform_dir=$2 + +## makes a launch layer +if [[ -f "$platform_dir/env/ENV1_CONTENTS" ]]; then + echo "making env1 layer" + mkdir "$launch_dir/env1-launch-layer" + contents=$(cat "$platform_dir/env/ENV1_CONTENTS") + echo "$contents" > "$launch_dir/env1-launch-layer/env1-launch-dep" + ln -snf "$launch_dir/env1-launch-layer/env1-launch-dep" env1-launch-dep + echo "launch = true" > "$launch_dir/env1-launch-layer.toml" +fi + +## makes a launch layer +if [[ -f "$platform_dir/env/ENV2_CONTENTS" ]]; then + echo "making env2 layer" + mkdir "$launch_dir/env2-launch-layer" + contents=$(cat "$platform_dir/env/ENV2_CONTENTS") + echo "$contents" > "$launch_dir/env2-launch-layer/env2-launch-dep" + ln -snf "$launch_dir/env2-launch-layer/env2-launch-dep" env2-launch-dep + echo "launch = true" > "$launch_dir/env2-launch-layer.toml" +fi + +echo "---> Done" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/detect new file mode 100755 index 000000000..bf9d25d47 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/bin/detect @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +echo "---> DETECT: Printenv buildpack" + +set -o errexit +set -o pipefail + +platform_dir=$1 + +if [[ ! -f $platform_dir/env/DETECT_ENV_BUILDPACK ]]; then + exit 1 +fi \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/buildpack.toml new file mode 100644 index 000000000..6d1b4733e --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/read-env-buildpack/buildpack.toml @@ -0,0 +1,7 @@ +[buildpack] + id = "read/env" + version = "read-env-version" + name = "Buildpack Reads Env" + +[[stacks]] + id = "pack.test.stack" \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/build b/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/build new file mode 100755 index 000000000..3f4475e66 --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/build @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +echo "---> Simple mock buildpack" + +set -o errexit +set -o nounset +set -o pipefail + +launch_dir=$1 + +## makes a launch layer +echo "making launch layer" +mkdir "$launch_dir/launch-layer" +echo "Launch Dep Contents" > "$launch_dir/launch-layer/launch-dep" +ln -snf "$launch_dir/launch-layer/launch-dep" launch-dep +echo "launch = true" > "$launch_dir/launch-layer.toml" + +## makes a cached launch layer +if [[ ! -f "$launch_dir/cached-launch-layer.toml" ]]; then + echo "making cached launch layer" + mkdir "$launch_dir/cached-launch-layer" + echo "Cached Dep Contents" > "$launch_dir/cached-launch-layer/cached-dep" + ln -snf "$launch_dir/cached-launch-layer/cached-dep" cached-dep + echo "launch = true" > "$launch_dir/cached-launch-layer.toml" + echo "cache = true" >> "$launch_dir/cached-launch-layer.toml" +else + echo "reusing cached launch layer" + ln -snf "$launch_dir/cached-launch-layer/cached-dep" cached-dep +fi + +## adds a process +echo 'processes = [{ type = "web", command = "./run"}]' > "$launch_dir/launch.toml" + +echo "---> Done" diff --git a/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/detect b/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/detect new file mode 100755 index 000000000..d1813055a --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/bin/detect @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +## always detect \ No newline at end of file diff --git a/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/buildpack.toml new file mode 100644 index 000000000..dddf09a1d --- /dev/null +++ b/acceptance/testdata/mock_buildpacks/v2/simple-layers-buildpack/buildpack.toml @@ -0,0 +1,7 @@ +[buildpack] + id = "simple/layers" + version = "simple-layers-version" + name = "Simple Layers Buildpack" + +[[stacks]] + id = "pack.test.stack" \ No newline at end of file diff --git a/build.go b/build.go index e7017f8b1..abffd3508 100644 --- a/build.go +++ b/build.go @@ -209,12 +209,12 @@ func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig { } func (c *Client) processBuildpacks(buildpacks []string) ([]buildpack.Buildpack, builder.GroupMetadata, error) { - group := builder.GroupMetadata{Buildpacks: []builder.GroupBuildpack{}} + group := builder.GroupMetadata{Buildpacks: []builder.BuildpackRefMetadata{}} var bps []buildpack.Buildpack for _, bp := range buildpacks { if isBuildpackId(bp) { id, version := c.parseBuildpack(bp) - group.Buildpacks = append(group.Buildpacks, builder.GroupBuildpack{ID: id, Version: version}) + group.Buildpacks = append(group.Buildpacks, builder.BuildpackRefMetadata{ID: id, Version: version}) } else { if runtime.GOOS == "windows" && filepath.Ext(bp) != ".tgz" { return nil, builder.GroupMetadata{}, fmt.Errorf("buildpack %s: Windows only supports .tgz-based buildpacks", style.Symbol(bp)) @@ -225,7 +225,7 @@ func (c *Client) processBuildpacks(buildpacks []string) ([]buildpack.Buildpack, return nil, builder.GroupMetadata{}, errors.Wrapf(err, "failed to fetch buildpack from URI '%s'", bp) } bps = append(bps, fetchedBP) - group.Buildpacks = append(group.Buildpacks, builder.GroupBuildpack{ID: fetchedBP.ID, Version: fetchedBP.Version}) + group.Buildpacks = append(group.Buildpacks, builder.BuildpackRefMetadata{ID: fetchedBP.ID, Version: fetchedBP.Version}) } } return bps, group, nil @@ -248,10 +248,14 @@ func isBuildpackId(path string) bool { func (c *Client) parseBuildpack(bp string) (string, string) { parts := strings.Split(bp, "@") if len(parts) == 2 { + if parts[1] == "latest" { + return parts[0], "" + } + return parts[0], parts[1] } - c.logger.Debugf("No version for %s buildpack provided, will use %s", style.Symbol(parts[0]), style.Symbol(parts[0]+"@latest")) - return parts[0], "latest" + + return parts[0], "" } func (c *Client) createEphemeralBuilder(rawBuilderImage imgutil.Image, env map[string]string, group builder.GroupMetadata, buildpacks []buildpack.Buildpack) (*builder.Builder, error) { @@ -263,15 +267,11 @@ func (c *Client) createEphemeralBuilder(rawBuilderImage imgutil.Image, env map[s bldr.SetEnv(env) for _, bp := range buildpacks { c.logger.Debugf("adding buildpack %s version %s to builder", style.Symbol(bp.ID), style.Symbol(bp.Version)) - if err := bldr.AddBuildpack(bp); err != nil { - return nil, errors.Wrapf(err, "failed to add buildpack %s version %s to builder", style.Symbol(bp.ID), style.Symbol(bp.Version)) - } + bldr.AddBuildpack(bp) } if len(group.Buildpacks) > 0 { c.logger.Debug("setting custom order") - if err := bldr.SetOrder([]builder.GroupMetadata{group}); err != nil { - return nil, errors.Wrap(err, "failed to set custom buildpack order") - } + bldr.SetOrder(builder.OrderMetadata{group}) } if err := bldr.Save(); err != nil { return nil, err diff --git a/build/phase_test.go b/build/phase_test.go index d7201f938..95c0b29b0 100644 --- a/build/phase_test.go +++ b/build/phase_test.go @@ -129,7 +129,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) { h.AssertContains(t, outBuf.String(), "failed to read file") }) - when("is posix", func() { + when.Pend("is posix", func() { it.Before(func() { h.SkipIf(t, runtime.GOOS == "windows", "Skipping on windows") }) diff --git a/build_test.go b/build_test.go index 01ae9e421..b3b82eb62 100644 --- a/build_test.go +++ b/build_test.go @@ -489,12 +489,12 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{{ID: "buildpack.id", Version: "buildpack.version"}}}, + {Buildpacks: []builder.BuildpackRefMetadata{{ID: "buildpack.id", Version: "buildpack.version"}}}, }) }) when("no version is provided", func() { - it("assumes latest", func() { + it("resolves version", func() { h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ Image: "some/app", Builder: builderName, @@ -505,7 +505,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{{ID: "buildpack.id", Version: "latest"}}}, + {Buildpacks: []builder.BuildpackRefMetadata{{ID: "buildpack.id", Version: "buildpack.version"}}}, }) }) }) @@ -517,7 +517,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { ClearCache: true, Buildpacks: []string{"missing.bp@version"}, }), - "failed to set custom buildpack order", + "no versions of buildpack 'missing.bp' were found on the builder", ) }) @@ -567,14 +567,14 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ + {Buildpacks: []builder.BuildpackRefMetadata{ {ID: "buildpack.id", Version: "buildpack.version"}, {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, }}, }) h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version", Latest: true}, }) }) }) @@ -601,7 +601,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ + {Buildpacks: []builder.BuildpackRefMetadata{ {ID: "buildpack.id", Version: "buildpack.version"}, {ID: "some-buildpack-id", Version: "some-buildpack-version"}, {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, @@ -609,8 +609,8 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, - {ID: "some-buildpack-id", Version: "some-buildpack-version"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + {ID: "some-buildpack-id", Version: "some-buildpack-version", Latest: true}, + {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version", Latest: true}, }) }) }) @@ -647,7 +647,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ + {Buildpacks: []builder.BuildpackRefMetadata{ {ID: "buildpack.id", Version: "buildpack.version"}, {ID: "some-buildpack-id", Version: "some-buildpack-version"}, {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, @@ -655,8 +655,8 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, - {ID: "some-buildpack-id", Version: "some-buildpack-version"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + {ID: "some-buildpack-id", Version: "some-buildpack-version", Latest: true}, + {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version", Latest: true}, }) }) }) @@ -707,7 +707,6 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { args = fakeImageFetcher.FetchCalls[builderName] h.AssertEq(t, args.Daemon, true) - }) when("false", func() { diff --git a/builder/builder.go b/builder/builder.go index 7e62abc8d..43d9173a8 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -13,7 +13,6 @@ import ( "path/filepath" "regexp" "strconv" - "strings" "time" "github.com/BurntSushi/toml" @@ -49,6 +48,19 @@ type Builder struct { replaceOrder bool } +type orderTOML struct { + Order OrderConfig `toml:"order"` +} + +type stackTOML struct { + RunImage stackTOMLRunImage `toml:"run-image"` +} + +type stackTOMLRunImage struct { + Image string `toml:"image"` + Mirrors []string `toml:"mirrors"` +} + func GetBuilder(img imgutil.Image) (*Builder, error) { uid, gid, err := userAndGroupIDs(img) if err != nil { @@ -144,13 +156,9 @@ func New(img imgutil.Image, name string) (*Builder, error) { }, nil } -func (b *Builder) AddBuildpack(bp buildpack.Buildpack) error { - if !bp.SupportsStack(b.StackID) { - return fmt.Errorf("buildpack %s version %s does not support stack %s", style.Symbol(bp.ID), style.Symbol(bp.Version), style.Symbol(b.StackID)) - } +func (b *Builder) AddBuildpack(bp buildpack.Buildpack) { b.buildpacks = append(b.buildpacks, bp) - b.metadata.Buildpacks = append(b.metadata.Buildpacks, BuildpackMetadata{ID: bp.ID, Version: bp.Version, Latest: bp.Latest}) - return nil + b.metadata.Buildpacks = append(b.metadata.Buildpacks, BuildpackMetadata{ID: bp.ID, Version: bp.Version}) } func (b *Builder) SetLifecycle(md lifecycle.Metadata) error { @@ -163,44 +171,9 @@ func (b *Builder) SetEnv(env map[string]string) { b.env = env } -func (b *Builder) SetOrder(order []GroupMetadata) error { - for _, group := range order { - for _, groupBP := range group.Buildpacks { - mdBPs := b.bpsWithID(groupBP.ID) - if len(mdBPs) == 0 { - return fmt.Errorf("no versions of buildpack %s were found on the builder", style.Symbol(groupBP.ID)) - } - if !hasBPWithVersion(mdBPs, groupBP.Version) { - if groupBP.Version == "latest" { - return fmt.Errorf("there is no version of buildpack %s marked as latest", style.Symbol(groupBP.ID)) - } else { - return fmt.Errorf("buildpack %s with version %s was not found on the builder", style.Symbol(groupBP.ID), style.Symbol(groupBP.Version)) - } - } - } - } +func (b *Builder) SetOrder(order OrderMetadata) { b.metadata.Groups = order b.replaceOrder = true - return nil -} - -func (b *Builder) bpsWithID(id string) []BuildpackMetadata { - var matchingBps []BuildpackMetadata - for _, bp := range b.metadata.Buildpacks { - if id == bp.ID { - matchingBps = append(matchingBps, bp) - } - } - return matchingBps -} - -func hasBPWithVersion(bps []BuildpackMetadata, version string) bool { - for _, bp := range bps { - if (bp.Version == version) || (bp.Latest && version == "latest") { - return true - } - } - return false } func (b *Builder) SetDescription(description string) { @@ -217,6 +190,14 @@ func (b *Builder) SetStackInfo(stackConfig StackConfig) { } func (b *Builder) Save() error { + if err := processMetadata(&b.metadata); err != nil { + return errors.Wrap(err, "processing metadata") + } + + if err := validateBuildpacks(b.StackID, b.buildpacks); err != nil { + return errors.Wrap(err, "validating buildpacks") + } + tmpDir, err := ioutil.TempDir("", "create-builder-scratch") if err != nil { return err @@ -294,6 +275,42 @@ func (b *Builder) Save() error { return err } +// TODO: error out when using incompatible lifecycle and buildpacks +func validateBuildpacks(stackID string, bps []buildpack.Buildpack) error { + bpLookup := map[string]interface{}{} + + for _, bp := range bps { + bpLookup[bp.ID+"@"+bp.Version] = nil + } + + for _, bp := range bps { + if len(bp.Order) == 0 && len(bp.Stacks) == 0 { + return fmt.Errorf("buildpack %s must have either stacks or an order defined", style.Symbol(bp.ID+"@"+bp.Version)) + } + + if len(bp.Order) >= 1 && len(bp.Stacks) >= 1 { + return fmt.Errorf("buildpack %s cannot have both stacks and an order defined", style.Symbol(bp.ID+"@"+bp.Version)) + } + + if len(bp.Stacks) >= 1 && !bp.SupportsStack(stackID) { + return fmt.Errorf( + "buildpack %s does not support stack %s", + style.Symbol(bp.ID+"@"+bp.Version), style.Symbol(stackID), + ) + } + + for _, g := range bp.Order { + for _, r := range g.Group { + if _, ok := bpLookup[r.ID+"@"+r.Version]; !ok { + return fmt.Errorf("buildpack %s not found on the builder", style.Symbol(r.ID+"@"+r.Version)) + } + } + } + } + + return nil +} + func userAndGroupIDs(img imgutil.Image) (int, int, error) { sUID, err := img.Env(envUID) if err != nil { @@ -379,14 +396,23 @@ func (b *Builder) rootOwnedDir(path string, time time.Time) *tar.Header { } func (b *Builder) orderLayer(dest string) (string, error) { - orderTOML := &bytes.Buffer{} - err := toml.NewEncoder(orderTOML).Encode(OrderTOML{Groups: b.metadata.Groups}) + buf := &bytes.Buffer{} + lifecycleVersion := b.GetLifecycleVersion() + + var tomlData interface{} + if lifecycleVersion != nil && lifecycleVersion.LessThan(semver.MustParse("0.4.0")) { + tomlData = v1OrderTOMLFromOrderTOML(orderTOML{Order: b.metadata.Groups.ToConfig()}) + } else { + tomlData = orderTOML{Order: b.metadata.Groups.ToConfig()} + } + + err := toml.NewEncoder(buf).Encode(tomlData) if err != nil { return "", errors.Wrapf(err, "failed to marshal order.toml") } layerTar := filepath.Join(dest, "order.tar") - err = archive.CreateSingleFileTar(layerTar, unixJoin(buildpacksDir, "order.toml"), orderTOML.String()) + err = archive.CreateSingleFileTar(layerTar, path.Join(buildpacksDir, "order.toml"), buf.String()) if err != nil { return "", errors.Wrapf(err, "failed to create order.toml layer tar") } @@ -395,14 +421,19 @@ func (b *Builder) orderLayer(dest string) (string, error) { } func (b *Builder) stackLayer(dest string) (string, error) { - stackTOML := &bytes.Buffer{} - err := toml.NewEncoder(stackTOML).Encode(b.metadata.Stack) + buf := &bytes.Buffer{} + err := toml.NewEncoder(buf).Encode(stackTOML{ + stackTOMLRunImage{ + Image: b.metadata.Stack.RunImage.Image, + Mirrors: b.metadata.Stack.RunImage.Mirrors, + }, + }) if err != nil { return "", errors.Wrapf(err, "failed to marshal stack.toml") } layerTar := filepath.Join(dest, "stack.tar") - err = archive.CreateSingleFileTar(layerTar, unixJoin(buildpacksDir, "stack.toml"), stackTOML.String()) + err = archive.CreateSingleFileTar(layerTar, path.Join(buildpacksDir, "stack.toml"), buf.String()) if err != nil { return "", errors.Wrapf(err, "failed to create stack.toml layer tar") } @@ -431,14 +462,14 @@ func (b *Builder) buildpackLayer(dest string, bp buildpack.Buildpack) (string, e if err := tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeDir, - Name: unixJoin(buildpacksDir, bp.EscapedID()), + Name: path.Join(buildpacksDir, bp.EscapedID()), Mode: 0755, ModTime: now, }); err != nil { return "", err } - baseTarDir := unixJoin(buildpacksDir, bp.EscapedID(), bp.Version) + baseTarDir := path.Join(buildpacksDir, bp.EscapedID(), bp.Version) if err := tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeDir, Name: baseTarDir, @@ -465,18 +496,6 @@ func (b *Builder) buildpackLayer(dest string, bp buildpack.Buildpack) (string, e return "", errors.Wrapf(err, "creating layer tar for buildpack '%s:%s'", bp.ID, bp.Version) } - if bp.Latest { - err := tw.WriteHeader(&tar.Header{ - Name: fmt.Sprintf("%s/%s/%s", buildpacksDir, bp.EscapedID(), "latest"), - Linkname: baseTarDir, - Typeflag: tar.TypeSymlink, - Mode: 0644, - }) - if err != nil { - return "", errors.Wrapf(err, "creating latest symlink for buildpack '%s:%s'", bp.ID, bp.Version) - } - } - return layerTar, nil } @@ -518,7 +537,7 @@ func (b *Builder) embedBuildpackTar(tw *tar.Writer, srcTar, baseTarDir string) e continue } - header.Name = path.Clean(unixJoin(baseTarDir, header.Name)) + header.Name = path.Clean(path.Join(baseTarDir, header.Name)) header.Uid = b.UID header.Gid = b.GID err = tw.WriteHeader(header) @@ -613,7 +632,7 @@ func (b *Builder) envLayer(dest string, env map[string]string) (string, error) { for k, v := range env { if err := tw.WriteHeader(&tar.Header{ - Name: unixJoin(platformDir, "env", k), + Name: path.Join(platformDir, "env", k), Size: int64(len(v)), Mode: 0644, ModTime: now, @@ -656,7 +675,3 @@ func (b *Builder) lifecycleLayer(dest string) (string, error) { return fh.Name(), nil } - -func unixJoin(paths ...string) string { - return strings.Join(paths, "/") -} diff --git a/builder/builder_test.go b/builder/builder_test.go index a638a64f1..683366be8 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -202,6 +202,119 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertOnTarEntry(t, layerTar, "/buildpacks/order.toml", h.ContentEquals("some content")) }) + when("order is invalid", func() { + it("produces an error", func() { + subject.SetOrder(builder.OrderMetadata{ + {Buildpacks: []builder.BuildpackRefMetadata{ + {ID: "missing-buildpack-id", Version: "missing-buildpack-version"}, + }}, + }) + + err := subject.Save() + + h.AssertError(t, err, "no versions of buildpack 'missing-buildpack-id' were found on the builder") + }) + }) + + when("validating buildpacks", func() { + when("buildpack is missing both order and stack", func() { + it("returns an error", func() { + subject.AddBuildpack(buildpack.Buildpack{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + Path: filepath.Join("testdata", "buildpack"), + }) + + err := subject.Save() + + h.AssertError(t, err, "buildpack 'some-buildpack-id@some-buildpack-version' must have either stacks or an order defined") + }) + }) + + when("buildpack has both order and stack", func() { + it("returns an error", func() { + subject.AddBuildpack(buildpack.Buildpack{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + Path: filepath.Join("testdata", "buildpack"), + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackRef{ + {ID: "some-buildpack-id", Version: "some-buildpack-version"}, + }, + }, + }, + Stacks: []buildpack.Stack{ + {ID: "some.stack.id"}, + }, + }) + + err := subject.Save() + + h.AssertError(t, err, "buildpack 'some-buildpack-id@some-buildpack-version' cannot have both stacks and an order defined") + }) + }) + + when("nested buildpack does not exist", func() { + when("buildpack by id does not exist", func() { + it("returns an error", func() { + subject.AddBuildpack(buildpack.Buildpack{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + Path: filepath.Join("testdata", "buildpack"), + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackRef{ + {ID: "missing-buildpack-id", Version: "missing-buildpack-version"}, + }, + }, + }, + }) + + err := subject.Save() + + h.AssertError(t, err, "buildpack 'missing-buildpack-id@missing-buildpack-version' not found on the builder") + }) + }) + + when("buildpack version does not exist", func() { + it("returns an error", func() { + subject.AddBuildpack(buildpack.Buildpack{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + Path: filepath.Join("testdata", "buildpack"), + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackRef{ + {ID: "some-buildpack-id", Version: "missing-buildpack-version"}, + }, + }, + }, + }) + + err := subject.Save() + + h.AssertError(t, err, "buildpack 'some-buildpack-id@missing-buildpack-version' not found on the builder") + }) + }) + }) + + when("buildpack stack id does not match", func() { + it("returns an error", func() { + subject.AddBuildpack(buildpack.Buildpack{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "other.stack.id"}}, + }) + + err := subject.Save() + + h.AssertError(t, err, "buildpack 'some-buildpack-id@some-buildpack-version' does not support stack 'some.stack.id'") + }) + }) + }) + }) when("#SetLifecycle", func() { @@ -281,147 +394,147 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { }) when("#AddBuildpack", func() { + var buildpackTgz string + it.Before(func() { err := os.Chmod(filepath.Join("testdata", "buildpack", "buildpack-file"), 0644) h.AssertNil(t, err) - }) + buildpackTgz = h.CreateTgz(t, filepath.Join("testdata", "buildpack"), "./", 0644) + + subject.AddBuildpack(buildpack.Buildpack{ + ID: "buildpack-1-id", + Version: "buildpack-1-version", + Path: buildpackTgz, + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackRef{ + { + ID: "buildpack-2-id", + Version: "buildpack-2-version", + }, + }, + }, + }, + }) - when("buildpack has matching stack", func() { - var buildpackTgz string + subject.AddBuildpack(buildpack.Buildpack{ + ID: "buildpack-2-id", + Version: "buildpack-2-version", + Path: buildpackTgz, + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + }) - it.Before(func() { - buildpackTgz = h.CreateTgz(t, filepath.Join("testdata", "buildpack"), "./", 0644) + subject.AddBuildpack(buildpack.Buildpack{ + ID: "buildpack-3-id", + Version: "buildpack-3-version", + Path: buildpackTgz, + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + }) - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ - ID: "tgz-buildpack-id", - Version: "tgz-buildpack-version", - Path: buildpackTgz, - Latest: false, + if runtime.GOOS != "windows" { + subject.AddBuildpack(buildpack.Buildpack{ + ID: "dir-buildpack-id", + Version: "dir-buildpack-version", + Path: filepath.Join("testdata", "buildpack"), Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) + }) + } - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ - ID: "latest-buildpack-id", - Version: "latest-buildpack-version", - Path: buildpackTgz, - Latest: true, - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) + h.AssertNil(t, subject.Save()) + h.AssertEq(t, baseImage.IsSaved(), true) + }) - if runtime.GOOS != "windows" { - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ - ID: "dir-buildpack-id", - Version: "dir-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) - } + it.After(func() { + h.AssertNil(t, os.Remove(buildpackTgz)) + }) - h.AssertNil(t, subject.Save()) - h.AssertEq(t, baseImage.IsSaved(), true) - }) + it("adds the buildpack as an image layer", func() { + var ( + layerTar string + err error + ) - it.After(func() { - h.AssertNil(t, os.Remove(buildpackTgz)) - }) + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/buildpack-1-id/buildpack-1-version") + h.AssertNil(t, err) - it("adds the buildpack as an image layer", func() { - var ( - layerTar string - err error - ) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-1-id/buildpack-1-version", + h.IsDirectory(), + ) - layerTar, err = baseImage.FindLayerWithPath("/buildpacks/tgz-buildpack-id/tgz-buildpack-version") - h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-1-id/buildpack-1-version/buildpack-file", + h.ContentEquals("buildpack-contents"), + h.HasOwnerAndGroup(1234, 4321), + h.HasFileMode(0644), + ) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/tgz-buildpack-id/tgz-buildpack-version", - h.IsDirectory(), - ) + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/buildpack-2-id/buildpack-2-version") + h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-2-id/buildpack-2-version", + h.IsDirectory(), + ) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/tgz-buildpack-id/tgz-buildpack-version/buildpack-file", - h.ContentEquals("buildpack-contents"), - h.HasOwnerAndGroup(1234, 4321), - h.HasFileMode(0644), - ) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-2-id/buildpack-2-version/buildpack-file", + h.ContentEquals("buildpack-contents"), + h.HasOwnerAndGroup(1234, 4321), + h.HasFileMode(0644), + ) + + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/buildpack-3-id/buildpack-3-version") + h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-3-id/buildpack-3-version", + h.IsDirectory(), + ) - layerTar, err = baseImage.FindLayerWithPath("/buildpacks/latest-buildpack-id/latest-buildpack-version") + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-3-id/buildpack-3-version/buildpack-file", + h.ContentEquals("buildpack-contents"), + h.HasOwnerAndGroup(1234, 4321), + h.HasFileMode(0644), + ) + + if runtime.GOOS != "windows" { + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/dir-buildpack-id/dir-buildpack-version") h.AssertNil(t, err) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/latest-buildpack-id/latest-buildpack-version", + h.AssertOnTarEntry(t, layerTar, "/buildpacks/dir-buildpack-id/dir-buildpack-version", h.IsDirectory(), ) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/latest-buildpack-id/latest-buildpack-version/buildpack-file", + h.AssertOnTarEntry(t, layerTar, "/buildpacks/dir-buildpack-id/dir-buildpack-version/buildpack-file", h.ContentEquals("buildpack-contents"), h.HasOwnerAndGroup(1234, 4321), h.HasFileMode(0644), ) - - if runtime.GOOS != "windows" { - layerTar, err = baseImage.FindLayerWithPath("/buildpacks/dir-buildpack-id/dir-buildpack-version") - h.AssertNil(t, err) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/dir-buildpack-id/dir-buildpack-version", - h.IsDirectory(), - ) - - h.AssertOnTarEntry(t, layerTar, "/buildpacks/dir-buildpack-id/dir-buildpack-version/buildpack-file", - h.ContentEquals("buildpack-contents"), - h.HasOwnerAndGroup(1234, 4321), - h.HasFileMode(0644), - ) - } - }) - - it("adds a symlink to the buildpack layer if latest is true", func() { - layerTar, err := baseImage.FindLayerWithPath("/buildpacks/latest-buildpack-id") - h.AssertNil(t, err) - - h.AssertOnTarEntry(t, - layerTar, - "/buildpacks/latest-buildpack-id/latest", - h.SymlinksTo("/buildpacks/latest-buildpack-id/latest-buildpack-version"), - h.HasOwnerAndGroup(0, 0), - h.HasFileMode(0644), - ) - }) - - it("adds the buildpack metadata", func() { - label, err := baseImage.Label("io.buildpacks.builder.metadata") - h.AssertNil(t, err) - - var metadata builder.Metadata - h.AssertNil(t, json.Unmarshal([]byte(label), &metadata)) - if runtime.GOOS == "windows" { - h.AssertEq(t, len(metadata.Buildpacks), 2) - } else { - h.AssertEq(t, len(metadata.Buildpacks), 3) - } - - h.AssertEq(t, metadata.Buildpacks[0].ID, "tgz-buildpack-id") - h.AssertEq(t, metadata.Buildpacks[0].Version, "tgz-buildpack-version") - h.AssertEq(t, metadata.Buildpacks[0].Latest, false) - - h.AssertEq(t, metadata.Buildpacks[1].ID, "latest-buildpack-id") - h.AssertEq(t, metadata.Buildpacks[1].Version, "latest-buildpack-version") - h.AssertEq(t, metadata.Buildpacks[1].Latest, true) - - if runtime.GOOS != "windows" { - h.AssertEq(t, metadata.Buildpacks[2].ID, "dir-buildpack-id") - h.AssertEq(t, metadata.Buildpacks[2].Version, "dir-buildpack-version") - h.AssertEq(t, metadata.Buildpacks[2].Latest, false) - } - }) + } }) - when("buildpack stack id does not match", func() { - it("returns an error", func() { - err := subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "other.stack.id"}}, - }) - h.AssertError(t, err, "buildpack 'some-buildpack-id' version 'some-buildpack-version' does not support stack 'some.stack.id'") - }) + it("adds the buildpack metadata", func() { + label, err := baseImage.Label("io.buildpacks.builder.metadata") + h.AssertNil(t, err) + + var metadata builder.Metadata + h.AssertNil(t, json.Unmarshal([]byte(label), &metadata)) + if runtime.GOOS == "windows" { + h.AssertEq(t, len(metadata.Buildpacks), 3) + } else { + h.AssertEq(t, len(metadata.Buildpacks), 4) + } + + h.AssertEq(t, metadata.Buildpacks[0].ID, "buildpack-1-id") + h.AssertEq(t, metadata.Buildpacks[0].Version, "buildpack-1-version") + h.AssertEq(t, metadata.Buildpacks[0].Latest, true) + + h.AssertEq(t, metadata.Buildpacks[1].ID, "buildpack-2-id") + h.AssertEq(t, metadata.Buildpacks[1].Version, "buildpack-2-version") + h.AssertEq(t, metadata.Buildpacks[1].Latest, true) + + h.AssertEq(t, metadata.Buildpacks[2].ID, "buildpack-3-id") + h.AssertEq(t, metadata.Buildpacks[2].Version, "buildpack-3-version") + h.AssertEq(t, metadata.Buildpacks[2].Latest, true) + + if runtime.GOOS != "windows" { + h.AssertEq(t, metadata.Buildpacks[3].ID, "dir-buildpack-id") + h.AssertEq(t, metadata.Buildpacks[3].Version, "dir-buildpack-version") + h.AssertEq(t, metadata.Buildpacks[3].Latest, true) + } }) when("base image already has metadata", func() { @@ -435,12 +548,12 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { subject, err = builder.New(baseImage, "some/builder") h.AssertNil(t, err) - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ + subject.AddBuildpack(buildpack.Buildpack{ ID: "some-buildpack-id", Version: "some-buildpack-version", Path: filepath.Join("testdata", "buildpack"), Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) + }) h.AssertNil(t, subject.Save()) h.AssertEq(t, baseImage.IsSaved(), true) }) @@ -463,7 +576,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { // adds new buildpack h.AssertEq(t, metadata.Buildpacks[1].ID, "some-buildpack-id") h.AssertEq(t, metadata.Buildpacks[1].Version, "some-buildpack-version") - h.AssertEq(t, metadata.Buildpacks[1].Latest, false) + h.AssertEq(t, metadata.Buildpacks[1].Latest, true) }) }) }) @@ -471,38 +584,38 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("#SetOrder", func() { when("the buildpacks exist in the image", func() { it.Before(func() { - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ + subject.AddBuildpack(buildpack.Buildpack{ ID: "some-buildpack-id", Version: "some-buildpack-version", Path: filepath.Join("testdata", "buildpack"), Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ + }) + subject.AddBuildpack(buildpack.Buildpack{ ID: "optional-buildpack-id", Version: "older-optional-buildpack-version", Path: filepath.Join("testdata", "buildpack"), Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ + }) + subject.AddBuildpack(buildpack.Buildpack{ ID: "optional-buildpack-id", Version: "optional-buildpack-version", - Latest: true, Path: filepath.Join("testdata", "buildpack"), Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) - h.AssertNil(t, subject.SetOrder([]builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ + }) + subject.SetOrder(builder.OrderMetadata{ + {Buildpacks: []builder.BuildpackRefMetadata{ { ID: "some-buildpack-id", Version: "some-buildpack-version", }, { ID: "optional-buildpack-id", - Version: "latest", + Version: "optional-buildpack-version", Optional: true, }, }}, - })) + }) + h.AssertNil(t, subject.Save()) h.AssertEq(t, baseImage.IsSaved(), true) }) @@ -510,15 +623,15 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { it("adds the order.toml to the image", func() { layerTar, err := baseImage.FindLayerWithPath("/buildpacks/order.toml") h.AssertNil(t, err) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/order.toml", h.ContentEquals(`[[groups]] + h.AssertOnTarEntry(t, layerTar, "/buildpacks/order.toml", h.ContentEquals(`[[order]] - [[groups.buildpacks]] + [[order.group]] id = "some-buildpack-id" version = "some-buildpack-version" - [[groups.buildpacks]] + [[order.group]] id = "optional-buildpack-id" - version = "latest" + version = "optional-buildpack-version" optional = true `)) }) @@ -537,57 +650,9 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, metadata.Groups[0].Buildpacks[0].Version, "some-buildpack-version") h.AssertEq(t, metadata.Groups[0].Buildpacks[1].ID, "optional-buildpack-id") - h.AssertEq(t, metadata.Groups[0].Buildpacks[1].Version, "latest") + h.AssertEq(t, metadata.Groups[0].Buildpacks[1].Version, "optional-buildpack-version") h.AssertEq(t, metadata.Groups[0].Buildpacks[1].Optional, true) }) - - when("the group buildpack has latest version", func() { - it("fails if no buildpack is tagged as latest", func() { - err := subject.SetOrder([]builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ - { - ID: "some-buildpack-id", - Version: "latest", - }, - }}, - }) - h.AssertError(t, err, "there is no version of buildpack 'some-buildpack-id' marked as latest") - }) - }) - }) - - when("no version of the group buildpack exists in the image", func() { - it("errors", func() { - err := subject.SetOrder([]builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ - { - ID: "some-buildpack-id", - Version: "some-buildpack-version", - }, - }}, - }) - h.AssertError(t, err, "no versions of buildpack 'some-buildpack-id' were found on the builder") - }) - }) - - when("wrong versions of the group buildpack exists in the image", func() { - it("errors", func() { - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) - err := subject.SetOrder([]builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ - { - ID: "some-buildpack-id", - Version: "wrong-version", - }, - }}, - }) - h.AssertError(t, err, "buildpack 'some-buildpack-id' with version 'wrong-version' was not found on the builder") - }) }) }) diff --git a/builder/compat.go b/builder/compat.go new file mode 100644 index 000000000..8e94a1cb1 --- /dev/null +++ b/builder/compat.go @@ -0,0 +1,37 @@ +package builder + +type v1OrderTOML struct { + Groups []v1Group `toml:"groups"` +} + +type v1Group struct { + Buildpacks []v1BuildpackRef `toml:"buildpacks"` +} + +type v1BuildpackRef struct { + ID string `toml:"id"` + Version string `toml:"version"` + Optional bool `toml:"optional,omitempty"` +} + +func v1OrderTOMLFromOrderTOML(order orderTOML) v1OrderTOML { + var groups []v1Group + for _, g := range order.Order { + var bps []v1BuildpackRef + for _, b := range g.Group { + bps = append(bps, v1BuildpackRef{ + ID: b.ID, + Version: b.Version, + Optional: b.Optional, + }) + } + + groups = append(groups, v1Group{ + Buildpacks: bps, + }) + } + + return v1OrderTOML{ + Groups: groups, + } +} diff --git a/builder/compat_test.go b/builder/compat_test.go new file mode 100644 index 000000000..4b90b091f --- /dev/null +++ b/builder/compat_test.go @@ -0,0 +1,46 @@ +package builder + +import ( + "testing" + + "github.com/fatih/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + h "github.com/buildpack/pack/testhelpers" +) + +func TestCompat(t *testing.T) { + color.NoColor = true + spec.Run(t, "Compat", testCompat, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testCompat(t *testing.T, when spec.G, it spec.S) { + when("#v1OrderTOMLFromOrderTOML", func() { + it("converts", func() { + newToml := orderTOML{ + Order: []GroupConfig{ + { + Group: []BuildpackRefConfig{ + {ID: "buildpack.id.1", Version: "1.2.3", Optional: false}, + {ID: "buildpack.id.2", Version: "4.5.6", Optional: true}, + }, + }, + }, + } + + result := v1OrderTOMLFromOrderTOML(newToml) + + h.AssertEq(t, result, v1OrderTOML{ + Groups: []v1Group{ + { + Buildpacks: []v1BuildpackRef{ + {ID: "buildpack.id.1", Version: "1.2.3", Optional: false}, + {ID: "buildpack.id.2", Version: "4.5.6", Optional: true}, + }, + }, + }, + }) + }) + }) +} diff --git a/builder/config.go b/builder/config.go index bd0e58927..cd669e7df 100644 --- a/builder/config.go +++ b/builder/config.go @@ -15,16 +15,47 @@ import ( type Config struct { Description string `toml:"description"` Buildpacks []BuildpackConfig `toml:"buildpacks"` - Groups []GroupMetadata `toml:"groups"` + Order OrderConfig `toml:"order"` Stack StackConfig `toml:"stack"` Lifecycle LifecycleConfig `toml:"lifecycle"` } +type OrderConfig []GroupConfig + +func (o OrderConfig) ToMetadata() OrderMetadata { + var order OrderMetadata + for _, gp := range o { + var buildpacks []BuildpackRefMetadata + for _, bp := range gp.Group { + buildpacks = append(buildpacks, BuildpackRefMetadata{ + ID: bp.ID, + Version: bp.Version, + Optional: bp.Optional, + }) + } + + order = append(order, GroupMetadata{ + Buildpacks: buildpacks, + }) + } + + return order +} + +type GroupConfig struct { + Group []BuildpackRefConfig `toml:"group"` +} + +type BuildpackRefConfig struct { + ID string `toml:"id"` + Version string `toml:"version"` + Optional bool `toml:"optional,omitempty"` +} + type BuildpackConfig struct { ID string `toml:"id"` Version string `toml:"version"` URI string `toml:"uri"` - Latest bool `toml:"latest"` } type StackConfig struct { diff --git a/builder/metadata.go b/builder/metadata.go index 4e5f37a9b..fc9a79a8f 100644 --- a/builder/metadata.go +++ b/builder/metadata.go @@ -1,7 +1,10 @@ package builder import ( + "fmt" + "github.com/buildpack/pack/lifecycle" + "github.com/buildpack/pack/style" ) const MetadataLabel = "io.buildpacks.builder.metadata" @@ -9,7 +12,7 @@ const MetadataLabel = "io.buildpacks.builder.metadata" type Metadata struct { Description string `json:"description"` Buildpacks []BuildpackMetadata `json:"buildpacks"` - Groups []GroupMetadata `json:"groups"` + Groups OrderMetadata `json:"groups"` Stack StackMetadata `json:"stack"` Lifecycle lifecycle.Metadata `json:"lifecycle"` } @@ -17,28 +20,99 @@ type Metadata struct { type BuildpackMetadata struct { ID string `json:"id"` Version string `json:"version"` - Latest bool `json:"latest"` + Latest bool `json:"latest"` // deprecated } -type GroupMetadata struct { - Buildpacks []GroupBuildpack `json:"buildpacks" toml:"buildpacks"` +type OrderMetadata []GroupMetadata + +func (o OrderMetadata) ToConfig() OrderConfig { + var order OrderConfig + + for _, group := range o { + var buildpacks []BuildpackRefConfig + for _, bp := range group.Buildpacks { + buildpacks = append(buildpacks, BuildpackRefConfig{ + ID: bp.ID, + Version: bp.Version, + Optional: bp.Optional, + }) + } + + order = append(order, GroupConfig{ + Group: buildpacks, + }) + } + + return order } -type OrderTOML struct { - Groups []GroupMetadata `toml:"groups"` +type GroupMetadata struct { + Buildpacks []BuildpackRefMetadata `json:"buildpacks"` } -type GroupBuildpack struct { - ID string `json:"id" toml:"id"` - Version string `json:"version" toml:"version"` - Optional bool `json:"optional,omitempty" toml:"optional,omitempty"` +type BuildpackRefMetadata struct { + ID string `json:"id"` + Version string `json:"version"` + Optional bool `json:"optional,omitempty"` } type StackMetadata struct { - RunImage RunImageMetadata `toml:"run-image" json:"runImage"` + RunImage RunImageMetadata `json:"runImage"` } type RunImageMetadata struct { - Image string `toml:"image" json:"image"` - Mirrors []string `toml:"mirrors" json:"mirrors"` + Image string `json:"image"` + Mirrors []string `json:"mirrors"` +} + +func bpsWithID(metadata Metadata, id string) []BuildpackMetadata { + var matchingBps []BuildpackMetadata + for _, bp := range metadata.Buildpacks { + if id == bp.ID { + matchingBps = append(matchingBps, bp) + } + } + return matchingBps +} + +func hasBPWithVersion(bps []BuildpackMetadata, version string) bool { + for _, bp := range bps { + if bp.Version == version { + return true + } + } + return false +} + +func processMetadata(md *Metadata) error { + for i, bp := range md.Buildpacks { + if len(bpsWithID(*md, bp.ID)) == 1 { + md.Buildpacks[i].Latest = true + } + } + + for _, g := range md.Groups { + for i := range g.Buildpacks { + bpRef := &g.Buildpacks[i] + bps := bpsWithID(*md, bpRef.ID) + + if len(bps) == 0 { + return fmt.Errorf("no versions of buildpack %s were found on the builder", style.Symbol(bpRef.ID)) + } + + if bpRef.Version == "" { + if len(bps) > 1 { + return fmt.Errorf("unable to resolve version: multiple versions of %s - must specify an explicit version", style.Symbol(bpRef.ID)) + } + + bpRef.Version = bps[0].Version + } + + if !hasBPWithVersion(bps, bpRef.Version) { + return fmt.Errorf("buildpack %s with version %s was not found on the builder", style.Symbol(bpRef.ID), style.Symbol(bpRef.Version)) + } + } + } + + return nil } diff --git a/builder/metadata_test.go b/builder/metadata_test.go new file mode 100644 index 000000000..4385cce5a --- /dev/null +++ b/builder/metadata_test.go @@ -0,0 +1,153 @@ +package builder + +import ( + "testing" + + "github.com/Masterminds/semver" + "github.com/fatih/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpack/pack/lifecycle" + h "github.com/buildpack/pack/testhelpers" +) + +func TestMetadata(t *testing.T) { + color.NoColor = true + spec.Run(t, "Metadata", testMetadata, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testMetadata(t *testing.T, when spec.G, it spec.S) { + when("#processMetadata", func() { + when("the buildpack is the only version", func() { + it("should resolve unset version", func() { + md := Metadata{ + Buildpacks: []BuildpackMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + }, + Groups: []GroupMetadata{ + { + Buildpacks: []BuildpackRefMetadata{ + {ID: "bp.id.1", Version: ""}, + }, + }, + }, + } + + err := processMetadata(&md) + h.AssertNil(t, err) + + h.AssertEq(t, md.Buildpacks[0].Latest, true) + h.AssertEq(t, md.Groups[0].Buildpacks[0].Version, "1.2.3") + }) + }) + + when("the buildpack has multiple versions", func() { + when("order contains specific version", func() { + it("nothing should be updated", func() { + md := Metadata{ + Buildpacks: []BuildpackMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + {ID: "bp.id.1", Version: "4.5.6"}, + }, + Groups: []GroupMetadata{ + { + Buildpacks: []BuildpackRefMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + }, + }, + }, + Lifecycle: lifecycle.Metadata{ + Version: semver.MustParse("0.0.0"), + }, + } + + err := processMetadata(&md) + h.AssertNil(t, err) + + expected := Metadata{ + Buildpacks: []BuildpackMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + {ID: "bp.id.1", Version: "4.5.6"}, + }, + Groups: []GroupMetadata{ + { + Buildpacks: []BuildpackRefMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + }, + }, + }, + Lifecycle: lifecycle.Metadata{ + Version: semver.MustParse("0.0.0"), + }, + } + + h.AssertEq(t, md, expected) + }) + }) + + when("order contains 'latest'", func() { + it("should error", func() { + md := Metadata{ + Buildpacks: []BuildpackMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + {ID: "bp.id.1", Version: "4.5.6"}, + }, + Groups: []GroupMetadata{ + { + Buildpacks: []BuildpackRefMetadata{ + {ID: "bp.id.1", Version: ""}, + }, + }, + }, + } + + err := processMetadata(&md) + h.AssertError(t, err, "multiple versions of 'bp.id.1' - must specify an explicit version") + }) + }) + }) + + when("the buildpack has no versions", func() { + when("order has unset version", func() { + it("should error", func() { + md := Metadata{ + Buildpacks: []BuildpackMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + }, + Groups: []GroupMetadata{ + { + Buildpacks: []BuildpackRefMetadata{ + {ID: "bp.id.no-exists", Version: ""}, + }, + }, + }, + } + + err := processMetadata(&md) + h.AssertError(t, err, "no versions of buildpack 'bp.id.no-exists' were found on the builder") + }) + }) + + when("order does not have unset version", func() { + it("should error", func() { + md := Metadata{ + Buildpacks: []BuildpackMetadata{ + {ID: "bp.id.1", Version: "1.2.3"}, + }, + Groups: []GroupMetadata{ + { + Buildpacks: []BuildpackRefMetadata{ + {ID: "bp.id.no-exists", Version: "4.5.6"}, + }, + }, + }, + } + + err := processMetadata(&md) + h.AssertError(t, err, "no versions of buildpack 'bp.id.no-exists' were found on the builder") + }) + }) + }) + }) +} diff --git a/buildpack/buildpack.go b/buildpack/buildpack.go index 91503ba98..fef299e45 100644 --- a/buildpack/buildpack.go +++ b/buildpack/buildpack.go @@ -9,10 +9,21 @@ type BuildpackTOML struct { type Buildpack struct { ID string - Latest bool Path string Version string Stacks []Stack + Order Order +} + +type Order []Group + +type Group struct { + Group []BuildpackRef +} + +type BuildpackRef struct { + ID string + Version string } type Stack struct { diff --git a/buildpack/fetcher.go b/buildpack/fetcher.go index 281cf75b4..a6c35665b 100644 --- a/buildpack/fetcher.go +++ b/buildpack/fetcher.go @@ -21,6 +21,7 @@ type buildpackTOML struct { ID string `toml:"id"` Version string `toml:"version"` } `toml:"buildpack"` + Order Order `toml:"order"` Stacks []Stack `toml:"stacks"` } @@ -47,6 +48,7 @@ func (f *Fetcher) FetchBuildpack(uri string) (Buildpack, error) { Path: downloadedPath, ID: data.Buildpack.ID, Version: data.Buildpack.Version, + Order: data.Order, Stacks: data.Stacks, }, err } diff --git a/buildpack/fetcher_test.go b/buildpack/fetcher_test.go index 62fc1d09c..e56d988b3 100644 --- a/buildpack/fetcher_test.go +++ b/buildpack/fetcher_test.go @@ -51,7 +51,9 @@ func testBuildpackFetcher(t *testing.T, when spec.G, it spec.S) { out, err := subject.FetchBuildpack(downloadPath) h.AssertNil(t, err) h.AssertEq(t, out.ID, "bp.one") - h.AssertEq(t, out.Version, "some-buildpack-version") + h.AssertEq(t, out.Version, "bp.one.version") + h.AssertEq(t, out.Order[0].Group[0].ID, "bp.nested") + h.AssertEq(t, out.Order[0].Group[0].Version, "bp.nested.version") h.AssertEq(t, out.Stacks[0].ID, "some.stack.id") h.AssertEq(t, out.Stacks[1].ID, "other.stack.id") h.AssertNotEq(t, out.Path, "") @@ -69,7 +71,9 @@ func testBuildpackFetcher(t *testing.T, when spec.G, it spec.S) { out, err := subject.FetchBuildpack(downloadPath) h.AssertNil(t, err) h.AssertEq(t, out.ID, "bp.one") - h.AssertEq(t, out.Version, "some-buildpack-version") + h.AssertEq(t, out.Version, "bp.one.version") + h.AssertEq(t, out.Order[0].Group[0].ID, "bp.nested") + h.AssertEq(t, out.Order[0].Group[0].Version, "bp.nested.version") h.AssertEq(t, out.Stacks[0].ID, "some.stack.id") h.AssertEq(t, out.Stacks[1].ID, "other.stack.id") h.AssertNotEq(t, out.Path, "") diff --git a/buildpack/testdata/buildpack/buildpack.toml b/buildpack/testdata/buildpack/buildpack.toml index 226902d9a..318df3248 100644 --- a/buildpack/testdata/buildpack/buildpack.toml +++ b/buildpack/testdata/buildpack/buildpack.toml @@ -1,6 +1,13 @@ [buildpack] -id = "bp.one" -version = "some-buildpack-version" + id = "bp.one" + version = "bp.one.version" + +# technically, this is invalid as a buildpack cannot have both an order and stacks +# however, we're just testing that all the correct fields are parsed +[[order]] +[[order.group]] + id = "bp.nested" + version = "bp.nested.version" [[stacks]] id = "some.stack.id" diff --git a/commands/inspect_builder.go b/commands/inspect_builder.go index 2d0c3316e..86e9ddd3f 100644 --- a/commands/inspect_builder.go +++ b/commands/inspect_builder.go @@ -53,6 +53,7 @@ func InspectBuilder(logger logging.Logger, cfg config.Config, client PackClient) return cmd } +// TODO: If necessary, how do we present buildpack order (nested order)? func inspectBuilderOutput(logger logging.Logger, client PackClient, imageName string, local bool, cfg config.Config) { info, err := client.InspectBuilder(imageName, local) if err != nil { @@ -115,12 +116,12 @@ func inspectBuilderOutput(logger logging.Logger, client PackClient, imageName st func logBuildpacksInfo(logger logging.Logger, info *pack.BuilderInfo) { buf := &bytes.Buffer{} tabWriter := new(tabwriter.Writer).Init(buf, 0, 0, 8, ' ', 0) - if _, err := fmt.Fprint(tabWriter, "\n ID\tVERSION\tLATEST"); err != nil { + if _, err := fmt.Fprint(tabWriter, "\n ID\tVERSION"); err != nil { logger.Error(err.Error()) } for _, bp := range info.Buildpacks { - if _, err := fmt.Fprint(tabWriter, fmt.Sprintf("\n %s\t%s\t%t", bp.ID, bp.Version, bp.Latest)); err != nil { + if _, err := fmt.Fprint(tabWriter, fmt.Sprintf("\n %s\t%s", bp.ID, bp.Version)); err != nil { logger.Error(err.Error()) } } diff --git a/commands/inspect_builder_test.go b/commands/inspect_builder_test.go index 1e74e1efb..6806d4182 100644 --- a/commands/inspect_builder_test.go +++ b/commands/inspect_builder_test.go @@ -146,7 +146,7 @@ ERROR: some local error RunImageMirrors: []string{"first/default", "second/default"}, Buildpacks: buildpacks, Groups: []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{ + {Buildpacks: []builder.BuildpackRefMetadata{ {ID: "test.bp.one", Version: "1.0.0", Optional: true}, {ID: "test.bp.two", Version: "2.0.0"}, }}}, @@ -159,8 +159,8 @@ ERROR: some local error RunImageMirrors: []string{"first/local-default", "second/local-default"}, Buildpacks: buildpacks, Groups: []builder.GroupMetadata{ - {Buildpacks: []builder.GroupBuildpack{{ID: "test.bp.one", Version: "1.0.0"}}}, - {Buildpacks: []builder.GroupBuildpack{{ID: "test.bp.two", Version: "2.0.0", Optional: true}}}, + {Buildpacks: []builder.BuildpackRefMetadata{{ID: "test.bp.one", Version: "1.0.0"}}}, + {Buildpacks: []builder.BuildpackRefMetadata{{ID: "test.bp.two", Version: "2.0.0", Optional: true}}}, }, LifecycleVersion: "4.5.6", } @@ -195,9 +195,9 @@ Run Images: second/default Buildpacks: - ID VERSION LATEST - test.bp.one 1.0.0 true - test.bp.two 2.0.0 false + ID VERSION + test.bp.one 1.0.0 + test.bp.two 2.0.0 Detection Order: Group #1: @@ -223,9 +223,9 @@ Run Images: second/local-default Buildpacks: - ID VERSION LATEST - test.bp.one 1.0.0 true - test.bp.two 2.0.0 false + ID VERSION + test.bp.one 1.0.0 + test.bp.two 2.0.0 Detection Order: Group #1: @@ -264,9 +264,9 @@ Run Images: second/default Buildpacks: - ID VERSION LATEST - test.bp.one 1.0.0 true - test.bp.two 2.0.0 false + ID VERSION + test.bp.one 1.0.0 + test.bp.two 2.0.0 Detection Order: Group #1: @@ -292,9 +292,9 @@ Run Images: second/local-default Buildpacks: - ID VERSION LATEST - test.bp.one 1.0.0 true - test.bp.two 2.0.0 false + ID VERSION + test.bp.one 1.0.0 + test.bp.two 2.0.0 Detection Order: Group #1: diff --git a/create_builder.go b/create_builder.go index e9f2fef38..90559a33a 100644 --- a/create_builder.go +++ b/create_builder.go @@ -62,7 +62,6 @@ func (c *Client) CreateBuilder(ctx context.Context, opts CreateBuilderOptions) e if err != nil { return err } - fetchedBuildpack.Latest = b.Latest if b.ID != "" && fetchedBuildpack.ID != b.ID { return fmt.Errorf("buildpack from URI '%s' has ID '%s' which does not match ID '%s' from builder config", b.URI, fetchedBuildpack.ID, b.ID) } @@ -71,15 +70,12 @@ func (c *Client) CreateBuilder(ctx context.Context, opts CreateBuilderOptions) e return fmt.Errorf("buildpack from URI '%s' has version '%s' which does not match version '%s' from builder config", b.URI, fetchedBuildpack.Version, b.Version) } - if err := builderImage.AddBuildpack(fetchedBuildpack); err != nil { - return err - } + builderImage.AddBuildpack(fetchedBuildpack) } - if err := builderImage.SetOrder(opts.BuilderConfig.Groups); err != nil { - return errors.Wrap(err, "builder config has invalid groups") - } + groupMetadata := opts.BuilderConfig.Order.ToMetadata() + builderImage.SetOrder(groupMetadata) builderImage.SetStackInfo(opts.BuilderConfig.Stack) lifecycleMd, err := c.lifecycleFetcher.Fetch(lifecycleVersion, opts.BuilderConfig.Lifecycle.URI) diff --git a/create_builder_test.go b/create_builder_test.go index 0e178d18c..0d437e721 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -73,7 +73,6 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { bp := buildpack.Buildpack{ ID: "bp.one", - Latest: true, Path: filepath.Join("testdata", "buildpack"), Version: "1.2.3", Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, @@ -103,11 +102,10 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { ID: "bp.one", Version: "1.2.3", URI: "https://example.fake/bp-one.tgz", - Latest: true, }, }, - Groups: []builder.GroupMetadata{{ - Buildpacks: []builder.GroupBuildpack{ + Order: []builder.GroupConfig{{ + Group: []builder.BuildpackRefConfig{ {ID: "bp.one", Version: "1.2.3", Optional: false}, }}, }, @@ -229,7 +227,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { Latest: true, }}) h.AssertEq(t, builderImage.GetOrder(), []builder.GroupMetadata{{ - Buildpacks: []builder.GroupBuildpack{{ + Buildpacks: []builder.BuildpackRefMetadata{{ ID: "bp.one", Version: "1.2.3", Optional: false, diff --git a/inspect_builder_test.go b/inspect_builder_test.go index 0f0528801..35c4dd08c 100644 --- a/inspect_builder_test.go +++ b/inspect_builder_test.go @@ -138,7 +138,7 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { it("sets the groups", func() { builderInfo, err := subject.InspectBuilder("some/builder", useDaemon) h.AssertNil(t, err) - h.AssertEq(t, builderInfo.Groups[0].Buildpacks[0], builder.GroupBuildpack{ + h.AssertEq(t, builderInfo.Groups[0].Buildpacks[0], builder.BuildpackRefMetadata{ ID: "test.bp.one", Version: "1.0.0", }) diff --git a/internal/mocks/images.go b/internal/mocks/images.go index 6fec2776d..dfe93d026 100644 --- a/internal/mocks/images.go +++ b/internal/mocks/images.go @@ -4,9 +4,11 @@ import ( "encoding/json" "testing" + "github.com/Masterminds/semver" "github.com/buildpack/imgutil/fakes" "github.com/buildpack/pack/builder" + "github.com/buildpack/pack/lifecycle" h "github.com/buildpack/pack/testhelpers" ) @@ -17,13 +19,16 @@ func NewFakeBuilderImage(t *testing.T, name string, buildpacks []builder.Buildpa h.AssertNil(t, fakeBuilderImage.SetEnv("CNB_GROUP_ID", "4321")) metadata := builder.Metadata{ Buildpacks: buildpacks, - Groups: config.Groups, + Groups: config.Order.ToMetadata(), Stack: builder.StackMetadata{ RunImage: builder.RunImageMetadata{ Image: config.Stack.RunImage, Mirrors: config.Stack.RunImageMirrors, }, }, + Lifecycle: lifecycle.Metadata{ + Version: semver.MustParse(lifecycle.DefaultLifecycleVersion), + }, } label, err := json.Marshal(&metadata) h.AssertNil(t, err) diff --git a/testdata/builder.toml b/testdata/builder.toml index 5f6b2a700..93cde173e 100644 --- a/testdata/builder.toml +++ b/testdata/builder.toml @@ -11,16 +11,19 @@ id = "some/bp2" uri = "some-latest-path-2" latest = true -[[groups]] -buildpacks = [ - { id = "some.bp1", version = "1.2.3" }, - { id = "some/bp2", version = "1.2.4" }, -] +[[order]] +[[order.group]] + id = "some.bp1" + version = "1.2.3" -[[groups]] -buildpacks = [ - { id = "some.bp1", version = "1.2.3" }, -] +[[order.group]] + id = "some/bp2" + version = "1.2.4" + +[[order]] +[[order.group]] + id = "some.bp1" + version = "1.2.3" [stack] id = "com.example.stack" diff --git a/testhelpers/tar_assertions.go b/testhelpers/tar_assertions.go index 7e0ccbbf5..0127395bc 100644 --- a/testhelpers/tar_assertions.go +++ b/testhelpers/tar_assertions.go @@ -22,22 +22,11 @@ func AssertOnTarEntry(t *testing.T, tarFile, entryPath string, assertFns ...TarE func ContentEquals(expected string) TarEntryAssertion { return func(t *testing.T, header *tar.Header, contents []byte) { + t.Helper() AssertEq(t, string(contents), expected) } } -func SymlinksTo(expectedTarget string) TarEntryAssertion { - return func(t *testing.T, header *tar.Header, _ []byte) { - if header.Typeflag != tar.TypeSymlink { - t.Fatalf("path '%s' is not a symlink, type flag is '%c'", header.Name, header.Typeflag) - } - - if header.Linkname != expectedTarget { - t.Fatalf("symlink '%s' does not point to '%s', instead it points to '%s'", header.Name, expectedTarget, header.Linkname) - } - } -} - func HasOwnerAndGroup(expectedUID int, expectedGID int) TarEntryAssertion { return func(t *testing.T, header *tar.Header, _ []byte) { t.Helper() diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index 8f10bfc62..31dbee14f 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -369,6 +369,7 @@ func RequireDocker(t *testing.T) { } func SkipIf(t *testing.T, expression bool, reason string) { + t.Helper() if expression { t.Skip(reason) } From b64c4fbb201cf1633db788c45fbd5a731ef006a1 Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Mon, 12 Aug 2019 16:47:40 -0400 Subject: [PATCH 2/3] Warn on @latest syntax * Refactors builder data objects Signed-off-by: Emily Casey Signed-off-by: Javier Romero --- acceptance/acceptance_test.go | 18 +- .../testdata/mock_buildpacks/v1/builder.toml | 3 +- build.go | 26 ++- build/phase_test.go | 2 +- build_test.go | 109 +++++++--- builder/builder.go | 38 ++-- builder/builder_test.go | 196 +++++++++++------- builder/compat.go | 38 ++-- builder/compat_test.go | 46 ---- builder/config.go | 40 +--- builder/metadata.go | 48 +---- builder/metadata_test.go | 59 +++--- buildpack/buildpack.go | 17 +- buildpack/fetcher.go | 12 +- commands/inspect_builder_test.go | 21 +- create_builder.go | 4 +- create_builder_test.go | 37 ++-- inspect_builder.go | 2 +- inspect_builder_test.go | 17 +- internal/mocks/images.go | 2 +- 20 files changed, 369 insertions(+), 366 deletions(-) delete mode 100644 builder/compat_test.go diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 42f5f585b..6a2c8b20a 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -276,18 +276,10 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { cmd := packCmd( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), - - // tgz not in builder - "--buildpack", notBuilderTgz, - - // with version - "--buildpack", "simple/layers@simple-layers-version", - - // without version - "--buildpack", "noop.buildpack", - - // latest (for backwards compatibility) - "--buildpack", "read/env@latest", + "--buildpack", notBuilderTgz, // tgz not in builder + "--buildpack", "simple/layers@simple-layers-version", // with version + "--buildpack", "noop.buildpack", // without version + "--buildpack", "read/env@latest", // latest (for backwards compatibility) "--env", "DETECT_ENV_BUILDPACK=true", ) output := h.Run(t, cmd) @@ -826,7 +818,7 @@ func createBuilder(t *testing.T, runImageMirror string) string { t.Log("Using buildpacks from: ", testBuildpacksDir()) h.RecursiveCopy(t, testBuildpacksDir(), tmpDir) - // COPY builder.toml + // AMEND builder.toml builderConfigFile, err := os.OpenFile(filepath.Join(tmpDir, "builder.toml"), os.O_RDWR|os.O_APPEND, 0666) h.AssertNil(t, err) diff --git a/acceptance/testdata/mock_buildpacks/v1/builder.toml b/acceptance/testdata/mock_buildpacks/v1/builder.toml index 907342f4f..1abe1c3b3 100644 --- a/acceptance/testdata/mock_buildpacks/v1/builder.toml +++ b/acceptance/testdata/mock_buildpacks/v1/builder.toml @@ -9,8 +9,7 @@ uri = "read-env-buildpack.tgz" [[buildpacks]] - id = "noop.buildpack" - version = "noop.buildpack.version" + # intentionally missing id/version as they are optional uri = "noop-buildpack.tgz" [[order]] diff --git a/build.go b/build.go index abffd3508..b148bd656 100644 --- a/build.go +++ b/build.go @@ -208,24 +208,31 @@ func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig { } } -func (c *Client) processBuildpacks(buildpacks []string) ([]buildpack.Buildpack, builder.GroupMetadata, error) { - group := builder.GroupMetadata{Buildpacks: []builder.BuildpackRefMetadata{}} +func (c *Client) processBuildpacks(buildpacks []string) ([]buildpack.Buildpack, builder.OrderEntry, error) { + group := builder.OrderEntry{Group: []builder.BuildpackRef{}} var bps []buildpack.Buildpack for _, bp := range buildpacks { if isBuildpackId(bp) { id, version := c.parseBuildpack(bp) - group.Buildpacks = append(group.Buildpacks, builder.BuildpackRefMetadata{ID: id, Version: version}) + group.Group = append(group.Group, builder.BuildpackRef{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: id, + Version: version, + }, + }) } else { if runtime.GOOS == "windows" && filepath.Ext(bp) != ".tgz" { - return nil, builder.GroupMetadata{}, fmt.Errorf("buildpack %s: Windows only supports .tgz-based buildpacks", style.Symbol(bp)) + return nil, builder.OrderEntry{}, fmt.Errorf("buildpack %s: Windows only supports .tgz-based buildpacks", style.Symbol(bp)) } c.logger.Debugf("fetching buildpack from %s", style.Symbol(bp)) fetchedBP, err := c.buildpackFetcher.FetchBuildpack(bp) if err != nil { - return nil, builder.GroupMetadata{}, errors.Wrapf(err, "failed to fetch buildpack from URI '%s'", bp) + return nil, builder.OrderEntry{}, errors.Wrapf(err, "failed to fetch buildpack from URI '%s'", bp) } bps = append(bps, fetchedBP) - group.Buildpacks = append(group.Buildpacks, builder.BuildpackRefMetadata{ID: fetchedBP.ID, Version: fetchedBP.Version}) + group.Group = append(group.Group, builder.BuildpackRef{ + BuildpackInfo: fetchedBP.BuildpackInfo, + }) } } return bps, group, nil @@ -249,6 +256,7 @@ func (c *Client) parseBuildpack(bp string) (string, string) { parts := strings.Split(bp, "@") if len(parts) == 2 { if parts[1] == "latest" { + c.logger.Info(style.Tip("WARNING: ") + "@latest syntax is deprecated, will not work in future releases") return parts[0], "" } @@ -258,7 +266,7 @@ func (c *Client) parseBuildpack(bp string) (string, string) { return parts[0], "" } -func (c *Client) createEphemeralBuilder(rawBuilderImage imgutil.Image, env map[string]string, group builder.GroupMetadata, buildpacks []buildpack.Buildpack) (*builder.Builder, error) { +func (c *Client) createEphemeralBuilder(rawBuilderImage imgutil.Image, env map[string]string, group builder.OrderEntry, buildpacks []buildpack.Buildpack) (*builder.Builder, error) { origBuilderName := rawBuilderImage.Name() bldr, err := builder.New(rawBuilderImage, fmt.Sprintf("pack.local/builder/%x:latest", randString(10))) if err != nil { @@ -269,9 +277,9 @@ func (c *Client) createEphemeralBuilder(rawBuilderImage imgutil.Image, env map[s c.logger.Debugf("adding buildpack %s version %s to builder", style.Symbol(bp.ID), style.Symbol(bp.Version)) bldr.AddBuildpack(bp) } - if len(group.Buildpacks) > 0 { + if len(group.Group) > 0 { c.logger.Debug("setting custom order") - bldr.SetOrder(builder.OrderMetadata{group}) + bldr.SetOrder([]builder.OrderEntry{group}) } if err := bldr.Save(); err != nil { return nil, err diff --git a/build/phase_test.go b/build/phase_test.go index 95c0b29b0..d7201f938 100644 --- a/build/phase_test.go +++ b/build/phase_test.go @@ -129,7 +129,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) { h.AssertContains(t, outBuf.String(), "failed to read file") }) - when.Pend("is posix", func() { + when("is posix", func() { it.Before(func() { h.SkipIf(t, runtime.GOOS == "windows", "Skipping on windows") }) diff --git a/build_test.go b/build_test.go index b3b82eb62..4ff3ba63f 100644 --- a/build_test.go +++ b/build_test.go @@ -2,6 +2,7 @@ package pack import ( "archive/tar" + "bytes" "context" "fmt" "io" @@ -45,6 +46,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { fakeMirror1 *fakes.Image fakeMirror2 *fakes.Image tmpDir string + outBuf bytes.Buffer ) it.Before(func() { fakeImageFetcher = mocks.NewFakeImageFetcher() @@ -55,7 +57,10 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { defaultBuilderImage = mocks.NewFakeBuilderImage(t, builderName, []builder.BuildpackMetadata{ - {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, + { + BuildpackInfo: buildpack.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}, + Latest: true, + }, }, builder.Config{ Stack: builder.StackConfig{ @@ -88,7 +93,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { docker, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38")) h.AssertNil(t, err) - logger := mocks.NewMockLogger(ioutil.Discard) + logger := mocks.NewMockLogger(&outBuf) subject = &Client{ logger: logger, @@ -488,8 +493,13 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) - h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{{ID: "buildpack.id", Version: "buildpack.version"}}}, + h.AssertEq(t, bldr.GetOrder(), []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack.id", + Version: "buildpack.version", + }}, + }}, }) }) @@ -504,12 +514,40 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) - h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{{ID: "buildpack.id", Version: "buildpack.version"}}}, + h.AssertEq(t, bldr.GetOrder(), []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack.id", + Version: "buildpack.version", + }}, + }}, }) }) }) + when("latest is explicitly provided", func() { + it("resolves version and prints a warning", func() { + h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ + Image: "some/app", + Builder: builderName, + ClearCache: true, + Buildpacks: []string{"buildpack.id@latest"}, + })) + h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) + bldr, err := builder.GetBuilder(defaultBuilderImage) + h.AssertNil(t, err) + h.AssertEq(t, bldr.GetOrder(), []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack.id", + Version: "buildpack.version", + }}, + }}, + }) + h.AssertContains(t, outBuf.String(), "WARNING: @latest syntax is deprecated, will not work in future releases") + }) + }) + it("ensures buildpacks exist on builder", func() { h.AssertError(t, subject.Build(context.TODO(), BuildOptions{ Image: "some/app", @@ -566,15 +604,27 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) - h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{ - {ID: "buildpack.id", Version: "buildpack.version"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + h.AssertEq(t, bldr.GetOrder(), []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, }}, }) h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ - {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version", Latest: true}, + { + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack.id", + Version: "buildpack.version", + }, + Latest: true, + }, + { + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-other-buildpack-id", + Version: "some-other-buildpack-version", + }, + Latest: true, + }, }) }) }) @@ -600,17 +650,20 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) - h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{ - {ID: "buildpack.id", Version: "buildpack.version"}, - {ID: "some-buildpack-id", Version: "some-buildpack-version"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + buildpackInfo := buildpack.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"} + dirBuildpackInfo := buildpack.BuildpackInfo{ID: "some-buildpack-id", Version: "some-buildpack-version"} + tgzBuildpackInfo := buildpack.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"} + h.AssertEq(t, bldr.GetOrder(), []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{ + {BuildpackInfo: buildpackInfo}, + {BuildpackInfo: dirBuildpackInfo}, + {BuildpackInfo: tgzBuildpackInfo}, }}, }) h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ - {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, - {ID: "some-buildpack-id", Version: "some-buildpack-version", Latest: true}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version", Latest: true}, + {BuildpackInfo: buildpackInfo, Latest: true}, + {BuildpackInfo: dirBuildpackInfo, Latest: true}, + {BuildpackInfo: tgzBuildpackInfo, Latest: true}, }) }) }) @@ -646,17 +699,17 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, fakeLifecycle.Opts.Builder.Name(), defaultBuilderImage.Name()) bldr, err := builder.GetBuilder(defaultBuilderImage) h.AssertNil(t, err) - h.AssertEq(t, bldr.GetOrder(), []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{ - {ID: "buildpack.id", Version: "buildpack.version"}, - {ID: "some-buildpack-id", Version: "some-buildpack-version"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + h.AssertEq(t, bldr.GetOrder(), []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "some-buildpack-id", Version: "some-buildpack-version"}}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}}, }}, }) h.AssertEq(t, bldr.GetBuildpacks(), []builder.BuildpackMetadata{ - {ID: "buildpack.id", Version: "buildpack.version", Latest: true}, - {ID: "some-buildpack-id", Version: "some-buildpack-version", Latest: true}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version", Latest: true}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "buildpack.id", Version: "buildpack.version"}, Latest: true}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "some-buildpack-id", Version: "some-buildpack-version"}, Latest: true}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, Latest: true}, }) }) }) diff --git a/builder/builder.go b/builder/builder.go index 43d9173a8..970c819b8 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -46,19 +46,22 @@ type Builder struct { UID, GID int StackID string replaceOrder bool + order Order } type orderTOML struct { - Order OrderConfig `toml:"order"` + Order Order `toml:"order"` } -type stackTOML struct { - RunImage stackTOMLRunImage `toml:"run-image"` +type Order []OrderEntry + +type OrderEntry struct { + Group []BuildpackRef `toml:"group"` } -type stackTOMLRunImage struct { - Image string `toml:"image"` - Mirrors []string `toml:"mirrors"` +type BuildpackRef struct { + buildpack.BuildpackInfo + Optional bool `toml:"optional,omitempty"` } func GetBuilder(img imgutil.Image) (*Builder, error) { @@ -107,7 +110,8 @@ func (b *Builder) GetBuildpacks() []BuildpackMetadata { return b.metadata.Buildpacks } -func (b *Builder) GetOrder() []GroupMetadata { +// TODO: change to v2 order type when order label is added +func (b *Builder) GetOrder() []V1Group { return b.metadata.Groups } @@ -158,7 +162,9 @@ func New(img imgutil.Image, name string) (*Builder, error) { func (b *Builder) AddBuildpack(bp buildpack.Buildpack) { b.buildpacks = append(b.buildpacks, bp) - b.metadata.Buildpacks = append(b.metadata.Buildpacks, BuildpackMetadata{ID: bp.ID, Version: bp.Version}) + b.metadata.Buildpacks = append(b.metadata.Buildpacks, BuildpackMetadata{ + BuildpackInfo: bp.BuildpackInfo, + }) } func (b *Builder) SetLifecycle(md lifecycle.Metadata) error { @@ -171,8 +177,9 @@ func (b *Builder) SetEnv(env map[string]string) { b.env = env } -func (b *Builder) SetOrder(order OrderMetadata) { - b.metadata.Groups = order +func (b *Builder) SetOrder(order Order) { + b.metadata.Groups = order.ToV1Order() + b.order = order b.replaceOrder = true } @@ -401,9 +408,9 @@ func (b *Builder) orderLayer(dest string) (string, error) { var tomlData interface{} if lifecycleVersion != nil && lifecycleVersion.LessThan(semver.MustParse("0.4.0")) { - tomlData = v1OrderTOMLFromOrderTOML(orderTOML{Order: b.metadata.Groups.ToConfig()}) + tomlData = v1OrderTOML{Groups: b.metadata.Groups} } else { - tomlData = orderTOML{Order: b.metadata.Groups.ToConfig()} + tomlData = orderTOML{Order: b.order} } err := toml.NewEncoder(buf).Encode(tomlData) @@ -422,12 +429,7 @@ func (b *Builder) orderLayer(dest string) (string, error) { func (b *Builder) stackLayer(dest string) (string, error) { buf := &bytes.Buffer{} - err := toml.NewEncoder(buf).Encode(stackTOML{ - stackTOMLRunImage{ - Image: b.metadata.Stack.RunImage.Image, - Mirrors: b.metadata.Stack.RunImage.Mirrors, - }, - }) + err := toml.NewEncoder(buf).Encode(b.metadata.Stack) if err != nil { return "", errors.Wrapf(err, "failed to marshal stack.toml") } diff --git a/builder/builder_test.go b/builder/builder_test.go index 683366be8..c76d95213 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -204,11 +204,10 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("order is invalid", func() { it("produces an error", func() { - subject.SetOrder(builder.OrderMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{ - {ID: "missing-buildpack-id", Version: "missing-buildpack-version"}, - }}, - }) + subject.SetOrder(builder.Order{{ + Group: []builder.BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "missing-buildpack-id", Version: "missing-buildpack-version"}}}, + }}) err := subject.Save() @@ -220,9 +219,11 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack is missing both order and stack", func() { it("returns an error", func() { subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), }) err := subject.Save() @@ -234,12 +235,14 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack has both order and stack", func() { it("returns an error", func() { subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), Order: buildpack.Order{ { - Group: []buildpack.BuildpackRef{ + Group: []buildpack.BuildpackInfo{ {ID: "some-buildpack-id", Version: "some-buildpack-version"}, }, }, @@ -259,12 +262,14 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack by id does not exist", func() { it("returns an error", func() { subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), Order: buildpack.Order{ { - Group: []buildpack.BuildpackRef{ + Group: []buildpack.BuildpackInfo{ {ID: "missing-buildpack-id", Version: "missing-buildpack-version"}, }, }, @@ -280,12 +285,14 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack version does not exist", func() { it("returns an error", func() { subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), Order: buildpack.Order{ { - Group: []buildpack.BuildpackRef{ + Group: []buildpack.BuildpackInfo{ {ID: "some-buildpack-id", Version: "missing-buildpack-version"}, }, }, @@ -302,10 +309,12 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack stack id does not match", func() { it("returns an error", func() { subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "other.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "other.stack.id"}}, }) err := subject.Save() @@ -402,12 +411,14 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { buildpackTgz = h.CreateTgz(t, filepath.Join("testdata", "buildpack"), "./", 0644) subject.AddBuildpack(buildpack.Buildpack{ - ID: "buildpack-1-id", - Version: "buildpack-1-version", - Path: buildpackTgz, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack-1-id", + Version: "buildpack-1-version", + }, + Path: buildpackTgz, Order: buildpack.Order{ { - Group: []buildpack.BuildpackRef{ + Group: []buildpack.BuildpackInfo{ { ID: "buildpack-2-id", Version: "buildpack-2-version", @@ -418,25 +429,40 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { }) subject.AddBuildpack(buildpack.Buildpack{ - ID: "buildpack-2-id", - Version: "buildpack-2-version", - Path: buildpackTgz, - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack-2-id", + Version: "buildpack-2-version", + }, + Path: buildpackTgz, + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) subject.AddBuildpack(buildpack.Buildpack{ - ID: "buildpack-3-id", - Version: "buildpack-3-version", - Path: buildpackTgz, - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack-2-id", + Version: "buildpack-2-other-version", + }, + Path: buildpackTgz, + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + }) + + subject.AddBuildpack(buildpack.Buildpack{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack-3-id", + Version: "buildpack-3-version", + }, + Path: buildpackTgz, + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) if runtime.GOOS != "windows" { subject.AddBuildpack(buildpack.Buildpack{ - ID: "dir-buildpack-id", - Version: "dir-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "dir-buildpack-id", + Version: "dir-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) } @@ -479,6 +505,18 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { h.HasFileMode(0644), ) + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/buildpack-2-id/buildpack-2-other-version") + h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-2-id/buildpack-2-other-version", + h.IsDirectory(), + ) + + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-2-id/buildpack-2-other-version/buildpack-file", + h.ContentEquals("buildpack-contents"), + h.HasOwnerAndGroup(1234, 4321), + h.HasFileMode(0644), + ) + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/buildpack-3-id/buildpack-3-version") h.AssertNil(t, err) h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-3-id/buildpack-3-version", @@ -513,9 +551,9 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { var metadata builder.Metadata h.AssertNil(t, json.Unmarshal([]byte(label), &metadata)) if runtime.GOOS == "windows" { - h.AssertEq(t, len(metadata.Buildpacks), 3) - } else { h.AssertEq(t, len(metadata.Buildpacks), 4) + } else { + h.AssertEq(t, len(metadata.Buildpacks), 5) } h.AssertEq(t, metadata.Buildpacks[0].ID, "buildpack-1-id") @@ -524,16 +562,20 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, metadata.Buildpacks[1].ID, "buildpack-2-id") h.AssertEq(t, metadata.Buildpacks[1].Version, "buildpack-2-version") - h.AssertEq(t, metadata.Buildpacks[1].Latest, true) + h.AssertEq(t, metadata.Buildpacks[1].Latest, false) - h.AssertEq(t, metadata.Buildpacks[2].ID, "buildpack-3-id") - h.AssertEq(t, metadata.Buildpacks[2].Version, "buildpack-3-version") - h.AssertEq(t, metadata.Buildpacks[2].Latest, true) + h.AssertEq(t, metadata.Buildpacks[2].ID, "buildpack-2-id") + h.AssertEq(t, metadata.Buildpacks[2].Version, "buildpack-2-other-version") + h.AssertEq(t, metadata.Buildpacks[2].Latest, false) + + h.AssertEq(t, metadata.Buildpacks[3].ID, "buildpack-3-id") + h.AssertEq(t, metadata.Buildpacks[3].Version, "buildpack-3-version") + h.AssertEq(t, metadata.Buildpacks[3].Latest, true) if runtime.GOOS != "windows" { - h.AssertEq(t, metadata.Buildpacks[3].ID, "dir-buildpack-id") - h.AssertEq(t, metadata.Buildpacks[3].Version, "dir-buildpack-version") - h.AssertEq(t, metadata.Buildpacks[3].Latest, true) + h.AssertEq(t, metadata.Buildpacks[4].ID, "dir-buildpack-id") + h.AssertEq(t, metadata.Buildpacks[4].Version, "dir-buildpack-version") + h.AssertEq(t, metadata.Buildpacks[4].Latest, true) } }) @@ -549,10 +591,12 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) h.AssertNil(t, subject.Save()) h.AssertEq(t, baseImage.IsSaved(), true) @@ -585,32 +629,42 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("the buildpacks exist in the image", func() { it.Before(func() { subject.AddBuildpack(buildpack.Buildpack{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) subject.AddBuildpack(buildpack.Buildpack{ - ID: "optional-buildpack-id", - Version: "older-optional-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "optional-buildpack-id", + Version: "older-optional-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) subject.AddBuildpack(buildpack.Buildpack{ - ID: "optional-buildpack-id", - Version: "optional-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "optional-buildpack-id", + Version: "optional-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, }) - subject.SetOrder(builder.OrderMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{ + subject.SetOrder(builder.Order{ + {Group: []builder.BuildpackRef{ { - ID: "some-buildpack-id", - Version: "some-buildpack-version", + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, }, { - ID: "optional-buildpack-id", - Version: "optional-buildpack-version", + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "optional-buildpack-id", + Version: "optional-buildpack-version", + }, Optional: true, }, }}, diff --git a/builder/compat.go b/builder/compat.go index 8e94a1cb1..69c7c3227 100644 --- a/builder/compat.go +++ b/builder/compat.go @@ -1,37 +1,27 @@ package builder -type v1OrderTOML struct { - Groups []v1Group `toml:"groups"` -} +type V1Order []V1Group -type v1Group struct { - Buildpacks []v1BuildpackRef `toml:"buildpacks"` +type V1Group struct { + Buildpacks []BuildpackRef `toml:"buildpacks" json:"buildpacks"` } -type v1BuildpackRef struct { - ID string `toml:"id"` - Version string `toml:"version"` - Optional bool `toml:"optional,omitempty"` +type v1OrderTOML struct { + Groups []V1Group `toml:"groups" json:"groups"` } -func v1OrderTOMLFromOrderTOML(order orderTOML) v1OrderTOML { - var groups []v1Group - for _, g := range order.Order { - var bps []v1BuildpackRef - for _, b := range g.Group { - bps = append(bps, v1BuildpackRef{ - ID: b.ID, - Version: b.Version, - Optional: b.Optional, - }) +func (o Order) ToV1Order() V1Order { + var order V1Order + for _, gp := range o { + var buildpacks []BuildpackRef + for _, bp := range gp.Group { + buildpacks = append(buildpacks, bp) } - groups = append(groups, v1Group{ - Buildpacks: bps, + order = append(order, V1Group{ + Buildpacks: buildpacks, }) } - return v1OrderTOML{ - Groups: groups, - } + return order } diff --git a/builder/compat_test.go b/builder/compat_test.go deleted file mode 100644 index 4b90b091f..000000000 --- a/builder/compat_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package builder - -import ( - "testing" - - "github.com/fatih/color" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - - h "github.com/buildpack/pack/testhelpers" -) - -func TestCompat(t *testing.T) { - color.NoColor = true - spec.Run(t, "Compat", testCompat, spec.Parallel(), spec.Report(report.Terminal{})) -} - -func testCompat(t *testing.T, when spec.G, it spec.S) { - when("#v1OrderTOMLFromOrderTOML", func() { - it("converts", func() { - newToml := orderTOML{ - Order: []GroupConfig{ - { - Group: []BuildpackRefConfig{ - {ID: "buildpack.id.1", Version: "1.2.3", Optional: false}, - {ID: "buildpack.id.2", Version: "4.5.6", Optional: true}, - }, - }, - }, - } - - result := v1OrderTOMLFromOrderTOML(newToml) - - h.AssertEq(t, result, v1OrderTOML{ - Groups: []v1Group{ - { - Buildpacks: []v1BuildpackRef{ - {ID: "buildpack.id.1", Version: "1.2.3", Optional: false}, - {ID: "buildpack.id.2", Version: "4.5.6", Optional: true}, - }, - }, - }, - }) - }) - }) -} diff --git a/builder/config.go b/builder/config.go index cd669e7df..847d50e9b 100644 --- a/builder/config.go +++ b/builder/config.go @@ -9,53 +9,21 @@ import ( "github.com/BurntSushi/toml" "github.com/pkg/errors" + "github.com/buildpack/pack/buildpack" "github.com/buildpack/pack/internal/paths" ) type Config struct { Description string `toml:"description"` Buildpacks []BuildpackConfig `toml:"buildpacks"` - Order OrderConfig `toml:"order"` + Order Order `toml:"order"` Stack StackConfig `toml:"stack"` Lifecycle LifecycleConfig `toml:"lifecycle"` } -type OrderConfig []GroupConfig - -func (o OrderConfig) ToMetadata() OrderMetadata { - var order OrderMetadata - for _, gp := range o { - var buildpacks []BuildpackRefMetadata - for _, bp := range gp.Group { - buildpacks = append(buildpacks, BuildpackRefMetadata{ - ID: bp.ID, - Version: bp.Version, - Optional: bp.Optional, - }) - } - - order = append(order, GroupMetadata{ - Buildpacks: buildpacks, - }) - } - - return order -} - -type GroupConfig struct { - Group []BuildpackRefConfig `toml:"group"` -} - -type BuildpackRefConfig struct { - ID string `toml:"id"` - Version string `toml:"version"` - Optional bool `toml:"optional,omitempty"` -} - type BuildpackConfig struct { - ID string `toml:"id"` - Version string `toml:"version"` - URI string `toml:"uri"` + buildpack.BuildpackInfo + URI string `toml:"uri"` } type StackConfig struct { diff --git a/builder/metadata.go b/builder/metadata.go index fc9a79a8f..d4328aecf 100644 --- a/builder/metadata.go +++ b/builder/metadata.go @@ -3,6 +3,8 @@ package builder import ( "fmt" + "github.com/buildpack/pack/buildpack" + "github.com/buildpack/pack/lifecycle" "github.com/buildpack/pack/style" ) @@ -12,57 +14,23 @@ const MetadataLabel = "io.buildpacks.builder.metadata" type Metadata struct { Description string `json:"description"` Buildpacks []BuildpackMetadata `json:"buildpacks"` - Groups OrderMetadata `json:"groups"` + Groups V1Order `json:"groups"` // deprecated Stack StackMetadata `json:"stack"` Lifecycle lifecycle.Metadata `json:"lifecycle"` } type BuildpackMetadata struct { - ID string `json:"id"` - Version string `json:"version"` - Latest bool `json:"latest"` // deprecated -} - -type OrderMetadata []GroupMetadata - -func (o OrderMetadata) ToConfig() OrderConfig { - var order OrderConfig - - for _, group := range o { - var buildpacks []BuildpackRefConfig - for _, bp := range group.Buildpacks { - buildpacks = append(buildpacks, BuildpackRefConfig{ - ID: bp.ID, - Version: bp.Version, - Optional: bp.Optional, - }) - } - - order = append(order, GroupConfig{ - Group: buildpacks, - }) - } - - return order -} - -type GroupMetadata struct { - Buildpacks []BuildpackRefMetadata `json:"buildpacks"` -} - -type BuildpackRefMetadata struct { - ID string `json:"id"` - Version string `json:"version"` - Optional bool `json:"optional,omitempty"` + buildpack.BuildpackInfo + Latest bool `json:"latest"` // deprecated } type StackMetadata struct { - RunImage RunImageMetadata `json:"runImage"` + RunImage RunImageMetadata `json:"runImage" toml:"run-image"` } type RunImageMetadata struct { - Image string `json:"image"` - Mirrors []string `json:"mirrors"` + Image string `json:"image" toml:"image"` + Mirrors []string `json:"mirrors" toml:"mirrors"` } func bpsWithID(metadata Metadata, id string) []BuildpackMetadata { diff --git a/builder/metadata_test.go b/builder/metadata_test.go index 4385cce5a..43832d59a 100644 --- a/builder/metadata_test.go +++ b/builder/metadata_test.go @@ -8,6 +8,7 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" + "github.com/buildpack/pack/buildpack" "github.com/buildpack/pack/lifecycle" h "github.com/buildpack/pack/testhelpers" ) @@ -23,12 +24,12 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { it("should resolve unset version", func() { md := Metadata{ Buildpacks: []BuildpackMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"}}, }, - Groups: []GroupMetadata{ + Groups: []V1Group{ { - Buildpacks: []BuildpackRefMetadata{ - {ID: "bp.id.1", Version: ""}, + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: ""}}, }, }, }, @@ -45,15 +46,17 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { when("the buildpack has multiple versions", func() { when("order contains specific version", func() { it("nothing should be updated", func() { + bpInfo1 := buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"} + bpInfo2 := buildpack.BuildpackInfo{ID: "bp.id.1", Version: "4.5.6"} md := Metadata{ Buildpacks: []BuildpackMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, - {ID: "bp.id.1", Version: "4.5.6"}, + {BuildpackInfo: bpInfo1}, + {BuildpackInfo: bpInfo2}, }, - Groups: []GroupMetadata{ + Groups: []V1Group{ { - Buildpacks: []BuildpackRefMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, + Buildpacks: []BuildpackRef{ + {BuildpackInfo: bpInfo1}, }, }, }, @@ -67,13 +70,13 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { expected := Metadata{ Buildpacks: []BuildpackMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, - {ID: "bp.id.1", Version: "4.5.6"}, + {BuildpackInfo: bpInfo1}, + {BuildpackInfo: bpInfo2}, }, - Groups: []GroupMetadata{ + Groups: []V1Group{ { - Buildpacks: []BuildpackRefMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, + Buildpacks: []BuildpackRef{ + {BuildpackInfo: bpInfo1}, }, }, }, @@ -88,15 +91,17 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { when("order contains 'latest'", func() { it("should error", func() { + bpInfo1 := buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"} + bpInfo2 := buildpack.BuildpackInfo{ID: "bp.id.1", Version: "4.5.6"} md := Metadata{ Buildpacks: []BuildpackMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, - {ID: "bp.id.1", Version: "4.5.6"}, + {BuildpackInfo: bpInfo1}, + {BuildpackInfo: bpInfo2}, }, - Groups: []GroupMetadata{ + Groups: []V1Group{ { - Buildpacks: []BuildpackRefMetadata{ - {ID: "bp.id.1", Version: ""}, + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: ""}}, }, }, }, @@ -113,12 +118,12 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { it("should error", func() { md := Metadata{ Buildpacks: []BuildpackMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"}}, }, - Groups: []GroupMetadata{ + Groups: []V1Group{ { - Buildpacks: []BuildpackRefMetadata{ - {ID: "bp.id.no-exists", Version: ""}, + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.no-exists", Version: ""}}, }, }, }, @@ -133,12 +138,12 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { it("should error", func() { md := Metadata{ Buildpacks: []BuildpackMetadata{ - {ID: "bp.id.1", Version: "1.2.3"}, + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"}}, }, - Groups: []GroupMetadata{ + Groups: []V1Group{ { - Buildpacks: []BuildpackRefMetadata{ - {ID: "bp.id.no-exists", Version: "4.5.6"}, + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.no-exists", Version: "4.5.6"}}, }, }, }, diff --git a/buildpack/buildpack.go b/buildpack/buildpack.go index fef299e45..fbde82fb5 100644 --- a/buildpack/buildpack.go +++ b/buildpack/buildpack.go @@ -8,22 +8,21 @@ type BuildpackTOML struct { } type Buildpack struct { - ID string - Path string - Version string - Stacks []Stack - Order Order + BuildpackInfo + Path string + Stacks []Stack + Order Order } type Order []Group type Group struct { - Group []BuildpackRef + Group []BuildpackInfo } -type BuildpackRef struct { - ID string - Version string +type BuildpackInfo struct { + ID string `toml:"id" json:"id"` + Version string `toml:"version" json:"version"` } type Stack struct { diff --git a/buildpack/fetcher.go b/buildpack/fetcher.go index a6c35665b..64a78ae4d 100644 --- a/buildpack/fetcher.go +++ b/buildpack/fetcher.go @@ -45,11 +45,13 @@ func (f *Fetcher) FetchBuildpack(uri string) (Buildpack, error) { } return Buildpack{ - Path: downloadedPath, - ID: data.Buildpack.ID, - Version: data.Buildpack.Version, - Order: data.Order, - Stacks: data.Stacks, + BuildpackInfo: BuildpackInfo{ + ID: data.Buildpack.ID, + Version: data.Buildpack.Version, + }, + Path: downloadedPath, + Order: data.Order, + Stacks: data.Stacks, }, err } diff --git a/commands/inspect_builder_test.go b/commands/inspect_builder_test.go index 6806d4182..767e4cbe2 100644 --- a/commands/inspect_builder_test.go +++ b/commands/inspect_builder_test.go @@ -12,6 +12,7 @@ import ( "github.com/buildpack/pack" "github.com/buildpack/pack/builder" + "github.com/buildpack/pack/buildpack" "github.com/buildpack/pack/commands" cmdmocks "github.com/buildpack/pack/commands/mocks" "github.com/buildpack/pack/config" @@ -135,9 +136,11 @@ ERROR: some local error ) it.Before(func() { + buildpack1Info := buildpack.BuildpackInfo{ID: "test.bp.one", Version: "1.0.0"} + buildpack2Info := buildpack.BuildpackInfo{ID: "test.bp.two", Version: "2.0.0"} buildpacks := []builder.BuildpackMetadata{ - {ID: "test.bp.one", Version: "1.0.0", Latest: true}, - {ID: "test.bp.two", Version: "2.0.0", Latest: false}, + {BuildpackInfo: buildpack1Info, Latest: true}, + {BuildpackInfo: buildpack2Info, Latest: false}, } remoteInfo = &pack.BuilderInfo{ Description: "Some remote description", @@ -145,10 +148,10 @@ ERROR: some local error RunImage: "some/run-image", RunImageMirrors: []string{"first/default", "second/default"}, Buildpacks: buildpacks, - Groups: []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{ - {ID: "test.bp.one", Version: "1.0.0", Optional: true}, - {ID: "test.bp.two", Version: "2.0.0"}, + Groups: []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{ + {BuildpackInfo: buildpack1Info, Optional: true}, + {BuildpackInfo: buildpack2Info}, }}}, LifecycleVersion: "6.7.8", } @@ -158,9 +161,9 @@ ERROR: some local error RunImage: "some/run-image", RunImageMirrors: []string{"first/local-default", "second/local-default"}, Buildpacks: buildpacks, - Groups: []builder.GroupMetadata{ - {Buildpacks: []builder.BuildpackRefMetadata{{ID: "test.bp.one", Version: "1.0.0"}}}, - {Buildpacks: []builder.BuildpackRefMetadata{{ID: "test.bp.two", Version: "2.0.0", Optional: true}}}, + Groups: []builder.V1Group{ + {Buildpacks: []builder.BuildpackRef{{BuildpackInfo: buildpack1Info}}}, + {Buildpacks: []builder.BuildpackRef{{BuildpackInfo: buildpack2Info, Optional: true}}}, }, LifecycleVersion: "4.5.6", } diff --git a/create_builder.go b/create_builder.go index 90559a33a..e737ad858 100644 --- a/create_builder.go +++ b/create_builder.go @@ -73,9 +73,7 @@ func (c *Client) CreateBuilder(ctx context.Context, opts CreateBuilderOptions) e builderImage.AddBuildpack(fetchedBuildpack) } - groupMetadata := opts.BuilderConfig.Order.ToMetadata() - - builderImage.SetOrder(groupMetadata) + builderImage.SetOrder(opts.BuilderConfig.Order) builderImage.SetStackInfo(opts.BuilderConfig.Stack) lifecycleMd, err := c.lifecycleFetcher.Fetch(lifecycleVersion, opts.BuilderConfig.Lifecycle.URI) diff --git a/create_builder_test.go b/create_builder_test.go index 0d437e721..99e30d9d5 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -72,10 +72,12 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { imageFetcher.RemoteImages["localhost:5000/some-run-image"] = fakeRunImageMirror bp := buildpack.Buildpack{ - ID: "bp.one", - Path: filepath.Join("testdata", "buildpack"), - Version: "1.2.3", - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "bp.one", + Version: "1.2.3", + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, } mockBPFetcher.EXPECT().FetchBuildpack(gomock.Any()).Return(bp, nil).AnyTimes() @@ -99,14 +101,13 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { Description: "Some description", Buildpacks: []builder.BuildpackConfig{ { - ID: "bp.one", - Version: "1.2.3", - URI: "https://example.fake/bp-one.tgz", + BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.one", Version: "1.2.3"}, + URI: "https://example.fake/bp-one.tgz", }, }, - Order: []builder.GroupConfig{{ - Group: []builder.BuildpackRefConfig{ - {ID: "bp.one", Version: "1.2.3", Optional: false}, + Order: []builder.OrderEntry{{ + Group: []builder.BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.one", Version: "1.2.3"}, Optional: false}, }}, }, Stack: builder.StackConfig{ @@ -221,16 +222,18 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, builderImage.UID, 1234) h.AssertEq(t, builderImage.GID, 4321) h.AssertEq(t, builderImage.StackID, "some.stack.id") - h.AssertEq(t, builderImage.GetBuildpacks(), []builder.BuildpackMetadata{{ + bpInfo := buildpack.BuildpackInfo{ ID: "bp.one", Version: "1.2.3", - Latest: true, + } + h.AssertEq(t, builderImage.GetBuildpacks(), []builder.BuildpackMetadata{{ + BuildpackInfo: bpInfo, + Latest: true, }}) - h.AssertEq(t, builderImage.GetOrder(), []builder.GroupMetadata{{ - Buildpacks: []builder.BuildpackRefMetadata{{ - ID: "bp.one", - Version: "1.2.3", - Optional: false, + h.AssertEq(t, builderImage.GetOrder(), []builder.V1Group{{ + Buildpacks: []builder.BuildpackRef{{ + BuildpackInfo: bpInfo, + Optional: false, }}, }}) h.AssertEq(t, builderImage.GetLifecycleVersion().String(), "3.4.5") diff --git a/inspect_builder.go b/inspect_builder.go index 25bf66661..36bdcf1a7 100644 --- a/inspect_builder.go +++ b/inspect_builder.go @@ -16,7 +16,7 @@ type BuilderInfo struct { RunImage string RunImageMirrors []string Buildpacks []builder.BuildpackMetadata - Groups []builder.GroupMetadata + Groups []builder.V1Group // TODO: change to v2 order type when order label is added LifecycleVersion string } diff --git a/inspect_builder_test.go b/inspect_builder_test.go index 35c4dd08c..d561954e1 100644 --- a/inspect_builder_test.go +++ b/inspect_builder_test.go @@ -13,6 +13,7 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpack/pack/builder" + "github.com/buildpack/pack/buildpack" "github.com/buildpack/pack/image" m "github.com/buildpack/pack/internal/mocks" "github.com/buildpack/pack/mocks" @@ -129,18 +130,22 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { builderInfo, err := subject.InspectBuilder("some/builder", useDaemon) h.AssertNil(t, err) h.AssertEq(t, builderInfo.Buildpacks[0], builder.BuildpackMetadata{ - ID: "test.bp.one", - Version: "1.0.0", - Latest: true, + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "test.bp.one", + Version: "1.0.0", + }, + Latest: true, }) }) it("sets the groups", func() { builderInfo, err := subject.InspectBuilder("some/builder", useDaemon) h.AssertNil(t, err) - h.AssertEq(t, builderInfo.Groups[0].Buildpacks[0], builder.BuildpackRefMetadata{ - ID: "test.bp.one", - Version: "1.0.0", + h.AssertEq(t, builderInfo.Groups[0].Buildpacks[0], builder.BuildpackRef{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "test.bp.one", + Version: "1.0.0", + }, }) }) diff --git a/internal/mocks/images.go b/internal/mocks/images.go index dfe93d026..fdfaee3fd 100644 --- a/internal/mocks/images.go +++ b/internal/mocks/images.go @@ -19,7 +19,7 @@ func NewFakeBuilderImage(t *testing.T, name string, buildpacks []builder.Buildpa h.AssertNil(t, fakeBuilderImage.SetEnv("CNB_GROUP_ID", "4321")) metadata := builder.Metadata{ Buildpacks: buildpacks, - Groups: config.Order.ToMetadata(), + Groups: config.Order.ToV1Order(), Stack: builder.StackMetadata{ RunImage: builder.RunImageMetadata{ Image: config.Stack.RunImage, From 727ae00e67becb0d3ed3161f12324339c38099ba Mon Sep 17 00:00:00 2001 From: Javier Romero Date: Mon, 12 Aug 2019 18:08:23 -0400 Subject: [PATCH 3/3] protect docker client from archive read errors (prevents container deadlock) Signed-off-by: Javier Romero Signed-off-by: Emily Casey --- build/phase.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/build/phase.go b/build/phase.go index eb41bb855..b541a35ea 100644 --- a/build/phase.go +++ b/build/phase.go @@ -113,16 +113,19 @@ func WithRegistryAccess(repos ...string) func(*Phase) (*Phase, error) { } } -func (p *Phase) Run(context context.Context) error { +func (p *Phase) Run(ctx context.Context) error { var err error - p.ctr, err = p.docker.ContainerCreate(context, p.ctrConf, p.hostConf, nil, "") + p.ctr, err = p.docker.ContainerCreate(ctx, p.ctrConf, p.hostConf, nil, "") if err != nil { return errors.Wrapf(err, "failed to create '%s' container", p.name) } p.appOnce.Do(func() { - var appReader io.ReadCloser + var ( + appReader io.ReadCloser + clientErr error + ) appReader, err = p.createAppReader() if err != nil { err = errors.Wrapf(err, "create tar archive from '%s'", p.appPath) @@ -130,17 +133,29 @@ func (p *Phase) Run(context context.Context) error { } defer appReader.Close() - if err = p.docker.CopyToContainer(context, p.ctr.ID, "/", appReader, types.CopyToContainerOptions{}); err != nil { - err = errors.Wrapf(err, "failed to copy files to '%s' container", p.name) - return + doneChan := make(chan interface{}) + pr, pw := io.Pipe() + go func() { + clientErr = p.docker.CopyToContainer(ctx, p.ctr.ID, "/", pr, types.CopyToContainerOptions{}) + close(doneChan) + }() + func() { + defer pw.Close() + _, err = io.Copy(pw, appReader) + }() + + <-doneChan + if err == nil { + err = clientErr } }) + if err != nil { - return errors.Wrapf(err, "run %s container", p.name) + return errors.Wrapf(err, "failed to copy files to '%s' container", p.name) } return container.Run( - context, + ctx, p.docker, p.ctr.ID, logging.NewPrefixWriter(logging.GetDebugWriter(p.logger), p.name),