diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 61bb2c380..5df44c18f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -68,7 +68,7 @@ make test To run our full acceptance suite (including cross-compatibility for n-1 `pack` and `lifecycle`): ```shell -make acceptance-all +make compatibility ``` ### Tidy diff --git a/Makefile b/Makefile index 33b04baa1..cad6b9959 100644 --- a/Makefile +++ b/Makefile @@ -109,12 +109,12 @@ unit: out acceptance: out @echo "> Running acceptance tests..." - $(GOCMD) test $(GOTESTFLAGS) -timeout=$(ACCEPTANCE_TIMEOUT) -tags=acceptance ./acceptance + $(GOCMD) test $(GOTESTFLAGS) -timeout=$(ACCEPTANCE_TIMEOUT) -tags=acceptance ./acceptance/... -acceptance-all: export ACCEPTANCE_SUITE_CONFIG:=$(shell $(CAT) .$/acceptance$/testconfig$/all.json) -acceptance-all: - @echo "> Running acceptance tests..." - $(GOCMD) test $(GOTESTFLAGS) -timeout=$(ACCEPTANCE_TIMEOUT) -tags=acceptance ./acceptance +compatibility: export ACCEPTANCE_SUITE_CONFIG:=$(shell $(CAT) .$/acceptance$/testconfig$/all.json) +compatibility: + @echo "> Configured acceptance tests to test compatibility..." + make acceptance clean: @echo "> Cleaning workspace..." diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index dc1ae73db..dd92b9b45 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -14,7 +14,6 @@ import ( "math/rand" "os" "path/filepath" - "regexp" "runtime" "strings" "testing" @@ -73,7 +72,7 @@ func TestAcceptance(t *testing.T) { inputConfigManager, err := config.NewInputConfigurationManager() assert.Nil(err) - assetsConfig := config.ConvergedAssetManager(t, assert, inputConfigManager) + assetsConfig := config.NewAssetManager(t, assert, inputConfigManager, "..") suiteManager = &SuiteManager{out: t.Logf} suite := spec.New("acceptance suite", spec.Report(report.Terminal{})) @@ -189,29 +188,21 @@ func testWithoutSpecificBuilderRequirement( }) generateAggregatePackageToml := func(buildpackURI, nestedPackageName, os string) string { - t.Helper() - packageTomlFile, err := ioutil.TempFile(tmpDir, "package_aggregate-*.toml") - assert.Nil(err) - - pack.FixtureManager().TemplateFixtureToFile( + return pack.FixtureManager().TemplateFixtureToFile( + tmpDir, "package_aggregate.toml", - packageTomlFile, map[string]interface{}{ "BuildpackURI": buildpackURI, "PackageName": nestedPackageName, "OS": os, }, ) - - assert.Nil(packageTomlFile.Close()) - - return packageTomlFile.Name() } when("no --format is provided", func() { it("creates the package as image", func() { packageName := "test/package-" + h.RandString(10) - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) output := pack.RunSuccessfully("buildpack", "package", packageName, "-c", packageTomlPath) assertions.NewOutputAssertionManager(t, output).ReportsPackageCreation(packageName) @@ -227,19 +218,19 @@ func testWithoutSpecificBuilderRequirement( nestedPackageName := "test/package-" + h.RandString(10) packageName := "test/package-" + h.RandString(10) - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) aggregatePackageToml := generateAggregatePackageToml("simple-layers-parent-buildpack.tgz", nestedPackageName, imageManager.HostOS()) packageBuildpack := buildpacks.NewPackageImage( t, - pack, + *pack, packageName, aggregatePackageToml, buildpacks.WithRequiredBuildpacks( buildpacks.SimpleLayersParent, buildpacks.NewPackageImage( t, - pack, + *pack, nestedPackageName, packageTomlPath, buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayers), @@ -255,12 +246,12 @@ func testWithoutSpecificBuilderRequirement( when("--publish", func() { it("publishes image to registry", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) nestedPackageName := registryConfig.RepoName("test/package-" + h.RandString(10)) nestedPackage := buildpacks.NewPackageImage( t, - pack, + *pack, nestedPackageName, packageTomlPath, buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayers), @@ -287,11 +278,11 @@ func testWithoutSpecificBuilderRequirement( when("--pull-policy=never", func() { it("should use local image", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) nestedPackageName := "test/package-" + h.RandString(10) nestedPackage := buildpacks.NewPackageImage( t, - pack, + *pack, nestedPackageName, packageTomlPath, buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayers), @@ -311,11 +302,11 @@ func testWithoutSpecificBuilderRequirement( }) it("should not pull image from registry", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) nestedPackageName := registryConfig.RepoName("test/package-" + h.RandString(10)) nestedPackage := buildpacks.NewPackageImage( t, - pack, + *pack, nestedPackageName, packageTomlPath, buildpacks.WithPublish(), @@ -341,7 +332,7 @@ func testWithoutSpecificBuilderRequirement( when("--format file", func() { when("the file extension is .cnb", func() { it("creates the package with the same extension", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) destinationFile := filepath.Join(tmpDir, "package.cnb") output := pack.RunSuccessfully( "buildpack", "package", destinationFile, @@ -354,7 +345,7 @@ func testWithoutSpecificBuilderRequirement( }) when("the file extension is empty", func() { it("creates the package with a .cnb extension", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) destinationFile := filepath.Join(tmpDir, "package") expectedFile := filepath.Join(tmpDir, "package.cnb") output := pack.RunSuccessfully( @@ -368,7 +359,7 @@ func testWithoutSpecificBuilderRequirement( }) when("the file extension is not .cnb", func() { it("creates the package with the given extension but shows a warning", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, simplePackageConfigFixtureName, imageManager.HostOS()) destinationFile := filepath.Join(tmpDir, "package.tar.gz") output := pack.RunSuccessfully( "buildpack", "package", destinationFile, @@ -418,11 +409,11 @@ func testWithoutSpecificBuilderRequirement( fmt.Sprintf("buildpack-%s.cnb", h.RandString(8)), ) - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, "package_for_build_cmd.toml", imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, "package_for_build_cmd.toml", imageManager.HostOS()) packageFile := buildpacks.NewPackageFile( t, - pack, + *pack, packageFileLocation, packageTomlPath, buildpacks.WithRequiredBuildpacks( @@ -449,12 +440,12 @@ func testWithoutSpecificBuilderRequirement( when("buildpack image", func() { when("inspect", func() { it("succeeds", func() { - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, "package_for_build_cmd.toml", imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, "package_for_build_cmd.toml", imageManager.HostOS()) packageImageName := registryConfig.RepoName("buildpack-" + h.RandString(8)) packageImage := buildpacks.NewPackageImage( t, - pack, + *pack, packageImageName, packageTomlPath, buildpacks.WithRequiredBuildpacks( @@ -663,7 +654,7 @@ func testAcceptance( value, err := suiteManager.RunTaskOnceString("create-stack", func() (string, error) { runImageMirror := registryConfig.RepoName(runImage) - err := createStack(t, dockerCli, runImageMirror) + err := createStack(t, dockerCli, imageManager, runImageMirror) if err != nil { return "", err } @@ -694,7 +685,13 @@ func testAcceptance( )..., ) value, err := suiteManager.RunTaskOnceString(key, func() (string, error) { - return createBuilder(t, assert, createBuilderPack, lifecycle, buildpackManager, runImageMirror) + return createBuilder(t, assert, imageManager, dockerCli, createBuilderPack, lifecycle, buildpackManager, config.Stack{ + RunImage: config.RunImage{ + Name: runImage, + MirrorName: runImageMirror, + }, + BuildImageName: buildImage, + }) }) assert.Nil(err) suiteManager.RegisterCleanUp("clean-"+key, func() error { @@ -787,242 +784,15 @@ func testAcceptance( launchCacheVolume.Clear(context.TODO()) }) - when("builder is untrusted", func() { - var untrustedBuilderName string - it.Before(func() { - var err error - untrustedBuilderName, err = createBuilder( - t, - assert, - createBuilderPack, - lifecycle, - buildpackManager, - runImageMirror, - ) - assert.Nil(err) - - suiteManager.RegisterCleanUp("remove-lifecycle-"+lifecycle.Image(), func() error { - img := imageManager.GetImageID(lifecycle.Image()) - imageManager.CleanupImages(img) - return nil - }) - }) - - it.After(func() { - imageManager.CleanupImages(untrustedBuilderName) - }) - - when("daemon", func() { - it("uses the 5 phases", func() { - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "-B", untrustedBuilderName, - ) - - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) - - assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) - assertOutput.IncludesSeparatePhases() - }) - }) - - when("--publish", func() { - it("uses the 5 phases", func() { - buildArgs := []string{ - repoName, - "-p", filepath.Join("testdata", "mock_app"), - "-B", untrustedBuilderName, - "--publish", - } - if imageManager.HostOS() != "windows" { - buildArgs = append(buildArgs, "--network", "host") - } - - output := pack.RunSuccessfully("build", buildArgs...) - - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) - - assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) - assertOutput.IncludesSeparatePhases() - }) - }) - - when("additional tags", func() { - var additionalRepoName string - - it.Before(func() { - additionalRepoName = fmt.Sprintf("%s_additional", repoName) - }) - it.After(func() { - imageManager.CleanupImages(additionalRepoName) - }) - it("pushes image to additional tags", func() { - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "-B", untrustedBuilderName, - "--tag", additionalRepoName, - ) - - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) - assert.Contains(output, additionalRepoName) - }) - }) - }) - when("default builder is set", func() { + it.Before(func() { pack.RunSuccessfully("config", "default-builder", builderName) pack.JustRunSuccessfully("config", "trusted-builders", "add", builderName) }) - it("creates a runnable, rebuildable image on daemon from app dir", func() { - appPath := filepath.Join("testdata", "mock_app") - - output := pack.RunSuccessfully( - "build", repoName, - "-p", appPath, - ) - - assertOutput := assertions.NewOutputAssertionManager(t, output) - - assertOutput.ReportsSuccessfulImageBuild(repoName) - assertOutput.ReportsUsingBuildCacheVolume() - assertOutput.ReportsSelectingRunImageMirror(runImageMirror) - - t.Log("app is runnable") - assertImage.RunsWithOutput(repoName, "Launch Dep Contents", "Cached Dep Contents") - - t.Log("it uses the run image as a base image") - assertImage.HasBaseImage(repoName, runImage) - - t.Log("sets the run image metadata") - assertImage.HasLabelWithData(repoName, "io.buildpacks.lifecycle.metadata", fmt.Sprintf(`"stack":{"runImage":{"image":"%s","mirrors":["%s"]}}}`, runImage, runImageMirror)) - - t.Log("sets the source metadata") - assertImage.HasLabelWithData(repoName, "io.buildpacks.project.metadata", (`{"source":{"type":"project","version":{"declared":"1.0.2"},"metadata":{"url":"https://github.com/buildpacks/pack"}}}`)) - - t.Log("registry is empty") - assertImage.NotExistsInRegistry(repo) - - t.Log("add a local mirror") - localRunImageMirror := registryConfig.RepoName("pack-test/run-mirror") - imageManager.TagImage(runImage, localRunImageMirror) - defer imageManager.CleanupImages(localRunImageMirror) - pack.JustRunSuccessfully("config", "run-image-mirrors", "add", runImage, "-m", localRunImageMirror) - - t.Log("rebuild") - output = pack.RunSuccessfully( - "build", repoName, - "-p", appPath, - ) - assertOutput = assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsSuccessfulImageBuild(repoName) - assertOutput.ReportsSelectingRunImageMirrorFromLocalConfig(localRunImageMirror) - cachedLaunchLayer := "simple/layers:cached-launch-layer" - - assertLifecycleOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertLifecycleOutput.ReportsRestoresCachedLayer(cachedLaunchLayer) - assertLifecycleOutput.ReportsExporterReusingUnchangedLayer(cachedLaunchLayer) - assertLifecycleOutput.ReportsCacheReuse(cachedLaunchLayer) - - t.Log("app is runnable") - assertImage.RunsWithOutput(repoName, "Launch Dep Contents", "Cached Dep Contents") - - t.Log("rebuild with --clear-cache") - output = pack.RunSuccessfully("build", repoName, "-p", appPath, "--clear-cache") - - assertOutput = assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsSuccessfulImageBuild(repoName) - assertLifecycleOutput = assertions.NewLifecycleOutputAssertionManager(t, output) - assertLifecycleOutput.ReportsExporterReusingUnchangedLayer(cachedLaunchLayer) - assertLifecycleOutput.ReportsCacheCreation(cachedLaunchLayer) - - t.Log("cacher adds layers") - assert.Matches(output, regexp.MustCompile(`(?i)Adding cache layer 'simple/layers:cached-launch-layer'`)) - - t.Log("inspecting image") - inspectCmd := "inspect" - if !pack.Supports("inspect") { - inspectCmd = "inspect-image" - } - - var ( - webCommand string - helloCommand string - helloArgs []string - helloArgsPrefix string - imageWorkdir string - ) - if imageManager.HostOS() == "windows" { - webCommand = ".\\run" - helloCommand = "cmd" - helloArgs = []string{"/c", "echo hello world"} - helloArgsPrefix = " " - imageWorkdir = "c:\\workspace" - - } else { - webCommand = "./run" - helloCommand = "echo" - helloArgs = []string{"hello", "world"} - helloArgsPrefix = "" - imageWorkdir = "/workspace" - } - - formats := []compareFormat{ - { - extension: "json", - compareFunc: assert.EqualJSON, - outputArg: "json", - }, - { - extension: "yaml", - compareFunc: assert.EqualYAML, - outputArg: "yaml", - }, - { - extension: "toml", - compareFunc: assert.EqualTOML, - outputArg: "toml", - }, - } - for _, format := range formats { - t.Logf("inspecting image %s format", format.outputArg) - - output = pack.RunSuccessfully(inspectCmd, repoName, "--output", format.outputArg) - expectedOutput := pack.FixtureManager().TemplateFixture( - fmt.Sprintf("inspect_image_local_output.%s", format.extension), - map[string]interface{}{ - "image_name": repoName, - "base_image_id": h.ImageID(t, runImageMirror), - "base_image_top_layer": h.TopLayerDiffID(t, runImageMirror), - "run_image_local_mirror": localRunImageMirror, - "run_image_mirror": runImageMirror, - "web_command": webCommand, - "hello_command": helloCommand, - "hello_args": helloArgs, - "hello_args_prefix": helloArgsPrefix, - "image_workdir": imageWorkdir, - }, - ) - - format.compareFunc(output, expectedOutput) - } - }) - - when("--no-color", func() { - it("doesn't have color", func() { - appPath := filepath.Join("testdata", "mock_app") - - // --no-color is set as a default option in our tests, and doesn't need to be explicitly provided - output := pack.RunSuccessfully("build", repoName, "-p", appPath) - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsSuccessfulImageBuild(repoName) - assertOutput.WithoutColors() - }) + it.After(func() { + pack.RunSuccessfully("config", "default-builder", "--unset") }) when("--quiet", func() { @@ -1038,12 +808,6 @@ func testAcceptance( }) }) - it("supports building app from a zip file", func() { - appPath := filepath.Join("testdata", "mock_app.zip") - output := pack.RunSuccessfully("build", repoName, "-p", appPath) - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) - }) - when("--network", func() { var tmpDir string @@ -1215,287 +979,6 @@ func testAcceptance( }) }) - when("--buildpack", func() { - when("the argument is an ID", func() { - it("adds the buildpacks to the builder if necessary and runs them", func() { - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", "simple/layers", // can omit version if only one - "--buildpack", "noop.buildpack@noop.buildpack.version", - ) - - assertOutput := assertions.NewOutputAssertionManager(t, output) - - assertTestAppOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) - assertTestAppOutput.ReportsBuildStep("Simple Layers Buildpack") - assertTestAppOutput.ReportsBuildStep("NOOP Buildpack") - assertOutput.ReportsSuccessfulImageBuild(repoName) - - t.Log("app is runnable") - assertImage.RunsWithOutput( - repoName, - "Launch Dep Contents", - "Cached Dep Contents", - ) - }) - }) - - when("the argument is an archive", func() { - var tmpDir string - - it.Before(func() { - var err error - tmpDir, err = ioutil.TempDir("", "archive-buildpack-tests-") - assert.Nil(err) - }) - - it.After(func() { - assert.Succeeds(os.RemoveAll(tmpDir)) - }) - - it("adds the buildpack to the builder and runs it", func() { - buildpackManager.PrepareBuildpacks(tmpDir, buildpacks.ArchiveNotInBuilder) - - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", buildpacks.ArchiveNotInBuilder.FullPathIn(tmpDir), - ) - - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsAddingBuildpack("local/bp", "local-bp-version") - assertOutput.ReportsSuccessfulImageBuild(repoName) - - assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) - assertBuildpackOutput.ReportsBuildStep("Local Buildpack") - }) - }) - - when("the argument is directory", func() { - var tmpDir string - - it.Before(func() { - var err error - tmpDir, err = ioutil.TempDir("", "folder-buildpack-tests-") - assert.Nil(err) - }) - - it.After(func() { - _ = os.RemoveAll(tmpDir) - }) - - it("adds the buildpacks to the builder and runs it", func() { - h.SkipIf(t, runtime.GOOS == "windows", "buildpack directories not supported on windows") - - buildpackManager.PrepareBuildpacks(tmpDir, buildpacks.FolderNotInBuilder) - - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", buildpacks.FolderNotInBuilder.FullPathIn(tmpDir), - ) - - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsAddingBuildpack("local/bp", "local-bp-version") - assertOutput.ReportsSuccessfulImageBuild(repoName) - - assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) - assertBuildpackOutput.ReportsBuildStep("Local Buildpack") - }) - }) - - when("the argument is a buildpackage image", func() { - var ( - tmpDir string - packageImageName string - ) - - it.After(func() { - imageManager.CleanupImages(packageImageName) - _ = os.RemoveAll(tmpDir) - }) - - it("adds the buildpacks to the builder and runs them", func() { - packageImageName = registryConfig.RepoName("buildpack-" + h.RandString(8)) - - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, "package_for_build_cmd.toml", imageManager.HostOS()) - packageImage := buildpacks.NewPackageImage( - t, - pack, - packageImageName, - packageTomlPath, - buildpacks.WithRequiredBuildpacks( - buildpacks.FolderSimpleLayersParent, - buildpacks.FolderSimpleLayers, - ), - ) - - buildpackManager.PrepareBuildpacks(tmpDir, packageImage) - - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", packageImageName, - ) - - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsAddingBuildpack( - "simple/layers/parent", - "simple-layers-parent-version", - ) - assertOutput.ReportsAddingBuildpack("simple/layers", "simple-layers-version") - assertOutput.ReportsSuccessfulImageBuild(repoName) - - assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) - assertBuildpackOutput.ReportsBuildStep("Simple Layers Buildpack") - }) - }) - - when("the argument is a buildpackage file", func() { - var tmpDir string - - it.Before(func() { - var err error - tmpDir, err = ioutil.TempDir("", "package-file") - assert.Nil(err) - }) - - it.After(func() { - assert.Succeeds(os.RemoveAll(tmpDir)) - }) - - it("adds the buildpacks to the builder and runs them", func() { - packageFileLocation := filepath.Join( - tmpDir, - fmt.Sprintf("buildpack-%s.cnb", h.RandString(8)), - ) - - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, "package_for_build_cmd.toml", imageManager.HostOS()) - packageFile := buildpacks.NewPackageFile( - t, - pack, - packageFileLocation, - packageTomlPath, - buildpacks.WithRequiredBuildpacks( - buildpacks.FolderSimpleLayersParent, - buildpacks.FolderSimpleLayers, - ), - ) - - buildpackManager.PrepareBuildpacks(tmpDir, packageFile) - - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", packageFileLocation, - ) - - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsAddingBuildpack( - "simple/layers/parent", - "simple-layers-parent-version", - ) - assertOutput.ReportsAddingBuildpack("simple/layers", "simple-layers-version") - assertOutput.ReportsSuccessfulImageBuild(repoName) - - assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) - assertBuildpackOutput.ReportsBuildStep("Simple Layers Buildpack") - }) - }) - - when("the buildpack stack doesn't match the builder", func() { - var otherStackBuilderTgz string - - it.Before(func() { - otherStackBuilderTgz = h.CreateTGZ(t, filepath.Join(bpDir, "other-stack-buildpack"), "./", 0755) - }) - - it.After(func() { - assert.Succeeds(os.Remove(otherStackBuilderTgz)) - }) - - it("errors", func() { - output, err := pack.Run( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--buildpack", otherStackBuilderTgz, - ) - - assert.NotNil(err) - assert.Contains(output, "other/stack/bp") - assert.Contains(output, "other-stack-version") - assert.Contains(output, "does not support stack 'pack.test.stack'") - }) - }) - }) - - when("--env-file", func() { - var envPath string - - it.Before(func() { - envfile, err := ioutil.TempFile("", "envfile") - assert.Nil(err) - defer envfile.Close() - - err = os.Setenv("ENV2_CONTENTS", "Env2 Layer Contents From Environment") - assert.Nil(err) - envfile.WriteString(` - DETECT_ENV_BUILDPACK=true - ENV1_CONTENTS=Env1 Layer Contents From File - ENV2_CONTENTS - `) - envPath = envfile.Name() - }) - - it.After(func() { - assert.Succeeds(os.Unsetenv("ENV2_CONTENTS")) - assert.Succeeds(os.RemoveAll(envPath)) - }) - - it("provides the env vars to the build and detect steps", func() { - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--env-file", envPath, - ) - - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) - assertImage.RunsWithOutput( - repoName, - "Env2 Layer Contents From Environment", - "Env1 Layer Contents From File", - ) - }) - }) - - when("--env", func() { - it.Before(func() { - assert.Succeeds(os.Setenv("ENV2_CONTENTS", "Env2 Layer Contents From Environment")) - }) - - it.After(func() { - assert.Succeeds(os.Unsetenv("ENV2_CONTENTS")) - }) - - it("provides the env vars to the build and detect steps", func() { - output := pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--env", "DETECT_ENV_BUILDPACK=true", - "--env", `ENV1_CONTENTS="Env1 Layer Contents From Command Line"`, - "--env", "ENV2_CONTENTS", - ) - - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) - assertImage.RunsWithOutput( - repoName, - "Env2 Layer Contents From Environment", - "Env1 Layer Contents From Command Line", - ) - }) - }) - when("--run-image", func() { var runImageName string @@ -1646,6 +1129,7 @@ func testAcceptance( "image_name": repoName, "base_image_ref": strings.Join([]string{runImageMirror, h.Digest(t, runImageMirror)}, "@"), "base_image_top_layer": h.TopLayerDiffID(t, runImageMirror), + "run_image": runImage, "run_image_mirror": runImageMirror, "web_command": webCommand, "hello_command": helloCommand, @@ -2039,50 +1523,6 @@ include = [ "*.jar", "media/mountain.jpg", "/media/person.png", ] }) }) }) - - when("--creation-time", func() { - it.Before(func() { - h.SkipIf(t, !pack.SupportsFeature(invoke.CreationTime), "") - h.SkipIf(t, !lifecycle.SupportsFeature(config.CreationTime), "") - }) - - when("provided as 'now'", func() { - it("image has create time of the current time", func() { - expectedTime := time.Now() - pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--creation-time", "now", - ) - assertImage.HasCreateTime(repoName, expectedTime) - }) - }) - - when("provided as unix timestamp", func() { - it("image has create time of the time that was provided", func() { - pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--creation-time", "1566172801", - ) - expectedTime, err := time.Parse("2006-01-02T03:04:05Z", "2019-08-19T00:00:01Z") - h.AssertNil(t, err) - assertImage.HasCreateTime(repoName, expectedTime) - }) - }) - - when("not provided", func() { - it("image has create time of Jan 1, 1980", func() { - pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - ) - expectedTime, err := time.Parse("2006-01-02T03:04:05Z", "1980-01-01T00:00:01Z") - h.AssertNil(t, err) - assertImage.HasCreateTime(repoName, expectedTime) - }) - }) - }) }) }) @@ -2401,172 +1841,6 @@ include = [ "*.jar", "media/mountain.jpg", "/media/person.png", ] assert.TrimmedEq(output, expectedOutput) }) }) - - when("rebase", func() { - var repoName, runBefore, origID string - var buildRunImage func(string, string, string) - - it.Before(func() { - pack.JustRunSuccessfully("config", "trusted-builders", "add", builderName) - - repoName = registryConfig.RepoName("some-org/" + h.RandString(10)) - runBefore = registryConfig.RepoName("run-before/" + h.RandString(10)) - - buildRunImage = func(newRunImage, contents1, contents2 string) { - user := func() string { - if imageManager.HostOS() == "windows" { - return "ContainerAdministrator" - } - - return "root" - } - - h.CreateImage(t, dockerCli, newRunImage, fmt.Sprintf(` - FROM %s - USER %s - RUN echo %s > /contents1.txt - RUN echo %s > /contents2.txt - USER pack - `, runImage, user(), contents1, contents2)) - } - - buildRunImage(runBefore, "contents-before-1", "contents-before-2") - pack.RunSuccessfully( - "build", repoName, - "-p", filepath.Join("testdata", "mock_app"), - "--builder", builderName, - "--run-image", runBefore, - "--pull-policy", "never", - ) - origID = h.ImageID(t, repoName) - assertImage.RunsWithOutput( - repoName, - "contents-before-1", - "contents-before-2", - ) - }) - - it.After(func() { - imageManager.CleanupImages(origID, repoName, runBefore) - ref, err := name.ParseReference(repoName, name.WeakValidation) - assert.Nil(err) - buildCacheVolume := cache.NewVolumeCache(ref, cache.CacheInfo{}, "build", dockerCli) - launchCacheVolume := cache.NewVolumeCache(ref, cache.CacheInfo{}, "launch", dockerCli) - assert.Succeeds(buildCacheVolume.Clear(context.TODO())) - assert.Succeeds(launchCacheVolume.Clear(context.TODO())) - }) - - when("daemon", func() { - when("--run-image", func() { - var runAfter string - - it.Before(func() { - runAfter = registryConfig.RepoName("run-after/" + h.RandString(10)) - buildRunImage(runAfter, "contents-after-1", "contents-after-2") - }) - - it.After(func() { - imageManager.CleanupImages(runAfter) - }) - - it("uses provided run image", func() { - output := pack.RunSuccessfully( - "rebase", repoName, - "--run-image", runAfter, - "--pull-policy", "never", - ) - - assert.Contains(output, fmt.Sprintf("Successfully rebased image '%s'", repoName)) - assertImage.RunsWithOutput( - repoName, - "contents-after-1", - "contents-after-2", - ) - }) - }) - - when("local config has a mirror", func() { - var localRunImageMirror string - - it.Before(func() { - localRunImageMirror = registryConfig.RepoName("run-after/" + h.RandString(10)) - buildRunImage(localRunImageMirror, "local-mirror-after-1", "local-mirror-after-2") - pack.JustRunSuccessfully("config", "run-image-mirrors", "add", runImage, "-m", localRunImageMirror) - }) - - it.After(func() { - imageManager.CleanupImages(localRunImageMirror) - }) - - it("prefers the local mirror", func() { - output := pack.RunSuccessfully("rebase", repoName, "--pull-policy", "never") - - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsSelectingRunImageMirrorFromLocalConfig(localRunImageMirror) - assertOutput.ReportsSuccessfulRebase(repoName) - assertImage.RunsWithOutput( - repoName, - "local-mirror-after-1", - "local-mirror-after-2", - ) - }) - }) - - when("image metadata has a mirror", func() { - it.Before(func() { - // clean up existing mirror first to avoid leaking images - imageManager.CleanupImages(runImageMirror) - - buildRunImage(runImageMirror, "mirror-after-1", "mirror-after-2") - }) - - it("selects the best mirror", func() { - output := pack.RunSuccessfully("rebase", repoName, "--pull-policy", "never") - - assertOutput := assertions.NewOutputAssertionManager(t, output) - assertOutput.ReportsSelectingRunImageMirror(runImageMirror) - assertOutput.ReportsSuccessfulRebase(repoName) - assertImage.RunsWithOutput( - repoName, - "mirror-after-1", - "mirror-after-2", - ) - }) - }) - }) - - when("--publish", func() { - it.Before(func() { - assert.Succeeds(h.PushImage(dockerCli, repoName, registryConfig)) - }) - - when("--run-image", func() { - var runAfter string - - it.Before(func() { - runAfter = registryConfig.RepoName("run-after/" + h.RandString(10)) - buildRunImage(runAfter, "contents-after-1", "contents-after-2") - assert.Succeeds(h.PushImage(dockerCli, runAfter, registryConfig)) - }) - - it.After(func() { - imageManager.CleanupImages(runAfter) - }) - - it("uses provided run image", func() { - output := pack.RunSuccessfully("rebase", repoName, "--publish", "--run-image", runAfter) - - assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulRebase(repoName) - assertImage.CanBePulledFromRegistry(repoName) - assertImage.RunsWithOutput( - repoName, - "contents-after-1", - "contents-after-2", - ) - }) - }) - }) - }) }) }) } @@ -2618,44 +1892,35 @@ func createComplexBuilder(t *testing.T, fixtureManager := pack.FixtureManager() - nestedLevelOneConfigFile, err := ioutil.TempFile(tmpDir, "nested-level-1-package.toml") - assert.Nil(err) - fixtureManager.TemplateFixtureToFile( + nestedLevelOneConfig := fixtureManager.TemplateFixtureToFile( + tmpDir, "nested-level-1-buildpack_package.toml", - nestedLevelOneConfigFile, templateMapping, ) - err = nestedLevelOneConfigFile.Close() - assert.Nil(err) - nestedLevelTwoConfigFile, err := ioutil.TempFile(tmpDir, "nested-level-2-package.toml") - assert.Nil(err) - fixtureManager.TemplateFixtureToFile( + nestedLevelTwoConfig := fixtureManager.TemplateFixtureToFile( + tmpDir, "nested-level-2-buildpack_package.toml", - nestedLevelTwoConfigFile, templateMapping, ) - err = nestedLevelTwoConfigFile.Close() - assert.Nil(err) - packageImageBuildpack := buildpacks.NewPackageImage( t, - pack, + *pack, packageImageName, - nestedLevelOneConfigFile.Name(), + nestedLevelOneConfig, buildpacks.WithRequiredBuildpacks( buildpacks.NestedLevelOne, buildpacks.NewPackageImage( t, - pack, + *pack, nestedLevelTwoBuildpackName, - nestedLevelTwoConfigFile.Name(), + nestedLevelTwoConfig, buildpacks.WithRequiredBuildpacks( buildpacks.NestedLevelTwo, buildpacks.NewPackageImage( t, - pack, + *pack, simpleLayersBuildpackName, fixtureManager.FixtureLocation("simple-layers-buildpack_package.toml"), buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayers), @@ -2667,7 +1932,7 @@ func createComplexBuilder(t *testing.T, simpleLayersDifferentShaBuildpack := buildpacks.NewPackageImage( t, - pack, + *pack, simpleLayersBuildpackDifferentShaName, fixtureManager.FixtureLocation("simple-layers-buildpack-different-sha_package.toml"), buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayersDifferentSha), @@ -2695,17 +1960,11 @@ func createComplexBuilder(t *testing.T, } // RENDER builder.toml - builderConfigFile, err := ioutil.TempFile(tmpDir, "nested_builder.toml") - if err != nil { - return "", err - } - - pack.FixtureManager().TemplateFixtureToFile("nested_builder.toml", builderConfigFile, templateMapping) - - err = builderConfigFile.Close() - if err != nil { - return "", err - } + builderConfig := pack.FixtureManager().TemplateFixtureToFile( + tmpDir, + "nested_builder.toml", + templateMapping, + ) // NAME BUILDER bldr := registryConfig.RepoName("test/builder-" + h.RandString(10)) @@ -2713,7 +1972,7 @@ func createComplexBuilder(t *testing.T, // CREATE BUILDER output := pack.RunSuccessfully( "builder", "create", bldr, - "-c", builderConfigFile.Name(), + "-c", builderConfig, "--no-color", ) @@ -2726,10 +1985,12 @@ func createComplexBuilder(t *testing.T, func createBuilder( t *testing.T, assert h.AssertionManager, + imageManager managers.ImageManager, + dockerCli client.CommonAPIClient, pack *invoke.PackInvoker, lifecycle config.LifecycleAsset, buildpackManager buildpacks.BuildpackManager, - runImageMirror string, + stack config.Stack, ) (string, error) { t.Log("creating builder image...") @@ -2739,7 +2000,9 @@ func createBuilder( defer os.RemoveAll(tmpDir) templateMapping := map[string]interface{}{ - "run_image_mirror": runImageMirror, + "build_image": stack.BuildImageName, + "run_image": stack.RunImage.Name, + "run_image_mirror": stack.RunImage.MirrorName, } // ARCHIVE BUILDPACKS @@ -2750,12 +2013,12 @@ func createBuilder( buildpacks.ReadEnv, } - packageTomlPath := generatePackageTomlWithOS(t, assert, pack, tmpDir, "package.toml", imageManager.HostOS()) + packageTomlPath := generatePackageTomlWithOS(t, pack, tmpDir, "package.toml", imageManager.HostOS()) packageImageName := registryConfig.RepoName("simple-layers-package-image-buildpack-" + h.RandString(8)) packageImageBuildpack := buildpacks.NewPackageImage( t, - pack, + *pack, packageImageName, packageTomlPath, buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayers), @@ -2784,27 +2047,19 @@ func createBuilder( } // RENDER builder.toml - configFileName := "builder.toml" - - builderConfigFile, err := ioutil.TempFile(tmpDir, "builder.toml") - assert.Nil(err) - - pack.FixtureManager().TemplateFixtureToFile( - configFileName, - builderConfigFile, + builderConfig := pack.FixtureManager().TemplateFixtureToFile( + tmpDir, + "builder.toml", templateMapping, ) - err = builderConfigFile.Close() - assert.Nil(err) - // NAME BUILDER bldr := registryConfig.RepoName("test/builder-" + h.RandString(10)) // CREATE BUILDER output := pack.RunSuccessfully( "builder", "create", bldr, - "-c", builderConfigFile.Name(), + "-c", builderConfig, "--no-color", ) @@ -2816,31 +2071,23 @@ func createBuilder( func generatePackageTomlWithOS( t *testing.T, - assert h.AssertionManager, pack *invoke.PackInvoker, tmpDir string, fixtureName string, - platform_os string, + platformOS string, ) string { t.Helper() - packageTomlFile, err := ioutil.TempFile(tmpDir, "package-*.toml") - assert.Nil(err) - - pack.FixtureManager().TemplateFixtureToFile( + return pack.FixtureManager().TemplateFixtureToFile( + tmpDir, fixtureName, - packageTomlFile, map[string]interface{}{ - "OS": platform_os, + "OS": platformOS, }, ) - - assert.Nil(packageTomlFile.Close()) - - return packageTomlFile.Name() } -func createStack(t *testing.T, dockerCli client.CommonAPIClient, runImageMirror string) error { +func createStack(t *testing.T, dockerCli client.CommonAPIClient, imageManager managers.ImageManager, runImageMirror string) error { t.Helper() t.Log("creating stack images...") diff --git a/acceptance/buildpacks/manager.go b/acceptance/buildpacks/manager.go index eafd80a88..1aa4f3154 100644 --- a/acceptance/buildpacks/manager.go +++ b/acceptance/buildpacks/manager.go @@ -8,21 +8,27 @@ import ( "testing" "github.com/buildpacks/pack/internal/builder" - "github.com/buildpacks/pack/testhelpers" ) type BuildpackManager struct { testObject *testing.T assert testhelpers.AssertionManager - sourceDir string + baseDir string + apiVersion string } type BuildpackManagerModifier func(b *BuildpackManager) +func WithBaseDir(baseDir string) func(b *BuildpackManager) { + return func(b *BuildpackManager) { + b.baseDir = baseDir + } +} + func WithBuildpackAPIVersion(apiVersion string) func(b *BuildpackManager) { return func(b *BuildpackManager) { - b.sourceDir = filepath.Join("testdata", "mock_buildpacks", apiVersion) + b.apiVersion = apiVersion } } @@ -30,7 +36,8 @@ func NewBuildpackManager(t *testing.T, assert testhelpers.AssertionManager, modi m := BuildpackManager{ testObject: t, assert: assert, - sourceDir: filepath.Join("testdata", "mock_buildpacks", builder.DefaultBuildpackAPIVersion), + baseDir: filepath.Join("testdata", "mock_buildpacks"), + apiVersion: builder.DefaultBuildpackAPIVersion, } for _, mod := range modifiers { @@ -48,7 +55,7 @@ func (b BuildpackManager) PrepareBuildpacks(destination string, buildpacks ...Te b.testObject.Helper() for _, buildpack := range buildpacks { - err := buildpack.Prepare(b.sourceDir, destination) + err := buildpack.Prepare(filepath.Join(b.baseDir, b.apiVersion), destination) b.assert.Nil(err) } } diff --git a/acceptance/buildpacks/package_file_buildpack.go b/acceptance/buildpacks/package_file_buildpack.go index ce9d29112..e0e8236bf 100644 --- a/acceptance/buildpacks/package_file_buildpack.go +++ b/acceptance/buildpacks/package_file_buildpack.go @@ -18,7 +18,7 @@ import ( type PackageFile struct { testObject *testing.T - pack *invoke.PackInvoker + pack invoke.PackInvoker destination string sourceConfigLocation string buildpacks []TestBuildpack @@ -32,7 +32,7 @@ func (p *PackageFile) SetPublish() {} func NewPackageFile( t *testing.T, - pack *invoke.PackInvoker, + pack invoke.PackInvoker, destination, configLocation string, modifiers ...PackageModifier, ) PackageFile { diff --git a/acceptance/buildpacks/package_image_buildpack.go b/acceptance/buildpacks/package_image_buildpack.go index a924e9bc6..00c7900b5 100644 --- a/acceptance/buildpacks/package_image_buildpack.go +++ b/acceptance/buildpacks/package_image_buildpack.go @@ -19,7 +19,7 @@ import ( type PackageImage struct { testObject *testing.T - pack *invoke.PackInvoker + pack invoke.PackInvoker name string sourceConfigLocation string buildpacks []TestBuildpack @@ -36,7 +36,7 @@ func (p *PackageImage) SetPublish() { func NewPackageImage( t *testing.T, - pack *invoke.PackInvoker, + pack invoke.PackInvoker, name, configLocation string, modifiers ...PackageModifier, ) PackageImage { diff --git a/acceptance/config/asset_manager.go b/acceptance/config/asset_manager.go index 7a6ea0c4a..af6e802f9 100644 --- a/acceptance/config/asset_manager.go +++ b/acceptance/config/asset_manager.go @@ -6,7 +6,6 @@ package config import ( "fmt" "io/ioutil" - "os" "os/exec" "path/filepath" "regexp" @@ -25,12 +24,11 @@ const ( ) var ( - currentPackFixturesDir = filepath.Join("testdata", "pack_fixtures") - previousPackFixturesOverridesDir = filepath.Join("testdata", "pack_previous_fixtures_overrides") - lifecycleTgzExp = regexp.MustCompile(`lifecycle-v\d+.\d+.\d+\+linux.x86-64.tgz`) + lifecycleTgzExp = regexp.MustCompile(`lifecycle-v\d+.\d+.\d+\+linux.x86-64.tgz`) ) type AssetManager struct { + testObject *testing.T packPath string packFixturesPath string previousPackPath string @@ -43,10 +41,9 @@ type AssetManager struct { previousLifecycleDescriptor builder.LifecycleDescriptor previousLifecycleImage string defaultLifecycleDescriptor builder.LifecycleDescriptor - testObject *testing.T } -func ConvergedAssetManager(t *testing.T, assert h.AssertionManager, inputConfig InputConfigurationManager) AssetManager { +func NewAssetManager(t *testing.T, assert h.AssertionManager, inputConfig InputConfigurationManager, projectBaseDir string) AssetManager { t.Helper() var ( @@ -63,10 +60,11 @@ func ConvergedAssetManager(t *testing.T, assert h.AssertionManager, inputConfig ) githubAssetFetcher, err := NewGithubAssetFetcher(t, inputConfig.githubToken) - h.AssertNil(t, err) + assert.Nil(err) assetBuilder := assetManagerBuilder{ testObject: t, + projectBaseDir: projectBaseDir, assert: assert, inputConfig: inputConfig, githubAssetFetcher: githubAssetFetcher, @@ -78,9 +76,10 @@ func ConvergedAssetManager(t *testing.T, assert h.AssertionManager, inputConfig if inputConfig.combinations.requiresPreviousPack() { convergedPreviousPackPath = assetBuilder.ensurePreviousPack() - convergedPreviousPackFixturesPath := assetBuilder.ensurePreviousPackFixtures() - - convergedPreviousPackFixturesPaths = []string{previousPackFixturesOverridesDir, convergedPreviousPackFixturesPath} + convergedPreviousPackFixturesPaths = []string{ + filepath.Join(projectBaseDir, "acceptance", "testdata", "pack_previous_fixtures_overrides"), + assetBuilder.ensurePreviousPackFixtures(), + } } if inputConfig.combinations.requiresCurrentLifecycle() { @@ -96,8 +95,9 @@ func ConvergedAssetManager(t *testing.T, assert h.AssertionManager, inputConfig } return AssetManager{ + testObject: t, packPath: convergedCurrentPackPath, - packFixturesPath: currentPackFixturesDir, + packFixturesPath: filepath.Join(projectBaseDir, "acceptance", "testdata", "pack_fixtures"), previousPackPath: convergedPreviousPackPath, previousPackFixturesPaths: convergedPreviousPackFixturesPaths, lifecyclePath: convergedCurrentLifecyclePath, @@ -107,7 +107,6 @@ func ConvergedAssetManager(t *testing.T, assert h.AssertionManager, inputConfig previousLifecycleImage: convergedPreviousLifecycleImage, previousLifecycleDescriptor: convergedPreviousLifecycleDescriptor, defaultLifecycleDescriptor: convergedDefaultLifecycleDescriptor, - testObject: t, } } @@ -178,6 +177,7 @@ func (a AssetManager) LifecycleImage(kind ComboValue) string { type assetManagerBuilder struct { testObject *testing.T + projectBaseDir string assert h.AssertionManager inputConfig InputConfigurationManager githubAssetFetcher *GithubAssetFetcher @@ -351,20 +351,17 @@ func (b assetManagerBuilder) buildPack(compileVersion string) string { packPath := filepath.Join(packTmpDir, acceptanceOS.PackBinaryName) - cwd, err := os.Getwd() - b.assert.Nil(err) - cmd := exec.Command("go", "build", "-ldflags", fmt.Sprintf("-X 'github.com/buildpacks/pack/cmd.Version=%s'", compileVersion), "-o", packPath, "./cmd/pack", ) - if filepath.Base(cwd) == "acceptance" { - cmd.Dir = filepath.Dir(cwd) - } + + cmd.Dir = b.projectBaseDir b.testObject.Logf("building pack: [CWD=%s] %s", cmd.Dir, cmd.Args) - _, err = cmd.CombinedOutput() + output, err := cmd.CombinedOutput() + b.testObject.Logf("output:\n%s", string(output)) b.assert.Nil(err) return packPath diff --git a/acceptance/config/github_asset_fetcher.go b/acceptance/config/github_asset_fetcher.go index 3b490178e..227fc5ec5 100644 --- a/acceptance/config/github_asset_fetcher.go +++ b/acceptance/config/github_asset_fetcher.go @@ -28,7 +28,6 @@ import ( "golang.org/x/oauth2" "github.com/buildpacks/pack/pkg/blob" - "github.com/buildpacks/pack/pkg/logging" ) const ( @@ -377,7 +376,7 @@ func (f *GithubAssetFetcher) writeCacheManifest(owner, repo string, op func(cach func (f *GithubAssetFetcher) downloadAndSave(assetURI, destPath string) error { f.testObject.Helper() - downloader := blob.NewDownloader(logging.NewSimpleLogger(&testWriter{t: f.testObject}), f.cacheDir) + downloader := blob.NewDownloader(&testLogger{t: f.testObject}, f.cacheDir) assetBlob, err := downloader.Download(f.ctx, assetURI) if err != nil { @@ -406,7 +405,7 @@ func (f *GithubAssetFetcher) downloadAndSave(assetURI, destPath string) error { func (f *GithubAssetFetcher) downloadAndExtractTgz(assetURI, destDir string) error { f.testObject.Helper() - downloader := blob.NewDownloader(logging.NewSimpleLogger(&testWriter{t: f.testObject}), f.cacheDir) + downloader := blob.NewDownloader(&testLogger{t: f.testObject}, f.cacheDir) assetBlob, err := downloader.Download(f.ctx, assetURI) if err != nil { @@ -528,3 +527,20 @@ func (w *testWriter) Write(p []byte) (n int, err error) { w.t.Log(string(p)) return len(p), nil } + +// testLogger implements the logger interface for testing purposes. +type testLogger struct { + t *testing.T +} + +func (l *testLogger) Debugf(format string, args ...interface{}) { + l.t.Logf(format, args...) +} + +func (l *testLogger) Infof(format string, args ...interface{}) { + l.t.Logf(format, args...) +} + +func (l *testLogger) Writer() io.Writer { + return &testWriter{t: l.t} +} diff --git a/acceptance/config/stack.go b/acceptance/config/stack.go new file mode 100644 index 000000000..ea446cb6b --- /dev/null +++ b/acceptance/config/stack.go @@ -0,0 +1,14 @@ +//go:build acceptance +// +build acceptance + +package config + +type Stack struct { + RunImage RunImage + BuildImageName string +} + +type RunImage struct { + Name string + MirrorName string +} diff --git a/acceptance/harness/builder_harness.go b/acceptance/harness/builder_harness.go new file mode 100644 index 000000000..e241f87d0 --- /dev/null +++ b/acceptance/harness/builder_harness.go @@ -0,0 +1,246 @@ +//go:build acceptance +// +build acceptance + +package harness + +import ( + "fmt" + "math/rand" + "path/filepath" + "reflect" + "runtime" + "testing" + "time" + + "github.com/docker/docker/client" + + "github.com/buildpacks/pack/acceptance/buildpacks" + "github.com/buildpacks/pack/acceptance/config" + "github.com/buildpacks/pack/acceptance/invoke" + "github.com/buildpacks/pack/acceptance/managers" + "github.com/buildpacks/pack/internal/style" + h "github.com/buildpacks/pack/testhelpers" +) + +type BuilderCombo struct { + comboConfig config.RunCombo + builderName string + pack invoke.PackInvoker + lifecycle config.LifecycleAsset +} + +func (b *BuilderCombo) Config() config.RunCombo { + return b.comboConfig +} + +func (b *BuilderCombo) BuilderName() string { + return b.builderName +} + +func (b *BuilderCombo) Pack() invoke.PackInvoker { + return b.pack +} + +func (b *BuilderCombo) Lifecycle() config.LifecycleAsset { + return b.lifecycle +} + +type BuilderTestHarness struct { + t *testing.T + registryConfig h.TestRegistryConfig + imageManager managers.ImageManager + stack config.Stack + combos []BuilderCombo + cleanups []func() +} + +func ContainingBuilder(t *testing.T, projectBaseDir string) BuilderTestHarness { + t.Helper() + h.RequireDocker(t) + rand.Seed(time.Now().UTC().UnixNano()) + + var err error + cleanups := []func(){} + assert := h.NewAssertionManager(t) + + projectBaseDir, err = filepath.Abs(projectBaseDir) + assert.Nil(err) + + testDataDir := filepath.Join(projectBaseDir, "acceptance", "testdata") + + // docker cli + dockerCli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38")) + assert.Nil(err) + + imageManager := managers.NewImageManager(t, dockerCli) + + // run temp registry + registry := h.RunRegistry(t) + cleanups = append(cleanups, func() { + t.Log("stoping and deleting registry...") + registry.RmRegistry(t) + }) + + // gather config + inputConfigManager, err := config.NewInputConfigurationManager() + assert.Nil(err) + + assetsConfig := config.NewAssetManager(t, assert, inputConfigManager, projectBaseDir) + + // create stack + stack, err := createStack( + t, + dockerCli, + registry, + imageManager, + "pack-test/run:"+h.RandString(6), + "pack-test/build:"+h.RandString(6), + testDataDir, + ) + assert.Nil(err) + + stackBaseImages := map[string][]string{ + "linux": {"ubuntu:bionic"}, + "windows": {"mcr.microsoft.com/windows/nanoserver:1809", "golang:1.17-nanoserver-1809"}, + } + baseStackNames := stackBaseImages[imageManager.HostOS()] + cleanups = append(cleanups, func() { + t.Log("cleaning up stack images...") + imageManager.CleanupImages(baseStackNames...) + imageManager.CleanupImages(stack.RunImage.Name, stack.BuildImageName, stack.RunImage.MirrorName) + }) + + combos := []BuilderCombo{} + for _, combo := range inputConfigManager.Combinations() { + t.Logf("creating assets for combo: %v", combo) + lifecycle := assetsConfig.NewLifecycleAsset(combo.Lifecycle) + pack := invoke.NewPackInvoker( + t, + assert, + assetsConfig.NewPackAsset(combo.Pack), + registry.DockerConfigDir, + ) + + pack.JustRunSuccessfully("config", "lifecycle-image", lifecycle.Image()) + pack.EnableExperimental() + + buildpackManager := buildpacks.NewBuildpackManager( + t, + assert, + buildpacks.WithBuildpackAPIVersion(lifecycle.EarliestBuildpackAPIVersion()), + buildpacks.WithBaseDir(filepath.Join(testDataDir, "mock_buildpacks")), + ) + + createBuilderPack := invoke.NewPackInvoker( + t, + assert, + assetsConfig.NewPackAsset(combo.PackCreateBuilder), + registry.DockerConfigDir, + ) + createBuilderPack.EnableExperimental() + + // create builder + builderName, err := createBuilder( + t, + assert, + registry, + imageManager, + dockerCli, + *createBuilderPack, + lifecycle, + buildpackManager, + stack, + ) + assert.Nil(err) + cleanups = append(cleanups, func() { + t.Logf("cleaning up builder: %s", style.Symbol(builderName)) + imageManager.CleanupImages(builderName) + }) + + pack.JustRunSuccessfully("config", "default-builder", builderName) + + combos = append(combos, BuilderCombo{ + comboConfig: *combo, + builderName: builderName, + lifecycle: lifecycle, + pack: *pack, + }) + } + + return BuilderTestHarness{ + t: t, + registryConfig: *registry, + imageManager: imageManager, + stack: stack, + combos: combos, + cleanups: cleanups, + } +} + +func (b *BuilderTestHarness) Combinations() []BuilderCombo { + return b.combos +} + +func (b *BuilderTestHarness) Run(name string, test func(t *testing.T, th *BuilderTestHarness, combo BuilderCombo)) { + for _, combo := range b.combos { + combo := combo + b.t.Run(runComboToPath(&combo.comboConfig)+name, func(t *testing.T) { + test(t, b, combo) + }) + } +} + +func (b *BuilderTestHarness) RunC(test func(combo BuilderCombo)) { + b.Run(funcName(test), func(t *testing.T, th *BuilderTestHarness, combo BuilderCombo) { + test(combo) + }) +} + +func (b *BuilderTestHarness) RunTC(test func(t *testing.T, combo BuilderCombo)) { + b.Run(funcName(test), func(t *testing.T, th *BuilderTestHarness, combo BuilderCombo) { + test(t, combo) + }) +} + +func (b *BuilderTestHarness) RunA(test func(t *testing.T, th *BuilderTestHarness, combo BuilderCombo)) { + b.Run(funcName(test), test) +} + +func (b *BuilderTestHarness) Registry() h.TestRegistryConfig { + return b.registryConfig +} + +func (b *BuilderTestHarness) ImageManager() managers.ImageManager { + return b.imageManager +} + +func (b *BuilderTestHarness) Stack() config.Stack { + return b.stack +} + +func (b *BuilderTestHarness) CleanUp() { + for _, fn := range b.cleanups { + fn() + } +} + +// funcName gathers the name of the given function and +// formats it to be used as part of the test path +func funcName(fn interface{}) string { + funcName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() + funcName = filepath.Base(funcName) + return funcName +} + +// runComboToPath creates a `go test` path string to use for identifying +// combination tests in test output +// +// ie: "builder:previous/lifecyle:previous/pack:current/" +func runComboToPath(combo *config.RunCombo) string { + return fmt.Sprintf( + "builder:%s/lifecyle:%s/pack:%s/", + combo.PackCreateBuilder, + combo.Lifecycle, + combo.Pack, + ) +} diff --git a/acceptance/harness/util.go b/acceptance/harness/util.go new file mode 100644 index 000000000..20262097b --- /dev/null +++ b/acceptance/harness/util.go @@ -0,0 +1,166 @@ +//go:build acceptance +// +build acceptance + +package harness + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + + "github.com/buildpacks/pack/acceptance/buildpacks" + "github.com/buildpacks/pack/acceptance/config" + "github.com/buildpacks/pack/acceptance/invoke" + "github.com/buildpacks/pack/acceptance/managers" + "github.com/buildpacks/pack/pkg/archive" + h "github.com/buildpacks/pack/testhelpers" +) + +func createBuilder( + t *testing.T, + assert h.AssertionManager, + registryConfig *h.TestRegistryConfig, + imageManager managers.ImageManager, + dockerCli client.CommonAPIClient, + pack invoke.PackInvoker, + lifecycle config.LifecycleAsset, + buildpackManager buildpacks.BuildpackManager, + stack config.Stack, +) (string, error) { + t.Log("creating builder image...") + + // CREATE TEMP WORKING DIR + tmpDir, err := ioutil.TempDir("", "create-test-builder") + assert.Nil(err) + defer os.RemoveAll(tmpDir) + + // ARCHIVE BUILDPACKS + builderBuildpacks := []buildpacks.TestBuildpack{ + buildpacks.Noop, + buildpacks.Noop2, + buildpacks.OtherStack, + buildpacks.ReadEnv, + } + + packageTomlPath := pack.FixtureManager().TemplateFixtureToFile( + tmpDir, + "package.toml", + map[string]interface{}{ + "OS": imageManager.HostOS(), + }, + ) + defer os.RemoveAll(packageTomlPath) + + packageImageName := registryConfig.RepoName("simple-layers-package-image-buildpack-" + h.RandString(8)) + + packageImageBuildpack := buildpacks.NewPackageImage( + t, + pack, + packageImageName, + packageTomlPath, + buildpacks.WithRequiredBuildpacks(buildpacks.SimpleLayers), + ) + + defer imageManager.CleanupImages(packageImageName) + + builderBuildpacks = append(builderBuildpacks, packageImageBuildpack) + + templateMapping := map[string]interface{}{ + "build_image": stack.BuildImageName, + "run_image": stack.RunImage.Name, + "run_image_mirror": stack.RunImage.MirrorName, + "package_image_name": packageImageName, + "package_id": "simple/layers", + } + + buildpackManager.PrepareBuildpacks(tmpDir, builderBuildpacks...) + + // ADD lifecycle + var lifecycleURI string + var lifecycleVersion string + if lifecycle.HasLocation() { + lifecycleURI = lifecycle.EscapedPath() + t.Logf("adding lifecycle path '%s' to builder config", lifecycleURI) + templateMapping["lifecycle_uri"] = lifecycleURI + } else { + lifecycleVersion = lifecycle.Version() + t.Logf("adding lifecycle version '%s' to builder config", lifecycleVersion) + templateMapping["lifecycle_version"] = lifecycleVersion + } + + // RENDER builder.toml + builderConfig := pack.FixtureManager().TemplateVersionedFixtureToFile( + tmpDir, + filepath.Join("%s", "builder.toml"), + pack.SanitizedVersion(), + "builder.toml", + templateMapping, + ) + + // NAME BUILDER + bldr := registryConfig.RepoName("test/builder-" + h.RandString(10)) + + // CREATE BUILDER + output := pack.RunSuccessfully( + "builder", "create", bldr, + "-c", builderConfig, + "--no-color", + ) + + assert.Contains(output, fmt.Sprintf("Successfully created builder image '%s'", bldr)) + assert.Succeeds(h.PushImage(dockerCli, bldr, registryConfig)) + + return bldr, nil +} + +func createStack(t *testing.T, dockerCli client.CommonAPIClient, registry *h.TestRegistryConfig, imageManager managers.ImageManager, runImageName, buildImageName, testDataDir string) (config.Stack, error) { + t.Helper() + t.Log("creating stack images...") + + stackBaseDir := filepath.Join(testDataDir, "mock_stack", imageManager.HostOS()) + + _, err := os.Stat(stackBaseDir) + if err != nil { + return config.Stack{}, err + } + + if err := createStackImage(dockerCli, runImageName, filepath.Join(stackBaseDir, "run")); err != nil { + return config.Stack{}, err + } + if err := createStackImage(dockerCli, buildImageName, filepath.Join(stackBaseDir, "build")); err != nil { + return config.Stack{}, err + } + + runImageMirror := registry.RepoName(runImageName) + imageManager.TagImage(runImageName, runImageMirror) + if err := h.PushImage(dockerCli, runImageMirror, registry); err != nil { + return config.Stack{}, err + } + + return config.Stack{ + RunImage: config.RunImage{ + Name: runImageName, + MirrorName: runImageMirror, + }, + BuildImageName: buildImageName, + }, nil +} + +func createStackImage(dockerCli client.CommonAPIClient, repoName string, dir string) error { + defaultFilterFunc := func(file string) bool { return true } + + ctx := context.Background() + buildContext := archive.ReadDirAsTar(dir, "/", 0, 0, -1, true, false, defaultFilterFunc) + + return h.CheckImageBuildResult(dockerCli.ImageBuild(ctx, buildContext, dockertypes.ImageBuildOptions{ + Tags: []string{repoName}, + Remove: true, + ForceRemove: true, + })) +} diff --git a/acceptance/invoke/pack.go b/acceptance/invoke/pack.go index 333dbb054..611780b2e 100644 --- a/acceptance/invoke/pack.go +++ b/acceptance/invoke/pack.go @@ -5,6 +5,8 @@ package invoke import ( "bytes" + "crypto/sha256" + "encoding/hex" "fmt" "io/ioutil" "os" @@ -22,6 +24,7 @@ import ( ) type PackInvoker struct { + identifier string testObject *testing.T assert h.AssertionManager path string @@ -50,7 +53,15 @@ func NewPackInvoker( testObject.Fatalf("couldn't create home folder for pack: %s", err) } + hash := sha256.New() + hash.Write([]byte(packAssets.Path())) + for _, v := range packAssets.FixturePaths() { + hash.Write([]byte(v)) + } + identifier := hex.EncodeToString(hash.Sum(nil)) + return &PackInvoker{ + identifier: identifier, testObject: testObject, assert: assert, path: packAssets.Path(), @@ -94,6 +105,10 @@ func (i *PackInvoker) cmd(name string, args ...string) *exec.Cmd { return cmd } +func (i *PackInvoker) Identifier() string { + return i.identifier +} + func (i *PackInvoker) baseCmd(parts ...string) *exec.Cmd { return exec.Command(i.path, parts...) } @@ -211,11 +226,44 @@ func (i *PackInvoker) Supports(command string) bool { search = command } - re := regexp.MustCompile(fmt.Sprint(`\b%s\b`, search)) output, err := i.baseCmd(cmdParts...).CombinedOutput() i.assert.Nil(err) - return re.MatchString(string(output)) && !strings.Contains(string(output), "Unknown help topic") + helpOutput := string(output) + if strings.Contains(helpOutput, "Unknown help topic") { + return false + } + + if search[0:1] == "-" { + return hasFlag(helpOutput, search) + } + + return hasCommand(helpOutput, search) +} + +func hasCommand(helpOutput string, command string) bool { + + commandsSection := "" + if parts := strings.Split(helpOutput, "Available Commands:"); len(parts) > 1 { + commandsSection = parts[1] + } + + if parts := strings.Split(commandsSection, "Flags:"); len(parts) > 1 { + commandsSection = parts[0] + } + + if commandsSection == "" { + return false + } + + expression := regexp.MustCompile(fmt.Sprintf(`\t\b%s\b`, command)) + return expression.MatchString(commandsSection) +} + +func hasFlag(helpOutput string, flag string) bool { + pattern := fmt.Sprintf(`\s%s\s`, flag) + expression := regexp.MustCompile(pattern) + return expression.MatchString(helpOutput) } type Feature int diff --git a/acceptance/invoke/pack_fixtures.go b/acceptance/invoke/pack_fixtures.go index 367ebd390..84064ce21 100644 --- a/acceptance/invoke/pack_fixtures.go +++ b/acceptance/invoke/pack_fixtures.go @@ -46,6 +46,7 @@ func (m PackFixtureManager) VersionedFixtureOrFallbackLocation(pattern, version, for _, dir := range m.locations { fixtureLocation := filepath.Join(dir, versionedName) + m.testObject.Logf("looking up possible fixture at: %s", fixtureLocation) _, err := os.Stat(fixtureLocation) if !os.IsNotExist(err) { return fixtureLocation @@ -75,9 +76,32 @@ func (m PackFixtureManager) TemplateVersionedFixture( return m.fillTemplate(outputTemplate, templateData) } -func (m PackFixtureManager) TemplateFixtureToFile(name string, destination *os.File, data map[string]interface{}) { - _, err := io.WriteString(destination, m.TemplateFixture(name, data)) +func (m PackFixtureManager) TemplateVersionedFixtureToFile( + tmpDir, versionedPattern, version, fallback string, + templateData map[string]interface{}, +) string { + m.testObject.Helper() + fixturePath := m.VersionedFixtureOrFallbackLocation(versionedPattern, version, fallback) + m.testObject.Logf("using %s for fixture %s", fixturePath, fallback) + + outputTemplate, err := ioutil.ReadFile(fixturePath) + m.assert.Nil(err) + + tmpFile, err := ioutil.TempFile(tmpDir, "*-"+fallback) + defer tmpFile.Close() + + _, err = io.WriteString(tmpFile, m.fillTemplate(outputTemplate, templateData)) + m.assert.Nil(err) + return tmpFile.Name() +} + +func (m PackFixtureManager) TemplateFixtureToFile(tmpDir string, fixture string, data map[string]interface{}) string { + tmpFile, err := ioutil.TempFile(tmpDir, fixture+"-*") + defer tmpFile.Close() + + _, err = io.WriteString(tmpFile, m.TemplateFixture(fixture, data)) m.assert.Nil(err) + return tmpFile.Name() } func (m PackFixtureManager) fillTemplate(templateContents []byte, data map[string]interface{}) string { diff --git a/acceptance/invoke/pack_test.go b/acceptance/invoke/pack_test.go new file mode 100644 index 000000000..e9915be89 --- /dev/null +++ b/acceptance/invoke/pack_test.go @@ -0,0 +1,41 @@ +//go:build acceptance +// +build acceptance + +package invoke + +import ( + "io/ioutil" + "testing" + + "github.com/buildpacks/pack/testhelpers" +) + +func TestPack(t *testing.T) { + assert := testhelpers.NewAssertionManager(t) + + t.Run("hasFlag:exists", func(t *testing.T) { + packBuildHelp, err := ioutil.ReadFile("testdata/pack_build_help.txt") + assert.Nil(err) + assert.Equal(hasFlag(string(packBuildHelp), "--quiet"), true) + }) + + t.Run("hasFlag:non-existent", func(t *testing.T) { + packBuildHelp, err := ioutil.ReadFile("testdata/pack_build_help.txt") + assert.Nil(err) + assert.Equal(hasFlag(string(packBuildHelp), "--non-existent"), false) + }) + + t.Run("hasCommand:exists", func(t *testing.T) { + packHelp, err := ioutil.ReadFile("testdata/pack_help.txt") + assert.Nil(err) + assert.Equal(hasCommand(string(packHelp), "build"), true) + }) + + t.Run("hasCommand:non-existent", func(t *testing.T) { + packHelp, err := ioutil.ReadFile("testdata/pack_help.txt") + assert.Nil(err) + // we use a word that is not in the help text to + // make sure we're not just getting lucky + assert.Equal(hasCommand(string(packHelp), "building"), false) + }) +} diff --git a/acceptance/invoke/testdata/pack_build_help.txt b/acceptance/invoke/testdata/pack_build_help.txt new file mode 100644 index 000000000..c6090544e --- /dev/null +++ b/acceptance/invoke/testdata/pack_build_help.txt @@ -0,0 +1,73 @@ +Pack Build uses Cloud Native Buildpacks to create a runnable app image from source code. + +Pack Build requires an image name, which will be generated from the source code. Build defaults to the current directory, but you can use '--path' to specify another source code directory. Build requires a 'builder', which can either be provided directly to build using '--builder', or can be set using the 'set-default-builder' command. For more on how to use 'pack build', see: https://buildpacks.io/docs/app-developer-guide/build-an-app/. + +Usage: + pack build [flags] + +Examples: +pack build test_img --path apps/test-app --builder cnbs/sample-builder:bionic + +Flags: + -B, --builder string Builder image (default "paketobuildpacks/builder:tiny") + -b, --buildpack strings Buildpack to use. One of: + a buildpack by id and version in the form of '@', + path to a buildpack directory (not supported on Windows), + path/URL to a buildpack .tar or .tgz file, or + a packaged buildpack image name in the form of '/[:]' + Repeat for each buildpack in order, or supply once by comma-separated list + -r, --buildpack-registry string Buildpack Registry by name + --cache-image string Cache build layers in remote registry. Requires --publish + --clear-cache Clear image's associated cache before building + --creation-time string Desired create time in the output image config. Accepted values are Unix timestamps (e.g., '1641013200'), or 'now'. Platform API version must be at least 0.9 to use this feature. + -D, --default-process string Set the default process type. (default "web") + -d, --descriptor string Path to the project descriptor file + --docker-host string Address to docker daemon that will be exposed to the build container. + If not set (or set to empty string) the standard socket location will be used. + Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used. + This option may set DOCKER_HOST environment variable for the build container if needed. + + -e, --env stringArray Build-time environment variable, in the form 'VAR=VALUE' or 'VAR'. + When using latter value-less form, value will be taken from current + environment at the time this command is executed. + This flag may be specified multiple times and will override + individual values defined by --env-file. + Repeat for each env in order (comma-separated lists not accepted) + NOTE: These are NOT available at image runtime. + --env-file stringArray Build-time environment variables file + One variable per line, of the form 'VAR=VALUE' or 'VAR' + When using latter value-less form, value will be taken from current + environment at the time this command is executed + NOTE: These are NOT available at image runtime." + --gid int Override GID of user's group in the stack's build and run images. The provided value must be a positive number + -h, --help Help for 'build' + --interactive Launch a terminal UI to depict the build process + --lifecycle-image string Custom lifecycle image to use for analysis, restore, and export when builder is untrusted. + --network string Connect detect and build containers to network + -p, --path string Path to app dir or zip-formatted file (defaults to current working directory) + --previous-image string Set previous image to a particular tag reference, digest reference, or (when performing a daemon build) image ID + --publish Publish to registry + --pull-policy string Pull policy to use. Accepted values are always, never, and if-not-present. (default "always") + --run-image string Run image (defaults to default stack's run image) + --sbom-output-dir string Path to export SBoM contents. + Omitting the flag will yield no SBoM content. + -t, --tag strings Additional tags to push the output image to. + Tags should be in the format 'image:tag' or 'repository/image:tag'. + Repeat for each tag in order, or supply once by comma-separated list + --trust-builder Trust the provided builder + All lifecycle phases will be run in a single container (if supported by the lifecycle). + --volume stringArray Mount host volume into the build container, in the form ':[:]'. + - 'host path': Name of the volume or absolute directory path to mount. + - 'target path': The path where the file or directory is available in the container. + - 'options' (default "ro"): An optional comma separated list of mount options. + - "ro", volume contents are read-only. + - "rw", volume contents are readable and writeable. + - "volume-opt==", can be specified more than once, takes a key-value pair consisting of the option name and its value. + Repeat for each volume in order (comma-separated lists not accepted) + --workspace string Location at which to mount the app dir in the build image + +Global Flags: + --no-color Disable color output + -q, --quiet Show less output + --timestamps Enable timestamps in output + -v, --verbose Show more output \ No newline at end of file diff --git a/acceptance/invoke/testdata/pack_help.txt b/acceptance/invoke/testdata/pack_help.txt new file mode 100644 index 000000000..b2821851f --- /dev/null +++ b/acceptance/invoke/testdata/pack_help.txt @@ -0,0 +1,28 @@ +CLI for building apps using Cloud Native Buildpacks + +Usage: + pack [command] + +Available Commands: + build Generate app image from source code + builder Interact with builders + buildpack Interact with buildpacks + config Interact with your local pack config file + inspect Show information about a built app image + stack Interact with stacks + rebase Rebase app image with latest run image + sbom Interact with SBoM + completion Outputs completion script location + report Display useful information for reporting an issue + version Show current 'pack' version + help Help about any command + +Flags: + -h, --help Help for 'pack' + --no-color Disable color output + -q, --quiet Show less output + --timestamps Enable timestamps in output + -v, --verbose Show more output + --version Show current 'pack' version + +Use "pack [command] --help" for more information about a command. diff --git a/acceptance/managers/image_manager.go b/acceptance/managers/image_manager.go index e3976ba13..a4e24fbe5 100644 --- a/acceptance/managers/image_manager.go +++ b/acceptance/managers/image_manager.go @@ -42,6 +42,10 @@ func (im ImageManager) CleanupImages(imageNames ...string) { } } +func (im ImageManager) CreateImage(name string, dockerfile string) { + h.CreateImage(im.testObject, im.dockerCli, name, dockerfile) +} + func (im ImageManager) InspectLocal(image string) (dockertypes.ImageInspect, error) { im.testObject.Helper() inspect, _, err := im.dockerCli.ImageInspectWithRaw(context.Background(), image) diff --git a/acceptance/testconfig/all.json b/acceptance/testconfig/all.json index 0238d7d02..8be5ed95e 100644 --- a/acceptance/testconfig/all.json +++ b/acceptance/testconfig/all.json @@ -1,7 +1,7 @@ [ - {"pack": "current", "pack_create_builder": "current", "lifecycle": "current"}, - {"pack": "current", "pack_create_builder": "current", "lifecycle": "previous"}, - {"pack": "current", "pack_create_builder": "previous", "lifecycle": "previous"}, - {"pack": "previous", "pack_create_builder": "current", "lifecycle": "current"}, - {"pack": "previous", "pack_create_builder": "current", "lifecycle": "previous"} + {"pack_create_builder": "current", "lifecycle": "current", "pack": "current"}, + {"pack_create_builder": "current", "lifecycle": "previous", "pack": "current"}, + {"pack_create_builder": "previous", "lifecycle": "previous", "pack": "current"}, + {"pack_create_builder": "current", "lifecycle": "current", "pack": "previous"}, + {"pack_create_builder": "current", "lifecycle": "previous", "pack": "previous"} ] \ No newline at end of file diff --git a/acceptance/testdata/pack_fixtures/builder.toml b/acceptance/testdata/pack_fixtures/builder.toml index afd2e7d03..de251b765 100644 --- a/acceptance/testdata/pack_fixtures/builder.toml +++ b/acceptance/testdata/pack_fixtures/builder.toml @@ -30,8 +30,16 @@ [stack] id = "pack.test.stack" +{{- if .build_image}} + build-image = "{{.build_image}}" +{{- else -}} build-image = "pack-test/build" +{{- end}} +{{- if .run_image}} + run-image = "{{.run_image}}" +{{- else -}} run-image = "pack-test/run" +{{- end}} run-image-mirrors = ["{{.run_image_mirror}}"] [lifecycle] diff --git a/acceptance/testdata/pack_fixtures/inspect_image_local_output.json b/acceptance/testdata/pack_fixtures/inspect_image_local_output.json index 83cbc9b12..7cb053e5d 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_local_output.json +++ b/acceptance/testdata/pack_fixtures/inspect_image_local_output.json @@ -13,7 +13,7 @@ "user_configured": true }, { - "name": "pack-test/run" + "name": "{{.run_image}}" }, { "name": "{{.run_image_mirror}}" diff --git a/acceptance/testdata/pack_fixtures/inspect_image_local_output.toml b/acceptance/testdata/pack_fixtures/inspect_image_local_output.toml index 97be50630..e10b49eb8 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_local_output.toml +++ b/acceptance/testdata/pack_fixtures/inspect_image_local_output.toml @@ -12,7 +12,7 @@ stack = "pack.test.stack" user_configured = true [[local_info.run_images]] - name = "pack-test/run" + name = "{{.run_image}}" [[local_info.run_images]] name = "{{.run_image_mirror}}" diff --git a/acceptance/testdata/pack_fixtures/inspect_image_local_output.yaml b/acceptance/testdata/pack_fixtures/inspect_image_local_output.yaml index 465ef5263..7fad7e313 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_local_output.yaml +++ b/acceptance/testdata/pack_fixtures/inspect_image_local_output.yaml @@ -9,7 +9,7 @@ local_info: run_images: - name: "{{.run_image_local_mirror}}" user_configured: true - - name: pack-test/run + - name: "{{.run_image}}" - name: "{{.run_image_mirror}}" buildpacks: - id: simple/layers diff --git a/acceptance/testdata/pack_fixtures/inspect_image_published_output.json b/acceptance/testdata/pack_fixtures/inspect_image_published_output.json index 691b8d17d..331fcf770 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_published_output.json +++ b/acceptance/testdata/pack_fixtures/inspect_image_published_output.json @@ -9,7 +9,7 @@ }, "run_images": [ { - "name": "pack-test/run" + "name": "{{.run_image}}" }, { "name": "{{.run_image_mirror}}" diff --git a/acceptance/testdata/pack_fixtures/inspect_image_published_output.toml b/acceptance/testdata/pack_fixtures/inspect_image_published_output.toml index 734eaad21..c144e8ae1 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_published_output.toml +++ b/acceptance/testdata/pack_fixtures/inspect_image_published_output.toml @@ -8,7 +8,7 @@ stack = "pack.test.stack" reference = "{{.base_image_ref}}" [[remote_info.run_images]] - name = "pack-test/run" + name = "{{.run_image}}" [[remote_info.run_images]] name = "{{.run_image_mirror}}" diff --git a/acceptance/testdata/pack_fixtures/inspect_image_published_output.yaml b/acceptance/testdata/pack_fixtures/inspect_image_published_output.yaml index 189c3c3e3..5bc966107 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_published_output.yaml +++ b/acceptance/testdata/pack_fixtures/inspect_image_published_output.yaml @@ -7,7 +7,7 @@ remote_info: top_layer: "{{.base_image_top_layer}}" reference: "{{.base_image_ref}}" run_images: - - name: pack-test/run + - name: "{{.run_image}}" - name: "{{.run_image_mirror}}" buildpacks: - id: simple/layers diff --git a/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml b/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml index afd2e7d03..de251b765 100644 --- a/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml +++ b/acceptance/testdata/pack_previous_fixtures_overrides/builder.toml @@ -30,8 +30,16 @@ [stack] id = "pack.test.stack" +{{- if .build_image}} + build-image = "{{.build_image}}" +{{- else -}} build-image = "pack-test/build" +{{- end}} +{{- if .run_image}} + run-image = "{{.run_image}}" +{{- else -}} run-image = "pack-test/run" +{{- end}} run-image-mirrors = ["{{.run_image_mirror}}"] [lifecycle] diff --git a/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.json b/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.json new file mode 100644 index 000000000..7cb053e5d --- /dev/null +++ b/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.json @@ -0,0 +1,51 @@ +{ + "image_name": "{{.image_name}}", + "remote_info": null, + "local_info": { + "stack": "pack.test.stack", + "base_image": { + "top_layer": "{{.base_image_top_layer}}", + "reference": "{{.base_image_id}}" + }, + "run_images": [ + { + "name": "{{.run_image_local_mirror}}", + "user_configured": true + }, + { + "name": "{{.run_image}}" + }, + { + "name": "{{.run_image_mirror}}" + } + ], + "buildpacks": [ + { + "id": "simple/layers", + "version": "simple-layers-version" + } + ], + "processes": [ + { + "type": "web", + "shell": "bash", + "command": "{{ ( StringsEscapeBackslash .web_command ) }}", + "default": true, + "args": [ + "8080" + ], + "working-dir": "{{ ( StringsEscapeBackslash .image_workdir ) }}" + }, + { + "type": "hello", + "shell": "", + "command": "{{.hello_command}}", + "default": false, + "args": [ + {{ ( StringsJoin (StringsDoubleQuote .hello_args) "," ) }} + ], + "working-dir": "{{ ( StringsEscapeBackslash .image_workdir ) }}" + } + ] + } +} \ No newline at end of file diff --git a/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.toml b/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.toml new file mode 100644 index 000000000..e10b49eb8 --- /dev/null +++ b/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.toml @@ -0,0 +1,38 @@ +image_name = "{{.image_name}}" + +[local_info] +stack = "pack.test.stack" + + [local_info.base_image] + top_layer = "{{.base_image_top_layer}}" + reference = "{{.base_image_id}}" + + [[local_info.run_images]] + name = "{{.run_image_local_mirror}}" + user_configured = true + + [[local_info.run_images]] + name = "{{.run_image}}" + + [[local_info.run_images]] + name = "{{.run_image_mirror}}" + + [[local_info.buildpacks]] + id = "simple/layers" + version = "simple-layers-version" + + [[local_info.processes]] + type = "web" + shell = "bash" + command = "{{ ( StringsEscapeBackslash .web_command ) }}" + default = true + args = [ "8080" ] + working-dir = "{{ ( StringsEscapeBackslash .image_workdir ) }}" + + [[local_info.processes]] + type = "hello" + shell = "" + command = "{{.hello_command}}" + default = false + args = [ {{ ( StringsJoin (StringsDoubleQuote .hello_args) ",") }} ] + working-dir = "{{ ( StringsEscapeBackslash .image_workdir ) }}" diff --git a/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.yaml b/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.yaml new file mode 100644 index 000000000..7fad7e313 --- /dev/null +++ b/acceptance/testdata/pack_previous_fixtures_overrides/inspect_image_local_output.yaml @@ -0,0 +1,30 @@ +--- +image_name: "{{.image_name}}" +remote_info: +local_info: + stack: pack.test.stack + base_image: + top_layer: "{{.base_image_top_layer}}" + reference: "{{.base_image_id}}" + run_images: + - name: "{{.run_image_local_mirror}}" + user_configured: true + - name: "{{.run_image}}" + - name: "{{.run_image_mirror}}" + buildpacks: + - id: simple/layers + version: simple-layers-version + processes: + - type: web + shell: bash + command: "{{ ( StringsEscapeBackslash .web_command ) }}" + default: true + args: + - '8080' + working-dir: "{{ ( StringsEscapeBackslash .image_workdir ) }}" + - type: hello + shell: '' + command: "{{.hello_command}}" + default: false + args: [ {{ ( StringsJoin (StringsDoubleQuote .hello_args) ",") }} ] + working-dir: "{{ ( StringsEscapeBackslash .image_workdir ) }}" \ No newline at end of file diff --git a/acceptance/tests/build/arg_app_test.go b/acceptance/tests/build/arg_app_test.go new file mode 100644 index 000000000..a72e0f80b --- /dev/null +++ b/acceptance/tests/build/arg_app_test.go @@ -0,0 +1,24 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_arg_app_zipfile(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + registry := th.Registry() + + pack := combo.Pack() + + repoName := registry.RepoName("sample/" + h.RandString(10)) + appPath := filepath.Join("..", "..", "testdata", "mock_app.zip") + output := pack.RunSuccessfully("build", repoName, "-p", appPath) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) +} diff --git a/acceptance/tests/build/arg_buildpack_test.go b/acceptance/tests/build/arg_buildpack_test.go new file mode 100644 index 000000000..13c0e5c58 --- /dev/null +++ b/acceptance/tests/build/arg_buildpack_test.go @@ -0,0 +1,231 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/buildpacks" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_arg_buildpack(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + pack := combo.Pack() + lifecycle := combo.Lifecycle() + + assert := h.NewAssertionManager(t) + registry := th.Registry() + imageManager := th.ImageManager() + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + appPath := filepath.Join("..", "..", "testdata", "mock_app") + repoName := registry.RepoName("test/" + h.RandString(10)) + + buildpackManager := buildpacks.NewBuildpackManager( + t, + assert, + buildpacks.WithBuildpackAPIVersion(lifecycle.EarliestBuildpackAPIVersion()), + buildpacks.WithBaseDir(filepath.Join("..", "..", "testdata", "mock_buildpacks")), + ) + + t.Run("the argument is an ID", func(t *testing.T) { + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--buildpack", "simple/layers", // can omit version if only one + "--buildpack", "noop.buildpack@noop.buildpack.version", + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + + assertTestAppOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) + assertTestAppOutput.ReportsBuildStep("Simple Layers Buildpack") + assertTestAppOutput.ReportsBuildStep("NOOP Buildpack") + assertOutput.ReportsSuccessfulImageBuild(repoName) + + t.Log("app is runnable") + assertImage.RunsWithOutput( + repoName, + "Launch Dep Contents", + "Cached Dep Contents", + ) + }) + + t.Run("the argument is an archive", func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "archive-buildpack-tests-") + assert.Nil(err) + t.Cleanup(func() { + assert.Succeeds(os.RemoveAll(tmpDir)) + }) + + buildpackManager.PrepareBuildpacks(tmpDir, buildpacks.ArchiveNotInBuilder) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--buildpack", buildpacks.ArchiveNotInBuilder.FullPathIn(tmpDir), + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsAddingBuildpack("local/bp", "local-bp-version") + assertOutput.ReportsSuccessfulImageBuild(repoName) + + assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) + assertBuildpackOutput.ReportsBuildStep("Local Buildpack") + }) + + t.Run("the argument is a directory", func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "folder-buildpack-tests-") + assert.Nil(err) + t.Cleanup(func() { + assert.Succeeds(os.RemoveAll(tmpDir)) + }) + + h.SkipIf(t, runtime.GOOS == "windows", "buildpack directories not supported on windows") + + buildpackManager.PrepareBuildpacks(tmpDir, buildpacks.FolderNotInBuilder) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--buildpack", buildpacks.FolderNotInBuilder.FullPathIn(tmpDir), + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsAddingBuildpack("local/bp", "local-bp-version") + assertOutput.ReportsSuccessfulImageBuild(repoName) + + assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) + assertBuildpackOutput.ReportsBuildStep("Local Buildpack") + }) + + t.Run("the argument is a buildpackage image", func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "test-buildpackages-") + assert.Nil(err) + + packageImageName := registry.RepoName("buildpack-" + h.RandString(8)) + + t.Cleanup(func() { + imageManager.CleanupImages(packageImageName) + assert.Succeeds(os.RemoveAll(tmpDir)) + }) + + packageTomlPath := pack.FixtureManager().TemplateFixtureToFile( + tmpDir, + "package_for_build_cmd.toml", + map[string]interface{}{ + "OS": imageManager.HostOS(), + }, + ) + + packageImage := buildpacks.NewPackageImage( + t, + pack, + packageImageName, + packageTomlPath, + buildpacks.WithRequiredBuildpacks( + buildpacks.FolderSimpleLayersParent, + buildpacks.FolderSimpleLayers, + ), + ) + + buildpackManager.PrepareBuildpacks(tmpDir, packageImage) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--buildpack", packageImageName, + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsAddingBuildpack( + "simple/layers/parent", + "simple-layers-parent-version", + ) + assertOutput.ReportsAddingBuildpack("simple/layers", "simple-layers-version") + assertOutput.ReportsSuccessfulImageBuild(repoName) + + assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) + assertBuildpackOutput.ReportsBuildStep("Simple Layers Buildpack") + }) + + t.Run("the argument is a buildpackage file", func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "package-file") + assert.Nil(err) + + t.Cleanup(func() { + assert.Succeeds(os.RemoveAll(tmpDir)) + }) + + packageFileLocation := filepath.Join( + tmpDir, + fmt.Sprintf("buildpack-%s.cnb", h.RandString(8)), + ) + + packageTomlPath := pack.FixtureManager().TemplateFixtureToFile( + tmpDir, + "package_for_build_cmd.toml", + map[string]interface{}{ + "OS": imageManager.HostOS(), + }, + ) + + packageFile := buildpacks.NewPackageFile( + t, + pack, + packageFileLocation, + packageTomlPath, + buildpacks.WithRequiredBuildpacks( + buildpacks.FolderSimpleLayersParent, + buildpacks.FolderSimpleLayers, + ), + ) + + buildpackManager.PrepareBuildpacks(tmpDir, packageFile) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--buildpack", packageFileLocation, + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsAddingBuildpack( + "simple/layers/parent", + "simple-layers-parent-version", + ) + assertOutput.ReportsAddingBuildpack("simple/layers", "simple-layers-version") + assertOutput.ReportsSuccessfulImageBuild(repoName) + + assertBuildpackOutput := assertions.NewTestBuildpackOutputAssertionManager(t, output) + assertBuildpackOutput.ReportsBuildStep("Simple Layers Buildpack") + }) + + t.Run("the buildpack stack doesn't match the builder", func(t *testing.T) { + bpDir := filepath.Join("..", "..", "testdata", "mock_buildpacks", lifecycle.EarliestBuildpackAPIVersion()) + otherStackBuilderTgz := h.CreateTGZ(t, filepath.Join(bpDir, "other-stack-buildpack"), "./", 0755) + + t.Cleanup(func() { + assert.Succeeds(os.Remove(otherStackBuilderTgz)) + }) + + output, err := pack.Run( + "build", repoName, + "-p", appPath, + "--buildpack", otherStackBuilderTgz, + ) + + assert.NotNil(err) + assert.Contains(output, "other/stack/bp") + assert.Contains(output, "other-stack-version") + assert.Contains(output, "does not support stack 'pack.test.stack'") + }) +} diff --git a/acceptance/tests/build/arg_creation_time_test.go b/acceptance/tests/build/arg_creation_time_test.go new file mode 100644 index 000000000..f4ee6bc5e --- /dev/null +++ b/acceptance/tests/build/arg_creation_time_test.go @@ -0,0 +1,64 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "path/filepath" + "testing" + "time" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/config" + "github.com/buildpacks/pack/acceptance/harness" + "github.com/buildpacks/pack/acceptance/invoke" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_arg_creation_time(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + registry := th.Registry() + imageManager := th.ImageManager() + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + pack := combo.Pack() + lifecycle := combo.Lifecycle() + + h.SkipIf(t, !pack.SupportsFeature(invoke.CreationTime), "pack doesn't support creation time") + h.SkipIf(t, !lifecycle.SupportsFeature(config.CreationTime), "lifecycle doesn't support creation time") + + appPath := filepath.Join("..", "..", "testdata", "mock_app") + + t.Run("provided as 'now'", func(t *testing.T) { + repoName := registry.RepoName("sample/" + h.RandString(10)) + expectedTime := time.Now() + pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--creation-time", "now", + ) + assertImage.HasCreateTime(repoName, expectedTime) + }) + + t.Run("provided as unix timestamp", func(t *testing.T) { + repoName := registry.RepoName("sample/" + h.RandString(10)) + pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + "--creation-time", "1566172801", + ) + expectedTime, err := time.Parse("2006-01-02T03:04:05Z", "2019-08-19T00:00:01Z") + h.AssertNil(t, err) + assertImage.HasCreateTime(repoName, expectedTime) + }) + + t.Run("not provided", func(t *testing.T) { + repoName := registry.RepoName("sample/" + h.RandString(10)) + pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + ) + expectedTime, err := time.Parse("2006-01-02T03:04:05Z", "1980-01-01T00:00:01Z") + h.AssertNil(t, err) + assertImage.HasCreateTime(repoName, expectedTime) + }) +} diff --git a/acceptance/tests/build/arg_env_file_test.go b/acceptance/tests/build/arg_env_file_test.go new file mode 100644 index 000000000..a8e54a281 --- /dev/null +++ b/acceptance/tests/build/arg_env_file_test.go @@ -0,0 +1,65 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_arg_env_file(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + assert := h.NewAssertionManager(t) + registry := th.Registry() + imageManager := th.ImageManager() + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + pack := combo.Pack() + + // mark builder as trusted + // TODO: Windows fails with "Access is denied" when builder is not trusted + pack.JustRunSuccessfully("config", "trusted-builders", "add", combo.BuilderName()) + defer pack.JustRunSuccessfully("config", "trusted-builders", "remove", combo.BuilderName()) + + envfile, err := ioutil.TempFile("", "envfile") + assert.Nil(err) + defer envfile.Close() + + err = os.Setenv("ENV2_CONTENTS", "Env2 Layer Contents From Environment") + assert.Nil(err) + + _, err = envfile.WriteString(` +DETECT_ENV_BUILDPACK=true +ENV1_CONTENTS=Env1 Layer Contents From File +ENV2_CONTENTS +`) + assert.Nil(err) + err = envfile.Close() + assert.Nil(err) + + envPath := envfile.Name() + t.Cleanup(func() { + assert.Succeeds(os.Unsetenv("ENV2_CONTENTS")) + assert.Succeeds(os.RemoveAll(envPath)) + }) + + repoName := registry.RepoName("some-org/" + h.RandString(10)) + output := pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + "--env-file", envPath, + ) + + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) + assertImage.RunsWithOutput( + repoName, + "Env2 Layer Contents From Environment", + "Env1 Layer Contents From File", + ) +} diff --git a/acceptance/tests/build/arg_env_test.go b/acceptance/tests/build/arg_env_test.go new file mode 100644 index 000000000..a2b140cf7 --- /dev/null +++ b/acceptance/tests/build/arg_env_test.go @@ -0,0 +1,49 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_arg_env_vars(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + assert := h.NewAssertionManager(t) + registry := th.Registry() + imageManager := th.ImageManager() + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + pack := combo.Pack() + + // mark builder as trusted + // TODO: Windows fails with "Access is denied" when builder is not trusted + pack.JustRunSuccessfully("config", "trusted-builders", "add", combo.BuilderName()) + defer pack.JustRunSuccessfully("config", "trusted-builders", "remove", combo.BuilderName()) + + assert.Succeeds(os.Setenv("ENV2_CONTENTS", "Env2 Layer Contents From Environment")) + t.Cleanup(func() { + assert.Succeeds(os.Unsetenv("ENV2_CONTENTS")) + }) + + repoName := registry.RepoName("some-org/" + h.RandString(10)) + output := pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + "--env", "DETECT_ENV_BUILDPACK=true", + "--env", `ENV1_CONTENTS="Env1 Layer Contents From Command Line"`, + "--env", "ENV2_CONTENTS", + ) + + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) + assertImage.RunsWithOutput( + repoName, + "Env2 Layer Contents From Environment", + "Env1 Layer Contents From Command Line", + ) +} diff --git a/acceptance/tests/build/arg_no_color_test.go b/acceptance/tests/build/arg_no_color_test.go new file mode 100644 index 000000000..fd861ecf8 --- /dev/null +++ b/acceptance/tests/build/arg_no_color_test.go @@ -0,0 +1,27 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_arg_no_color(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + pack := combo.Pack() + + appPath := filepath.Join("..", "..", "testdata", "mock_app") + + registry := th.Registry() + repoName := registry.RepoName("sample/" + h.RandString(3)) + + output := pack.RunSuccessfully("build", repoName, "-p", appPath, "--no-color") + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsSuccessfulImageBuild(repoName) + assertOutput.WithoutColors() +} diff --git a/acceptance/tests/build/build_test.go b/acceptance/tests/build/build_test.go new file mode 100644 index 000000000..3bfcc2601 --- /dev/null +++ b/acceptance/tests/build/build_test.go @@ -0,0 +1,27 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/harness" +) + +func TestBuild(t *testing.T) { + testHarness := harness.ContainingBuilder(t, filepath.Join("..", "..", "..")) + t.Cleanup(testHarness.CleanUp) + + testHarness.RunA(test_app_image_is_runnable_and_rebuildable) + testHarness.RunA(test_app_image_is_inspectable) + testHarness.RunA(test_untrusted_daemon) + testHarness.RunA(test_untrusted_registry) + testHarness.RunA(test_arg_app_zipfile) + testHarness.RunA(test_arg_creation_time) + testHarness.RunA(test_arg_env_file) + testHarness.RunA(test_arg_env_vars) + testHarness.RunA(test_arg_no_color) + testHarness.RunA(test_arg_buildpack) +} diff --git a/acceptance/tests/build/inspectable_test.go b/acceptance/tests/build/inspectable_test.go new file mode 100644 index 000000000..028daab34 --- /dev/null +++ b/acceptance/tests/build/inspectable_test.go @@ -0,0 +1,112 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_app_image_is_inspectable(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + assert := h.NewAssertionManager(t) + + registry := th.Registry() + imageManager := th.ImageManager() + runImageName := th.Stack().RunImage.Name + runImageMirror := th.Stack().RunImage.MirrorName + + pack := combo.Pack() + + appPath := filepath.Join("..", "..", "testdata", "mock_app") + repoName := registry.RepoName("some-org/" + h.RandString(10)) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + ) + + localRunImageMirror := registry.RepoName("pack-test/run-mirror") + imageManager.TagImage(runImageName, localRunImageMirror) + defer imageManager.CleanupImages(localRunImageMirror) + + pack.JustRunSuccessfully("config", "run-image-mirrors", "add", runImageName, "-m", localRunImageMirror) + defer pack.JustRunSuccessfully("config", "run-image-mirrors", "remove", runImageName) + + inspectCmd := "inspect" + if !pack.Supports("inspect") { + inspectCmd = "inspect-image" + } + + var ( + webCommand string + helloCommand string + helloArgs []string + helloArgsPrefix string + imageWorkdir string + ) + if imageManager.HostOS() == "windows" { + webCommand = ".\\run" + helloCommand = "cmd" + helloArgs = []string{"/c", "echo hello world"} + helloArgsPrefix = " " + imageWorkdir = "c:\\workspace" + } else { + webCommand = "./run" + helloCommand = "echo" + helloArgs = []string{"hello", "world"} + helloArgsPrefix = "" + imageWorkdir = "/workspace" + } + + formats := []compareFormat{ + { + extension: "json", + compareFunc: assert.EqualJSON, + outputArg: "json", + }, + { + extension: "yaml", + compareFunc: assert.EqualYAML, + outputArg: "yaml", + }, + { + extension: "toml", + compareFunc: assert.EqualTOML, + outputArg: "toml", + }, + } + for _, format := range formats { + t.Logf("inspecting image %s format", format.outputArg) + + output = pack.RunSuccessfully(inspectCmd, repoName, "--output", format.outputArg) + expectedOutput := pack.FixtureManager().TemplateFixture( + fmt.Sprintf("inspect_image_local_output.%s", format.extension), + map[string]interface{}{ + "image_name": repoName, + "base_image_id": h.ImageID(t, runImageMirror), + "base_image_top_layer": h.TopLayerDiffID(t, runImageMirror), + "run_image_local_mirror": localRunImageMirror, + "run_image": runImageName, + "run_image_mirror": runImageMirror, + "web_command": webCommand, + "hello_command": helloCommand, + "hello_args": helloArgs, + "hello_args_prefix": helloArgsPrefix, + "image_workdir": imageWorkdir, + }, + ) + + format.compareFunc(output, expectedOutput) + } +} + +type compareFormat struct { + extension string + compareFunc func(string, string) + outputArg string +} diff --git a/acceptance/tests/build/rebuild_test.go b/acceptance/tests/build/rebuild_test.go new file mode 100644 index 000000000..bc5b93f96 --- /dev/null +++ b/acceptance/tests/build/rebuild_test.go @@ -0,0 +1,100 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "fmt" + "path/filepath" + "regexp" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func test_app_image_is_runnable_and_rebuildable(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + registry := th.Registry() + imageManager := th.ImageManager() + runImageName := th.Stack().RunImage.Name + runImageMirror := th.Stack().RunImage.MirrorName + + assert := h.NewAssertionManager(t) + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + pack := combo.Pack() + + appPath := filepath.Join("..", "..", "testdata", "mock_app") + + repo := "some-org/" + h.RandString(10) + repoName := registry.RepoName(repo) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + ) + + assertOutput := assertions.NewOutputAssertionManager(t, output) + + assertOutput.ReportsSuccessfulImageBuild(repoName) + assertOutput.ReportsUsingBuildCacheVolume() + assertOutput.ReportsSelectingRunImageMirror(runImageMirror) + + t.Log("app is runnable") + assertImage.RunsWithOutput(repoName, "Launch Dep Contents", "Cached Dep Contents") + + t.Log("it uses the run image as a base image") + assertImage.HasBaseImage(repoName, runImageName) + + t.Log("sets the run image metadata") + assertImage.HasLabelWithData(repoName, "io.buildpacks.lifecycle.metadata", fmt.Sprintf(`"stack":{"runImage":{"image":"%s","mirrors":["%s"]}}}`, runImageName, runImageMirror)) + + t.Log("sets the source metadata") + assertImage.HasLabelWithData(repoName, "io.buildpacks.project.metadata", (`{"source":{"type":"project","version":{"declared":"1.0.2"},"metadata":{"url":"https://github.com/buildpacks/pack"}}}`)) + + t.Log("registry is empty") + assertImage.NotExistsInRegistry(repo) + + t.Log("add a local mirror") + localRunImageMirror := registry.RepoName("pack-test/run-mirror") + imageManager.TagImage(runImageName, localRunImageMirror) + defer imageManager.CleanupImages(localRunImageMirror) + + pack.JustRunSuccessfully("config", "run-image-mirrors", "add", runImageName, "-m", localRunImageMirror) + defer pack.JustRunSuccessfully("config", "run-image-mirrors", "remove", runImageName) + + t.Log("rebuild") + output = pack.RunSuccessfully( + "build", repoName, + "-p", appPath, + ) + assertOutput = assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsSuccessfulImageBuild(repoName) + assertOutput.ReportsSelectingRunImageMirrorFromLocalConfig(localRunImageMirror) + cachedLaunchLayer := "simple/layers:cached-launch-layer" + + assertLifecycleOutput := assertions.NewLifecycleOutputAssertionManager(t, output) + assertLifecycleOutput.ReportsRestoresCachedLayer(cachedLaunchLayer) + assertLifecycleOutput.ReportsExporterReusingUnchangedLayer(cachedLaunchLayer) + assertLifecycleOutput.ReportsCacheReuse(cachedLaunchLayer) + + t.Log("app is runnable") + assertImage.RunsWithOutput(repoName, "Launch Dep Contents", "Cached Dep Contents") + + t.Log("rebuild with --clear-cache") + output = pack.RunSuccessfully("build", + repoName, + "-p", appPath, + "--clear-cache", + ) + + assertOutput = assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsSuccessfulImageBuild(repoName) + assertLifecycleOutput = assertions.NewLifecycleOutputAssertionManager(t, output) + assertLifecycleOutput.ReportsExporterReusingUnchangedLayer(cachedLaunchLayer) + assertLifecycleOutput.ReportsCacheCreation(cachedLaunchLayer) + + t.Log("cacher adds layers") + assert.Matches(output, regexp.MustCompile(`(?i)Adding cache layer 'simple/layers:cached-launch-layer'`)) +} diff --git a/acceptance/tests/build/untrusted_test.go b/acceptance/tests/build/untrusted_test.go new file mode 100644 index 000000000..6109f3a70 --- /dev/null +++ b/acceptance/tests/build/untrusted_test.go @@ -0,0 +1,59 @@ +//go:build acceptance +// +build acceptance + +package build_test + +import ( + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + "github.com/buildpacks/pack/testhelpers" +) + +func test_untrusted_daemon(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + pack := combo.Pack() + lifecycle := combo.Lifecycle() + + registry := th.Registry() + repoName := registry.RepoName("sample/" + testhelpers.RandString(3)) + + output := pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + ) + + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) + + assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) + assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) + assertOutput.IncludesSeparatePhases() +} + +func test_untrusted_registry(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + imageManager := th.ImageManager() + registry := th.Registry() + repoName := registry.RepoName("sample/" + testhelpers.RandString(3)) + + pack := combo.Pack() + lifecycle := combo.Lifecycle() + + buildArgs := []string{ + repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + "--publish", + } + + if imageManager.HostOS() != "windows" { + buildArgs = append(buildArgs, "--network", "host") + } + + output := pack.RunSuccessfully("build", buildArgs...) + + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) + + assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) + assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) + assertOutput.IncludesSeparatePhases() +} diff --git a/acceptance/tests/rebase/rebase_test.go b/acceptance/tests/rebase/rebase_test.go new file mode 100644 index 000000000..cf332d739 --- /dev/null +++ b/acceptance/tests/rebase/rebase_test.go @@ -0,0 +1,204 @@ +//go:build acceptance +// +build acceptance + +package rebase_test + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/buildpacks/pack/acceptance/assertions" + "github.com/buildpacks/pack/acceptance/harness" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestRebase(t *testing.T) { + testHarness := harness.ContainingBuilder(t, filepath.Join("..", "..", "..")) + t.Cleanup(testHarness.CleanUp) + + testHarness.RunA(test_run_image_flag) + testHarness.RunA(test_local_config_mirror) + testHarness.RunA(test_stack_config_mirror) +} + +func test_run_image_flag(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + assert := h.NewAssertionManager(t) + + pack := combo.Pack() + builderName := combo.BuilderName() + + registry := th.Registry() + runImageName := th.Stack().RunImage.Name + imageManager := th.ImageManager() + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + pack.JustRunSuccessfully("config", "trusted-builders", "add", builderName) + + repoName := registry.RepoName("some-org/" + h.RandString(10)) + originalRunImage := registry.RepoName("acceptance/run-image:original" + h.RandString(10)) + + th.ImageManager().CreateImage( + originalRunImage, + runImageDockerfile( + runImageName, + hostRootUser(imageManager.HostOS()), + "contents-original-1", + "contents-original-2", + ), + ) + + pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + "--builder", builderName, + "--run-image", originalRunImage, + "--pull-policy", "never", + ) + + originalID := h.ImageID(t, repoName) + + assertImage.RunsWithOutput( + repoName, + "contents-original-1", + "contents-original-2", + ) + + t.Cleanup(func() { + imageManager.CleanupImages(originalID, repoName, originalRunImage) + }) + + updatedRunImage := registry.RepoName("acceptance/run-image:updated" + h.RandString(10)) + imageManager.CreateImage( + updatedRunImage, + runImageDockerfile( + runImageName, + hostRootUser(imageManager.HostOS()), + "contents-updated-1", + "contents-updated-2", + )) + t.Cleanup(func() { + imageManager.CleanupImages(updatedRunImage) + }) + + output := pack.RunSuccessfully( + "rebase", repoName, + "--run-image", updatedRunImage, + "--pull-policy", "never", + ) + + assert.Contains(output, fmt.Sprintf("Successfully rebased image '%s'", repoName)) + assertImage.RunsWithOutput( + repoName, + "contents-updated-1", + "contents-updated-2", + ) +} + +func test_local_config_mirror(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + builderName := combo.BuilderName() + pack := combo.Pack() + + registry := th.Registry() + runImageName := th.Stack().RunImage.Name + imageManager := th.ImageManager() + + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + localRunImageMirror := "run-after/" + h.RandString(10) + imageManager.CreateImage( + localRunImageMirror, + runImageDockerfile( + runImageName, + hostRootUser(imageManager.HostOS()), + "local-mirror-after-1", + "local-mirror-after-2", + )) + + output := pack.RunSuccessfully("config", "run-image-mirrors", "add", runImageName, "-m", localRunImageMirror) + t.Log(output) + + t.Cleanup(func() { + imageManager.CleanupImages(localRunImageMirror) + }) + + repoName := "pack-test/rebase-local-config-mirror:app" + h.RandString(6) + + pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + "--builder", builderName, + "--pull-policy", "never", + ) + + output = pack.RunSuccessfully("rebase", repoName, "--pull-policy", "never") + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsSelectingRunImageMirrorFromLocalConfig(localRunImageMirror) + assertOutput.ReportsSuccessfulRebase(repoName) + assertImage.RunsWithOutput( + repoName, + "local-mirror-after-1", + "local-mirror-after-2", + ) +} + +func test_stack_config_mirror(t *testing.T, th *harness.BuilderTestHarness, combo harness.BuilderCombo) { + builderName := combo.BuilderName() + pack := combo.Pack() + + registry := th.Registry() + runImageName := th.Stack().RunImage.Name + runImageMirror := th.Stack().RunImage.MirrorName + imageManager := th.ImageManager() + + assertImage := assertions.NewImageAssertionManager(t, imageManager, ®istry) + + // FIXME: we should not be overriding suite level test asset + th.ImageManager().CreateImage(runImageMirror, + runImageDockerfile(runImageName, + hostRootUser(imageManager.HostOS()), + "mirror-after-1", + "mirror-after-2", + )) + + t.Cleanup(func() { + imageManager.CleanupImages(runImageMirror) + }) + + repoName := registry.RepoName("pack-test/stack-config-run-image:app" + h.RandString(6)) + + pack.RunSuccessfully( + "build", repoName, + "-p", filepath.Join("..", "..", "testdata", "mock_app"), + "--builder", builderName, + "--pull-policy", "never", + ) + + output := pack.RunSuccessfully("rebase", repoName, "--pull-policy", "never") + + assertOutput := assertions.NewOutputAssertionManager(t, output) + assertOutput.ReportsSelectingRunImageMirror(runImageMirror) + assertOutput.ReportsSuccessfulRebase(repoName) + assertImage.RunsWithOutput( + repoName, + "mirror-after-1", + "mirror-after-2", + ) +} + +func runImageDockerfile(baseRunImage, user, contents1, contents2 string) string { + return fmt.Sprintf(`FROM %s +USER %s +RUN echo %s > /contents1.txt +RUN echo %s > /contents2.txt +USER pack`, baseRunImage, user, contents1, contents2) +} + +func hostRootUser(hostOS string) string { + if hostOS == "windows" { + return "ContainerAdministrator" + } + + return "root" +} diff --git a/testhelpers/registry.go b/testhelpers/registry.go index ec3d6af4b..fab29059e 100644 --- a/testhelpers/registry.go +++ b/testhelpers/registry.go @@ -77,8 +77,8 @@ func CreateRegistryFixture(t *testing.T, tmpDir, fixturePath string) string { } func RunRegistry(t *testing.T) *TestRegistryConfig { - t.Log("run registry") t.Helper() + t.Log("run registry") runRegistryName := "test-registry-" + RandString(10) username := RandString(10) diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index 964274686..28f3ac902 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -309,7 +309,11 @@ var dockerCliErr error func dockerCli(t *testing.T) client.CommonAPIClient { dockerCliOnce.Do(func() { - dockerCliVal, dockerCliErr = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38")) + dockerCliVal, dockerCliErr = client.NewClientWithOpts( + client.FromEnv, + client.WithVersion("1.38"), + client.WithTimeout(60*time.Second), + ) }) AssertNil(t, dockerCliErr) return dockerCliVal