diff --git a/Makefile b/Makefile index 5034af6075..822b2368d1 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 5e79ec0829..42f5f585b7 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 bd28b034f5..d0bd3c3bf5 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 e5a56171d0..0000000000 --- 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 e266f08618..907342f4fa 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 0000000000..a39147a6ca --- /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 96% rename from acceptance/testdata/mock_buildpacks/read-env-buildpack/bin/build rename to acceptance/testdata/mock_buildpacks/v1/read-env-buildpack/bin/build index 7d0744375d..4ebe895ebd 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 0000000000..1abe1c3b3a --- /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 0000000000..a39147a6ca --- /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 0000000000..d1813055aa --- /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 0000000000..47eb4aecfd --- /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 0000000000..2bd1e271c3 --- /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 0000000000..d1813055aa --- /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 0000000000..7e0876737f --- /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 0000000000..09c85a273e --- /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 0000000000..4ebe895ebd --- /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 0000000000..bf9d25d479 --- /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 0000000000..6d1b4733e4 --- /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 0000000000..3f4475e663 --- /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 0000000000..d1813055aa --- /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 0000000000..dddf09a1d9 --- /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 e7017f8b16..abffd35089 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 d7201f9381..95c0b29b06 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 01ae9e421e..b3b82eb620 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 7e62abc8de..43d9173a88 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 a638a64f1b..683366be8e 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 0000000000..8e94a1cb16 --- /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 0000000000..4b90b091fb --- /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 bd0e58927e..cd669e7df7 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 4e5f37a9b8..fc9a79a8fd 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 0000000000..4385cce5a9 --- /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 91503ba987..fef299e453 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 281cf75b49..a6c35665b7 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 62fc1d09c7..e56d988b31 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 226902d9a9..318df32482 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 2d0c3316ea..86e9ddd3f8 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 1e74e1efb3..6806d4182d 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 e9f2fef382..90559a33aa 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 0e178d18cc..0d437e7214 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 0f0528801c..35c4dd08cd 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 6fec2776dc..dfe93d026e 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 5f6b2a7007..93cde173e0 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 7e0ccbbf52..0127395bca 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 8f10bfc625..31dbee14f8 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) }