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..6a2c8b20a 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,17 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { cmd := packCmd( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", notBuilderTgz, - "--buildpack", "simple/layers@simple-layers-version", - "--buildpack", "noop.buildpack", + "--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) 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 +294,6 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) { "Cached Dep Contents", ) }) - }) when("the argument is directory", func() { @@ -300,7 +303,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 +316,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 +331,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 +783,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) + + // AMEND 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 +871,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 60% rename from acceptance/testdata/mock_buildpacks/builder.toml rename to acceptance/testdata/mock_buildpacks/v1/builder.toml index e266f0861..1abe1c3b3 100644 --- a/acceptance/testdata/mock_buildpacks/builder.toml +++ b/acceptance/testdata/mock_buildpacks/v1/builder.toml @@ -9,19 +9,18 @@ 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" - 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..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.GroupBuildpack{}} +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.GroupBuildpack{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.GroupBuildpack{ID: fetchedBP.ID, Version: fetchedBP.Version}) + group.Group = append(group.Group, builder.BuildpackRef{ + BuildpackInfo: fetchedBP.BuildpackInfo, + }) } } return bps, group, nil @@ -248,13 +255,18 @@ 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" { + c.logger.Info(style.Tip("WARNING: ") + "@latest syntax is deprecated, will not work in future releases") + 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) { +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 { @@ -263,15 +275,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 { + if len(group.Group) > 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.OrderEntry{group}) } if err := bldr.Save(); err != nil { return nil, err 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), diff --git a/build_test.go b/build_test.go index 01ae9e421..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,13 +493,18 @@ 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.GroupBuildpack{{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("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, @@ -504,9 +514,37 @@ 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.GroupBuildpack{{ID: "buildpack.id", Version: "latest"}}}, + 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") }) }) @@ -517,7 +555,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", ) }) @@ -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.GroupBuildpack{ - {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"}, + { + 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.GroupBuildpack{ - {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"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + {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.GroupBuildpack{ - {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"}, - {ID: "some-other-buildpack-id", Version: "some-other-buildpack-version"}, + {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}, }) }) }) @@ -707,7 +760,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..970c819b8 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -13,7 +13,6 @@ import ( "path/filepath" "regexp" "strconv" - "strings" "time" "github.com/BurntSushi/toml" @@ -47,6 +46,22 @@ type Builder struct { UID, GID int StackID string replaceOrder bool + order Order +} + +type orderTOML struct { + Order Order `toml:"order"` +} + +type Order []OrderEntry + +type OrderEntry struct { + Group []BuildpackRef `toml:"group"` +} + +type BuildpackRef struct { + buildpack.BuildpackInfo + Optional bool `toml:"optional,omitempty"` } func GetBuilder(img imgutil.Image) (*Builder, error) { @@ -95,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 } @@ -144,13 +160,11 @@ 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{ + BuildpackInfo: bp.BuildpackInfo, + }) } func (b *Builder) SetLifecycle(md lifecycle.Metadata) error { @@ -163,44 +177,10 @@ 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)) - } - } - } - } - b.metadata.Groups = order +func (b *Builder) SetOrder(order Order) { + b.metadata.Groups = order.ToV1Order() + b.order = 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 +197,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 +282,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 +403,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 = v1OrderTOML{Groups: b.metadata.Groups} + } else { + tomlData = orderTOML{Order: b.order} + } + + 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 +428,14 @@ 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(b.metadata.Stack) 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 +464,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 +498,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 +539,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 +634,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 +677,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..c76d95213 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -202,6 +202,128 @@ 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.Order{{ + Group: []builder.BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{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{ + BuildpackInfo: buildpack.BuildpackInfo{ + 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{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackInfo{ + {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{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackInfo{ + {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{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "some-buildpack-id", + Version: "some-buildpack-version", + }, + Path: filepath.Join("testdata", "buildpack"), + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackInfo{ + {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{ + 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() + + h.AssertError(t, err, "buildpack 'some-buildpack-id@some-buildpack-version' does not support stack 'some.stack.id'") + }) + }) + }) + }) when("#SetLifecycle", func() { @@ -281,147 +403,180 @@ 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{ + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "buildpack-1-id", + Version: "buildpack-1-version", + }, + Path: buildpackTgz, + Order: buildpack.Order{ + { + Group: []buildpack.BuildpackInfo{ + { + ID: "buildpack-2-id", + Version: "buildpack-2-version", + }, + }, + }, + }, + }) - when("buildpack has matching stack", func() { - var buildpackTgz string + subject.AddBuildpack(buildpack.Buildpack{ + BuildpackInfo: buildpack.BuildpackInfo{ + 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) - - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ - ID: "tgz-buildpack-id", - Version: "tgz-buildpack-version", - Path: buildpackTgz, - Latest: false, - 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"}}, - })) - - if runtime.GOOS != "windows" { - h.AssertNil(t, subject.AddBuildpack(buildpack.Buildpack{ + subject.AddBuildpack(buildpack.Buildpack{ + 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{ + BuildpackInfo: buildpack.BuildpackInfo{ ID: "dir-buildpack-id", Version: "dir-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) - } + }, + Path: filepath.Join("testdata", "buildpack"), + Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, + }) + } - h.AssertNil(t, subject.Save()) - h.AssertEq(t, baseImage.IsSaved(), true) - }) + h.AssertNil(t, subject.Save()) + h.AssertEq(t, baseImage.IsSaved(), true) + }) - it.After(func() { - h.AssertNil(t, os.Remove(buildpackTgz)) - }) + it.After(func() { + h.AssertNil(t, os.Remove(buildpackTgz)) + }) - it("adds the buildpack as an image layer", func() { - var ( - layerTar string - err error - ) + it("adds the buildpack as an image layer", func() { + var ( + layerTar string + err error + ) - layerTar, err = baseImage.FindLayerWithPath("/buildpacks/tgz-buildpack-id/tgz-buildpack-version") - h.AssertNil(t, err) + layerTar, err = baseImage.FindLayerWithPath("/buildpacks/buildpack-1-id/buildpack-1-version") + h.AssertNil(t, err) - h.AssertOnTarEntry(t, layerTar, "/buildpacks/tgz-buildpack-id/tgz-buildpack-version", - h.IsDirectory(), - ) + h.AssertOnTarEntry(t, layerTar, "/buildpacks/buildpack-1-id/buildpack-1-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-1-id/buildpack-1-version/buildpack-file", + h.ContentEquals("buildpack-contents"), + h.HasOwnerAndGroup(1234, 4321), + h.HasFileMode(0644), + ) + + 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/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-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", + 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), 4) + } else { + h.AssertEq(t, len(metadata.Buildpacks), 5) + } + + 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, false) + + 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[4].ID, "dir-buildpack-id") + h.AssertEq(t, metadata.Buildpacks[4].Version, "dir-buildpack-version") + h.AssertEq(t, metadata.Buildpacks[4].Latest, true) + } }) when("base image already has metadata", func() { @@ -435,12 +590,14 @@ 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{ - ID: "some-buildpack-id", - Version: "some-buildpack-version", - Path: filepath.Join("testdata", "buildpack"), - Stacks: []buildpack.Stack{{ID: "some.stack.id"}}, - })) + subject.AddBuildpack(buildpack.Buildpack{ + 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) }) @@ -463,7 +620,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 +628,48 @@ 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{ - 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{ - 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{ - 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.AddBuildpack(buildpack.Buildpack{ + 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{ + 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{ + 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.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: "latest", + BuildpackInfo: buildpack.BuildpackInfo{ + ID: "optional-buildpack-id", + Version: "optional-buildpack-version", + }, Optional: true, }, }}, - })) + }) + h.AssertNil(t, subject.Save()) h.AssertEq(t, baseImage.IsSaved(), true) }) @@ -510,15 +677,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 +704,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..69c7c3227 --- /dev/null +++ b/builder/compat.go @@ -0,0 +1,27 @@ +package builder + +type V1Order []V1Group + +type V1Group struct { + Buildpacks []BuildpackRef `toml:"buildpacks" json:"buildpacks"` +} + +type v1OrderTOML struct { + Groups []V1Group `toml:"groups" json:"groups"` +} + +func (o Order) ToV1Order() V1Order { + var order V1Order + for _, gp := range o { + var buildpacks []BuildpackRef + for _, bp := range gp.Group { + buildpacks = append(buildpacks, bp) + } + + order = append(order, V1Group{ + Buildpacks: buildpacks, + }) + } + + return order +} diff --git a/builder/config.go b/builder/config.go index bd0e58927..847d50e9b 100644 --- a/builder/config.go +++ b/builder/config.go @@ -9,22 +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"` - Groups []GroupMetadata `toml:"groups"` + Order Order `toml:"order"` Stack StackConfig `toml:"stack"` Lifecycle LifecycleConfig `toml:"lifecycle"` } type BuildpackConfig struct { - ID string `toml:"id"` - Version string `toml:"version"` - URI string `toml:"uri"` - Latest bool `toml:"latest"` + buildpack.BuildpackInfo + URI string `toml:"uri"` } type StackConfig struct { diff --git a/builder/metadata.go b/builder/metadata.go index 4e5f37a9b..d4328aecf 100644 --- a/builder/metadata.go +++ b/builder/metadata.go @@ -1,7 +1,12 @@ package builder import ( + "fmt" + + "github.com/buildpack/pack/buildpack" + "github.com/buildpack/pack/lifecycle" + "github.com/buildpack/pack/style" ) const MetadataLabel = "io.buildpacks.builder.metadata" @@ -9,36 +14,73 @@ const MetadataLabel = "io.buildpacks.builder.metadata" type Metadata struct { Description string `json:"description"` Buildpacks []BuildpackMetadata `json:"buildpacks"` - Groups []GroupMetadata `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"` + buildpack.BuildpackInfo + Latest bool `json:"latest"` // deprecated } -type GroupMetadata struct { - Buildpacks []GroupBuildpack `json:"buildpacks" toml:"buildpacks"` +type StackMetadata struct { + RunImage RunImageMetadata `json:"runImage" toml:"run-image"` } -type OrderTOML struct { - Groups []GroupMetadata `toml:"groups"` +type RunImageMetadata struct { + Image string `json:"image" toml:"image"` + Mirrors []string `json:"mirrors" toml:"mirrors"` } -type GroupBuildpack struct { - ID string `json:"id" toml:"id"` - Version string `json:"version" toml:"version"` - Optional bool `json:"optional,omitempty" toml:"optional,omitempty"` +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 } -type StackMetadata struct { - RunImage RunImageMetadata `toml:"run-image" json:"runImage"` +func hasBPWithVersion(bps []BuildpackMetadata, version string) bool { + for _, bp := range bps { + if bp.Version == version { + return true + } + } + return false } -type RunImageMetadata struct { - Image string `toml:"image" json:"image"` - Mirrors []string `toml:"mirrors" json:"mirrors"` +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..43832d59a --- /dev/null +++ b/builder/metadata_test.go @@ -0,0 +1,158 @@ +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/buildpack" + "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{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"}}, + }, + Groups: []V1Group{ + { + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{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() { + 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{ + {BuildpackInfo: bpInfo1}, + {BuildpackInfo: bpInfo2}, + }, + Groups: []V1Group{ + { + Buildpacks: []BuildpackRef{ + {BuildpackInfo: bpInfo1}, + }, + }, + }, + Lifecycle: lifecycle.Metadata{ + Version: semver.MustParse("0.0.0"), + }, + } + + err := processMetadata(&md) + h.AssertNil(t, err) + + expected := Metadata{ + Buildpacks: []BuildpackMetadata{ + {BuildpackInfo: bpInfo1}, + {BuildpackInfo: bpInfo2}, + }, + Groups: []V1Group{ + { + Buildpacks: []BuildpackRef{ + {BuildpackInfo: bpInfo1}, + }, + }, + }, + Lifecycle: lifecycle.Metadata{ + Version: semver.MustParse("0.0.0"), + }, + } + + h.AssertEq(t, md, expected) + }) + }) + + 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{ + {BuildpackInfo: bpInfo1}, + {BuildpackInfo: bpInfo2}, + }, + Groups: []V1Group{ + { + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{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{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"}}, + }, + Groups: []V1Group{ + { + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{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{ + {BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.id.1", Version: "1.2.3"}}, + }, + Groups: []V1Group{ + { + Buildpacks: []BuildpackRef{ + {BuildpackInfo: buildpack.BuildpackInfo{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..fbde82fb5 100644 --- a/buildpack/buildpack.go +++ b/buildpack/buildpack.go @@ -8,11 +8,21 @@ type BuildpackTOML struct { } type Buildpack struct { - ID string - Latest bool - Path string - Version string - Stacks []Stack + BuildpackInfo + Path string + Stacks []Stack + Order Order +} + +type Order []Group + +type Group struct { + Group []BuildpackInfo +} + +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 281cf75b4..64a78ae4d 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"` } @@ -44,10 +45,13 @@ func (f *Fetcher) FetchBuildpack(uri string) (Buildpack, error) { } return Buildpack{ - Path: downloadedPath, - ID: data.Buildpack.ID, - Version: data.Buildpack.Version, - 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/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..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.GroupBuildpack{ - {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.GroupBuildpack{{ID: "test.bp.one", Version: "1.0.0"}}}, - {Buildpacks: []builder.GroupBuildpack{{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", } @@ -195,9 +198,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 +226,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 +267,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 +295,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..e737ad858 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,10 @@ 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 - } - } - - if err := builderImage.SetOrder(opts.BuilderConfig.Groups); err != nil { - return errors.Wrap(err, "builder config has invalid groups") + builderImage.AddBuildpack(fetchedBuildpack) } + 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 0e178d18c..99e30d9d5 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -72,11 +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", - Latest: true, - 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() @@ -100,15 +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", - Latest: true, + BuildpackInfo: buildpack.BuildpackInfo{ID: "bp.one", Version: "1.2.3"}, + URI: "https://example.fake/bp-one.tgz", }, }, - Groups: []builder.GroupMetadata{{ - Buildpacks: []builder.GroupBuildpack{ - {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{ @@ -223,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.GroupBuildpack{{ - 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 0f0528801..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.GroupBuildpack{ - 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 6fec2776d..fdfaee3fd 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.ToV1Order(), 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) }