diff --git a/.gitignore b/.gitignore index 77a88a3de..99af7fc48 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ .vscode acceptance/testdata/*/**/container/cnb/lifecycle/* acceptance/testdata/*/**/container/docker-config/* +acceptance/testdata/restorer/container/layers/*analyzed.toml acceptance/testdata/exporter/container/layers/*analyzed.toml acceptance/testdata/exporter/container/other_layers/*analyzed.toml diff --git a/acceptance/exporter_test.go b/acceptance/exporter_test.go index 4ce20cff9..a29fc6986 100644 --- a/acceptance/exporter_test.go +++ b/acceptance/exporter_test.go @@ -354,30 +354,41 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe } } +// This helper function exists because we expect the run image in analyzed.toml to contain the registry IP and port, +// which aren't known until we start the test. func updateAnalyzedTOMLFixturesWithRegRepoName(t *testing.T, phaseTest *PhaseTest) { - placeHolderPath := filepath.Join("testdata", "exporter", "container", "layers", "analyzed.toml.placeholder") - analyzedMD := assertAnalyzedMetadata(t, placeHolderPath) - analyzedMD.RunImage = &platform.RunImage{Reference: phaseTest.targetRegistry.fixtures.ReadOnlyRunImage} - encoding.WriteTOML(strings.TrimSuffix(placeHolderPath, ".placeholder"), analyzedMD) - - placeHolderPath = filepath.Join("testdata", "exporter", "container", "layers", "some-analyzed.toml.placeholder") - analyzedMD = assertAnalyzedMetadata(t, placeHolderPath) - analyzedMD.PreviousImage = &platform.ImageIdentifier{Reference: phaseTest.targetRegistry.fixtures.SomeAppImage} - analyzedMD.RunImage = &platform.RunImage{Reference: phaseTest.targetRegistry.fixtures.ReadOnlyRunImage} - encoding.WriteTOML(strings.TrimSuffix(placeHolderPath, ".placeholder"), analyzedMD) - - placeHolderPath = filepath.Join("testdata", "exporter", "container", "other_layers", "analyzed.toml.placeholder") - analyzedMD = assertAnalyzedMetadata(t, placeHolderPath) - analyzedMD.RunImage = &platform.RunImage{Reference: phaseTest.targetRegistry.fixtures.ReadOnlyRunImage} - encoding.WriteTOML(strings.TrimSuffix(placeHolderPath, ".placeholder"), analyzedMD) - - placeHolderPath = filepath.Join("testdata", "exporter", "container", "layers", "layout-analyzed.toml.placeholder") - analyzedMD = assertAnalyzedMetadata(t, placeHolderPath) - // Values from image acceptance/testdata/exporter/container/layout-repo in OCI layout format - analyzedMD.RunImage = &platform.RunImage{ - Reference: "/layout-repo/index.docker.io/library/busybox/latest@sha256:445c45cc89fdeb64b915b77f042e74ab580559b8d0d5ef6950be1c0265834c33", + regPlaceholders := []string{ + filepath.Join(phaseTest.testImageDockerContext, "container", "layers", "analyzed.toml.placeholder"), + filepath.Join(phaseTest.testImageDockerContext, "container", "layers", "some-analyzed.toml.placeholder"), + filepath.Join(phaseTest.testImageDockerContext, "container", "layers", "some-extend-false-analyzed.toml.placeholder"), + filepath.Join(phaseTest.testImageDockerContext, "container", "layers", "some-extend-true-analyzed.toml.placeholder"), + filepath.Join(phaseTest.testImageDockerContext, "container", "other_layers", "analyzed.toml.placeholder"), + } + layoutPlaceholders := []string{ + filepath.Join(phaseTest.testImageDockerContext, "container", "layers", "layout-analyzed.toml.placeholder"), + } + + for _, pPath := range regPlaceholders { + if _, err := os.Stat(pPath); os.IsNotExist(err) { + continue + } + analyzedMD := assertAnalyzedMetadata(t, pPath) + if analyzedMD.RunImage != nil { + analyzedMD.RunImage.Reference = phaseTest.targetRegistry.fixtures.ReadOnlyRunImage // don't override extend + } + encoding.WriteTOML(strings.TrimSuffix(pPath, ".placeholder"), analyzedMD) + } + for _, pPath := range layoutPlaceholders { + if _, err := os.Stat(pPath); os.IsNotExist(err) { + continue + } + analyzedMD := assertAnalyzedMetadata(t, pPath) + if analyzedMD.RunImage != nil { + // Values from image acceptance/testdata/exporter/container/layout-repo in OCI layout format + analyzedMD.RunImage = &platform.RunImage{Reference: "/layout-repo/index.docker.io/library/busybox/latest@sha256:445c45cc89fdeb64b915b77f042e74ab580559b8d0d5ef6950be1c0265834c33"} + } + encoding.WriteTOML(strings.TrimSuffix(pPath, ".placeholder"), analyzedMD) } - encoding.WriteTOML(strings.TrimSuffix(placeHolderPath, ".placeholder"), analyzedMD) } func calculateEmptyLayerSha(t *testing.T) string { diff --git a/acceptance/restorer_test.go b/acceptance/restorer_test.go index 9e3849024..4d65bd6a8 100644 --- a/acceptance/restorer_test.go +++ b/acceptance/restorer_test.go @@ -12,11 +12,13 @@ import ( "testing" "time" + "github.com/google/go-containerregistry/pkg/name" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/cmd" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -38,7 +40,7 @@ func TestRestorer(t *testing.T) { testImageDockerContext := filepath.Join("testdata", "restorer") restoreTest = NewPhaseTest(t, "restorer", testImageDockerContext) - restoreTest.Start(t) + restoreTest.Start(t, updateAnalyzedTOMLFixturesWithRegRepoName) defer restoreTest.Stop(t) restoreImage = restoreTest.testImageRef @@ -80,7 +82,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe }) }) - when("called with -analyzed", func() { + when("called with -analyzed (on older platforms)", func() { it("errors", func() { h.SkipIf(t, api.MustParse(platformAPI).AtLeast("0.7"), "Platform API >= 0.7 supports -analyzed flag") command := exec.Command("docker", "run", "--rm", restoreImage, "-analyzed some-file-location") @@ -91,7 +93,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe }) }) - when("called with -skip-layers", func() { + when("called with -skip-layers (on older platforms)", func() { it("errors", func() { h.SkipIf(t, api.MustParse(platformAPI).AtLeast("0.7"), "Platform API >= 0.7 supports -skip-layers flag") command := exec.Command("docker", "run", "--rm", restoreImage, "-skip-layers true") @@ -188,9 +190,9 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe }) }) - when("using kaniko cache", func() { - it("accepts -build-image", func() { - h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.10"), "Platform API < 0.10 does not use kaniko") + when("restoring builder image metadata for extensions", func() { + it("accepts -build-image and saves the metadata to /kaniko/cache", func() { + h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.10"), "Platform API < 0.10 does not restore builder image metadata") h.DockerRunAndCopy(t, containerName, copyDir, @@ -204,14 +206,86 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe h.WithArgs("-build-image", restoreRegFixtures.SomeCacheImage), // some-cache-image simulates a builder image in a registry ) t.Log("records builder image digest in analyzed.toml") - analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "analyzed.toml"), nil) + analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "analyzed.toml"), cmd.DefaultLogger) h.AssertNil(t, err) h.AssertStringContains(t, analyzedMD.BuildImage.Reference, restoreRegFixtures.SomeCacheImage+"@sha256:") t.Log("writes builder manifest and config to the kaniko cache") + ref, err := name.ParseReference(analyzedMD.BuildImage.Reference) + h.AssertNil(t, err) fis, err := os.ReadDir(filepath.Join(copyDir, "kaniko", "cache", "base")) h.AssertNil(t, err) h.AssertEq(t, len(fis), 1) - h.AssertPathExists(t, filepath.Join(copyDir, "kaniko", "cache", "base", fis[0].Name(), "oci-layout")) + h.AssertPathExists(t, filepath.Join(copyDir, "kaniko", "cache", "base", ref.Identifier(), "oci-layout")) + }) + }) + + when("restoring run image metadata for extensions", func() { + it("saves metadata to /kaniko/cache", func() { + h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "Platform API < 0.12 does not restore run image metadata") + h.DockerRunAndCopy(t, + containerName, + copyDir, + "/", + restoreImage, + h.WithFlags( + "--env", "CNB_PLATFORM_API="+platformAPI, + "--env", "DOCKER_CONFIG=/docker-config", + "--network", restoreRegNetwork, + ), + h.WithArgs( + "-analyzed", "/layers/some-extend-true-analyzed.toml", + "-log-level", "debug", + ), + ) + t.Log("updates run image reference in analyzed.toml to include digest and target data") + analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-true-analyzed.toml"), cmd.DefaultLogger) + h.AssertNil(t, err) + h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:") + h.AssertEq(t, analyzedMD.RunImage.TargetMetadata.OS, "linux") + t.Log("writes run image manifest and config to the kaniko cache") + ref, err := name.ParseReference(analyzedMD.RunImage.Reference) + h.AssertNil(t, err) + fis, err := os.ReadDir(filepath.Join(copyDir, "kaniko", "cache", "base")) + h.AssertNil(t, err) + h.AssertEq(t, len(fis), 1) + h.AssertPathExists(t, filepath.Join(copyDir, "kaniko", "cache", "base", ref.Identifier(), "oci-layout")) + }) + }) + + when("target data", func() { + it("updates run image reference in analyzed.toml to include digest and target data on newer platforms", func() { + h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.7"), "Platform API < 0.7 does not support -analyzed flag") + h.DockerRunAndCopy(t, + containerName, + copyDir, + "/", + restoreImage, + h.WithFlags( + "--env", "CNB_PLATFORM_API="+platformAPI, + "--env", "DOCKER_CONFIG=/docker-config", + "--network", restoreRegNetwork, + ), + h.WithArgs( + "-analyzed", "/layers/some-extend-false-analyzed.toml", + "-log-level", "debug", + ), + ) + if api.MustParse(platformAPI).AtLeast("0.12") { + t.Log("updates run image reference in analyzed.toml to include digest and target data") + analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger) + h.AssertNil(t, err) + h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:") + h.AssertEq(t, analyzedMD.RunImage.TargetMetadata.OS, "linux") + t.Log("does not write run image manifest and config to the kaniko cache") + fis, err := os.ReadDir(filepath.Join(copyDir, "kaniko")) + h.AssertNil(t, err) + h.AssertEq(t, len(fis), 1) // .gitkeep + } else { + t.Log("doesn't update analyzed.toml") + analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger) + h.AssertNil(t, err) + h.AssertNil(t, analyzedMD.RunImage.TargetMetadata) + } }) }) } diff --git a/acceptance/testdata/restorer/container/layers/analyzed.toml b/acceptance/testdata/restorer/container/layers/analyzed.toml.placeholder similarity index 96% rename from acceptance/testdata/restorer/container/layers/analyzed.toml rename to acceptance/testdata/restorer/container/layers/analyzed.toml.placeholder index 71a32aa6f..7e9489d34 100644 --- a/acceptance/testdata/restorer/container/layers/analyzed.toml +++ b/acceptance/testdata/restorer/container/layers/analyzed.toml.placeholder @@ -1,5 +1,5 @@ [image] -reference = "some-ref" +reference = "some-previous-image-ref" [[metadata.buildpacks]] key = "some-buildpack-id" diff --git a/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder b/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder new file mode 100644 index 000000000..04e2eadb9 --- /dev/null +++ b/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder @@ -0,0 +1,2 @@ +[run-image] + reference = "REPLACE" diff --git a/acceptance/testdata/restorer/container/layers/some-extend-true-analyzed.toml.placeholder b/acceptance/testdata/restorer/container/layers/some-extend-true-analyzed.toml.placeholder new file mode 100644 index 000000000..505ea0a64 --- /dev/null +++ b/acceptance/testdata/restorer/container/layers/some-extend-true-analyzed.toml.placeholder @@ -0,0 +1,3 @@ +[run-image] + reference = "REPLACE" + extend = true \ No newline at end of file diff --git a/analyzer.go b/analyzer.go index 01f31c69e..33b6a5040 100644 --- a/analyzer.go +++ b/analyzer.go @@ -241,7 +241,7 @@ func (a *Analyzer) Analyze() (platform.AnalyzedMetadata, error) { return platform.AnalyzedMetadata{ PreviousImage: &platform.ImageIdentifier{Reference: previousImageRef}, - RunImage: &platform.RunImage{Reference: runImageRef, Target: atm}, + RunImage: &platform.RunImage{Reference: runImageRef, TargetMetadata: atm}, Metadata: appMeta, }, nil } diff --git a/analyzer_test.go b/analyzer_test.go index 4f18751b1..8bf63390f 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -682,16 +682,16 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) md, err := analyzer.Analyze() h.AssertNil(t, err) if api.MustParse(platformAPI).LessThan("0.12") { - h.AssertNil(t, md.RunImage.Target) + h.AssertNil(t, md.RunImage.TargetMetadata) } else { - h.AssertNotNil(t, md.RunImage.Target) - h.AssertEq(t, md.RunImage.Target.Arch, "Pentium") - h.AssertEq(t, md.RunImage.Target.ArchVariant, "MMX") - h.AssertEq(t, md.RunImage.Target.OS, "windows") - h.AssertEq(t, md.RunImage.Target.ID, "id software") - h.AssertNotNil(t, md.RunImage.Target.Distribution) - h.AssertEq(t, md.RunImage.Target.Distribution.Name, "moobuntu") - h.AssertEq(t, md.RunImage.Target.Distribution.Version, "Helpful Holstein") + h.AssertNotNil(t, md.RunImage.TargetMetadata) + h.AssertEq(t, md.RunImage.TargetMetadata.Arch, "Pentium") + h.AssertEq(t, md.RunImage.TargetMetadata.ArchVariant, "MMX") + h.AssertEq(t, md.RunImage.TargetMetadata.OS, "windows") + h.AssertEq(t, md.RunImage.TargetMetadata.ID, "id software") + h.AssertNotNil(t, md.RunImage.TargetMetadata.Distribution) + h.AssertEq(t, md.RunImage.TargetMetadata.Distribution.Name, "moobuntu") + h.AssertEq(t, md.RunImage.TargetMetadata.Distribution.Version, "Helpful Holstein") } }) }) diff --git a/buildpack/bp_descriptor.go b/buildpack/bp_descriptor.go index d46f2ecc4..adb8e7b46 100644 --- a/buildpack/bp_descriptor.go +++ b/buildpack/bp_descriptor.go @@ -23,18 +23,14 @@ type StackMetadata struct { ID string `toml:"id"` } -type TargetPartial struct { - Arch string `json:"arch" toml:"arch"` - ArchVariant string `json:"arch-variant" toml:"arch-variant"` - OS string `json:"os" toml:"os"` -} - type TargetMetadata struct { - TargetPartial - Distributions []DistributionMetadata `json:"distributions" toml:"distributions"` + OS string `json:"os" toml:"os"` + Arch string `json:"arch" toml:"arch"` + ArchVariant string `json:"arch-variant" toml:"arch-variant"` + Distributions []OSDistribution `json:"distributions" toml:"distributions"` } -type DistributionMetadata struct { +type OSDistribution struct { Name string `json:"name" toml:"name"` Version string `json:"version" toml:"version"` } @@ -66,9 +62,9 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) { if len(descriptor.Targets) == 0 { for _, stack := range descriptor.Stacks { if stack.ID == "io.buildpacks.stacks.bionic" { - descriptor.Targets = append(descriptor.Targets, TargetMetadata{TargetPartial: TargetPartial{OS: "linux", Arch: "amd64"}, Distributions: []DistributionMetadata{{Name: "ubuntu", Version: "18.04"}}}) + descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distributions: []OSDistribution{{Name: "ubuntu", Version: "18.04"}}}) } else if stack.ID == "*" { - descriptor.Targets = append(descriptor.Targets, TargetMetadata{TargetPartial: TargetPartial{OS: "*", Arch: "*"}, Distributions: []DistributionMetadata{}}) + descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "*", Arch: "*", Distributions: []OSDistribution{}}) } } } @@ -84,11 +80,11 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) { bf := binFiles[len(binFiles)-i-1] // we're iterating backwards b/c os.ReadDir sorts "build.exe" after "build" but we want to preferentially detect windows first. fname := bf.Name() if fname == "build.exe" || fname == "build.bat" { - descriptor.Targets = append(descriptor.Targets, TargetMetadata{TargetPartial: TargetPartial{OS: "windows", Arch: "*"}}) + descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "windows", Arch: "*"}) break } if fname == "build" { - descriptor.Targets = append(descriptor.Targets, TargetMetadata{TargetPartial: TargetPartial{OS: "linux", Arch: "*"}}) + descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "*"}) } } } diff --git a/buildpack/dockerfile_test.go b/buildpack/dockerfile_test.go index ef22f2902..78d4632a3 100644 --- a/buildpack/dockerfile_test.go +++ b/buildpack/dockerfile_test.go @@ -41,8 +41,7 @@ func testDockerfile(t *testing.T, when spec.G, it spec.S) { }) when("verifying dockerfiles", func() { - validCases := []string{ - ` + validCases := []string{` ARG base_image=0 FROM ${base_image} @@ -132,23 +131,23 @@ RUN echo "this statement is never cached" h.AssertEq(t, len(logHandler.Entries), 0) } }) - }) - when("valid, but violates SHOULD directives in spec", func() { - it("succeeds with warning", func() { - preamble := ` + when("violates SHOULD directives in spec", func() { + it("succeeds with warning", func() { + preamble := ` ARG base_image=0 FROM ${base_image} ` - for i, tc := range warnCases { - dockerfilePath := filepath.Join(tmpDir, fmt.Sprintf("Dockerfile%d", i)) - h.AssertNil(t, os.WriteFile(dockerfilePath, []byte(preamble+tc.dockerfileContent), 0600)) - logHandler = memory.New() - logger = &log.Logger{Handler: logHandler} - err := buildpack.VerifyBuildDockerfile(dockerfilePath, logger) - h.AssertNil(t, err) - assertLogEntry(t, logHandler, "build.Dockerfile "+tc.expectedWarning) - } + for i, tc := range warnCases { + dockerfilePath := filepath.Join(tmpDir, fmt.Sprintf("Dockerfile%d", i)) + h.AssertNil(t, os.WriteFile(dockerfilePath, []byte(preamble+tc.dockerfileContent), 0600)) + logHandler = memory.New() + logger = &log.Logger{Handler: logHandler} + err := buildpack.VerifyBuildDockerfile(dockerfilePath, logger) + h.AssertNil(t, err) + assertLogEntry(t, logHandler, "build.Dockerfile "+tc.expectedWarning) + } + }) }) }) diff --git a/buildpack/generate.go b/buildpack/generate.go index 9598cc98d..e98951f45 100644 --- a/buildpack/generate.go +++ b/buildpack/generate.go @@ -142,14 +142,14 @@ func findDockerfileFor(d ExtDescriptor, extOutputDir string, kind string, logger return DockerfileInfo{}, false, nil } - newBase, err := verifyDockerfileFor(d, dockerfilePath, kind, logger) + newBase, err := verifyDockerfileFor(dockerfilePath, kind, logger) if err != nil { return DockerfileInfo{}, true, fmt.Errorf("failed to parse %s.Dockerfile for extension %s: %w", kind, d.Extension.ID, err) } return DockerfileInfo{ExtensionID: d.Extension.ID, Kind: kind, Path: dockerfilePath, NewBase: newBase}, true, nil } -func verifyDockerfileFor(d ExtDescriptor, path string, kind string, logger log.Logger) (string, error) { +func verifyDockerfileFor(path string, kind string, logger log.Logger) (string, error) { switch kind { case DockerfileKindBuild: return "", VerifyBuildDockerfile(path, logger) diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go index 923ec812b..2fd1d72ca 100644 --- a/cmd/lifecycle/exporter.go +++ b/cmd/lifecycle/exporter.go @@ -64,12 +64,12 @@ func (e *exportCmd) DefineFlags() { cli.FlagProcessType(&e.DefaultProcessType) cli.FlagProjectMetadataPath(&e.ProjectMetadataPath) cli.FlagReportPath(&e.ReportPath) - cli.FlagRunImage(&e.RunImageRef) + cli.FlagRunImage(&e.RunImageRef) // FIXME: this flag isn't valid on Platform 0.7 and later cli.FlagStackPath(&e.StackPath) cli.FlagUID(&e.UID) cli.FlagUseDaemon(&e.UseDaemon) - cli.DeprecatedFlagRunImage(&e.DeprecatedRunImageRef) + cli.DeprecatedFlagRunImage(&e.DeprecatedRunImageRef) // FIXME: this flag isn't valid on Platform 0.7 and later } // Args validates arguments and flags, and fills in default values. diff --git a/cmd/lifecycle/restorer.go b/cmd/lifecycle/restorer.go index 3a3492c67..170c8f9f3 100644 --- a/cmd/lifecycle/restorer.go +++ b/cmd/lifecycle/restorer.go @@ -6,10 +6,12 @@ import ( "os" "path/filepath" + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/layout" + "github.com/buildpacks/imgutil/layout/sparse" + "github.com/buildpacks/imgutil/remote" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/empty" - "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/buildpacks/lifecycle" "github.com/buildpacks/lifecycle/auth" @@ -18,7 +20,6 @@ import ( "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/internal/layer" - "github.com/buildpacks/lifecycle/internal/selective" "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/priv" ) @@ -33,34 +34,21 @@ type restoreCmd struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (r *restoreCmd) DefineFlags() { - switch { - case r.PlatformAPI.AtLeast("0.10"): + if r.PlatformAPI.AtLeast("0.12") { + cli.FlagGeneratedDir(&r.GeneratedDir) + } + if r.PlatformAPI.AtLeast("0.10") { cli.FlagBuildImage(&r.BuildImageRef) - cli.FlagCacheDir(&r.CacheDir) - cli.FlagCacheImage(&r.CacheImageRef) - cli.FlagGroupPath(&r.GroupPath) - cli.FlagLayersDir(&r.LayersDir) - cli.FlagUID(&r.UID) - cli.FlagGID(&r.GID) - cli.FlagAnalyzedPath(&r.AnalyzedPath) - cli.FlagSkipLayers(&r.SkipLayers) - case r.PlatformAPI.AtLeast("0.7"): - cli.FlagCacheDir(&r.CacheDir) - cli.FlagCacheImage(&r.CacheImageRef) - cli.FlagGroupPath(&r.GroupPath) - cli.FlagLayersDir(&r.LayersDir) - cli.FlagUID(&r.UID) - cli.FlagGID(&r.GID) + } + if r.PlatformAPI.AtLeast("0.7") { cli.FlagAnalyzedPath(&r.AnalyzedPath) cli.FlagSkipLayers(&r.SkipLayers) - default: - cli.FlagCacheDir(&r.CacheDir) - cli.FlagCacheImage(&r.CacheImageRef) - cli.FlagGroupPath(&r.GroupPath) - cli.FlagLayersDir(&r.LayersDir) - cli.FlagUID(&r.UID) - cli.FlagGID(&r.GID) } + cli.FlagCacheDir(&r.CacheDir) + cli.FlagCacheImage(&r.CacheImageRef) + cli.FlagGroupPath(&r.GroupPath) + cli.FlagUID(&r.UID) + cli.FlagGID(&r.GID) } // Args validates arguments and flags, and fills in default values. @@ -90,10 +78,67 @@ func (r *restoreCmd) Privileges() error { } func (r *restoreCmd) Exec() error { - if r.supportsBuildImageExtension() { - if err := r.pullBuilderImageIfNeeded(); err != nil { - return cmd.FailErr(err, "read builder image") + var ( + analyzedMD platform.AnalyzedMetadata + err error + ) + if analyzedMD, err = platform.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger); err == nil { + if r.supportsBuildImageExtension() && r.BuildImageRef != "" { + cmd.DefaultLogger.Debugf("Pulling builder image metadata...") + buildImage, err := r.pullSparse(r.BuildImageRef) + if err != nil { + return cmd.FailErr(err, "read builder image") + } + ref, err := digestReference(r.BuildImageRef, buildImage) + if err != nil { + return cmd.FailErr(err, "get digest reference for builder image") + } + analyzedMD.BuildImage = &platform.ImageIdentifier{Reference: ref} } + if r.supportsRunImageExtension() && needsPulling(analyzedMD.RunImage) { + cmd.DefaultLogger.Debugf("Pulling run image metadata...") + runImage, err := r.pullSparse(analyzedMD.RunImage.Reference) + if err != nil { + return cmd.FailErr(err, "read run image") + } + targetData, err := platform.GetTargetFromImage(runImage) + if err != nil { + return cmd.FailErr(err, "read target data from run image") + } + ref, err := digestReference(analyzedMD.RunImage.Reference, runImage) + if err != nil { + return cmd.FailErr(err, "get digest reference for builder image") + } + analyzedMD.RunImage = &platform.RunImage{ + Reference: ref, + Extend: true, + TargetMetadata: targetData, + } + } else if r.supportsTargetData() && needsUpdating(analyzedMD.RunImage) { + cmd.DefaultLogger.Debugf("Updating analyzed metadata...") + runImage, err := remote.NewImage(analyzedMD.RunImage.Reference, r.keychain) + if err != nil { + return cmd.FailErr(err, "read run image") + } + targetData, err := platform.GetTargetFromImage(runImage) + if err != nil { + return cmd.FailErr(err, "read target data from run image") + } + ref, err := digestReference(analyzedMD.RunImage.Reference, runImage) + if err != nil { + return cmd.FailErr(err, "get digest reference for builder image") + } + analyzedMD.RunImage = &platform.RunImage{ + Reference: ref, + Extend: analyzedMD.RunImage.Extend, + TargetMetadata: targetData, + } + } + if err = encoding.WriteTOML(r.AnalyzedPath, analyzedMD); err != nil { + return cmd.FailErr(err, "write analyzed metadata") + } + } else { + cmd.DefaultLogger.Warnf("Not using analyzed data, usable file not found: %s", err) } group, err := lifecycle.ReadGroup(r.GroupPath) @@ -111,64 +156,101 @@ func (r *restoreCmd) Exec() error { var appMeta platform.LayersMetadata if r.restoresLayerMetadata() { - amd, err := platform.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger) - if err == nil { - appMeta = amd.Metadata - } + appMeta = analyzedMD.Metadata } return r.restore(appMeta, group, cacheStore) } +func needsPulling(runImage *platform.RunImage) bool { + return runImage != nil && runImage.Extend +} + +func needsUpdating(runImage *platform.RunImage) bool { + if runImage == nil { + return false + } + if runImage.TargetMetadata == nil { + return true + } + digest, err := name.NewDigest(runImage.Reference) + if err != nil { + return true + } + return digest.DigestStr() == "" +} + func (r *restoreCmd) supportsBuildImageExtension() bool { return r.PlatformAPI.AtLeast("0.10") } -func (r *restoreCmd) pullBuilderImageIfNeeded() error { - if r.BuildImageRef == "" { - return nil +func (r *restoreCmd) supportsRunImageExtension() bool { + return r.PlatformAPI.AtLeast("0.12") +} + +func (r *restoreCmd) supportsTargetData() bool { + return r.PlatformAPI.AtLeast("0.12") +} + +func (r *restoreCmd) pullSparse(imageRef string) (imgutil.Image, error) { + baseCacheDir := filepath.Join(kanikoDir, "cache", "base") + if err := os.MkdirAll(baseCacheDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create cache directory: %w", err) } + // get remote image + remoteImage, err := remote.NewV1Image(imageRef, r.keychain) + if err != nil { + return nil, fmt.Errorf("failed to get remote image: %w", err) + } + // check for usable kaniko dir if _, err := os.Stat(kanikoDir); err != nil { if !os.IsNotExist(err) { - return fmt.Errorf("failed to read kaniko directory: %w", err) + return nil, fmt.Errorf("failed to read kaniko directory: %w", err) } - return nil + return nil, nil } - ref, authr, err := auth.ReferenceForRepoName(r.keychain, r.BuildImageRef) + // save to disk + h, err := remoteImage.Digest() if err != nil { - return fmt.Errorf("failed to get reference: %w", err) + return nil, fmt.Errorf("failed to get remote image digest: %w", err) } - remoteImage, err := remote.Image(ref, remote.WithAuth(authr)) + sparseImage, err := sparse.NewImage( + filepath.Join(baseCacheDir, h.String()), + remoteImage, + layout.WithMediaTypes(imgutil.DefaultTypes), + ) if err != nil { - return fmt.Errorf("failed to read image: %w", err) + return nil, fmt.Errorf("failed to initialize sparse image: %w", err) + } + if err = sparseImage.Save(); err != nil { + return nil, fmt.Errorf("failed to save sparse image: %w", err) } - buildImageHash, err := remoteImage.Digest() + return sparseImage, nil +} + +func digestReference(imageRef string, image imgutil.Image) (string, error) { + ir, err := name.ParseReference(imageRef) if err != nil { - return fmt.Errorf("failed to get digest: %w", err) + return "", err } - buildImageDigest := buildImageHash.String() - baseCacheDir := filepath.Join(kanikoDir, "cache", "base") - if err = os.MkdirAll(baseCacheDir, 0755); err != nil { - return fmt.Errorf("failed to create cache directory") + _, err = name.NewDigest(ir.String()) + if err == nil { + // if we already have a digest reference, return it + return imageRef, nil } - layoutPath, err := selective.Write(filepath.Join(baseCacheDir, buildImageDigest), empty.Index) + id, err := image.Identifier() if err != nil { - return fmt.Errorf("failed to write layout path: %w", err) - } - if err = layoutPath.AppendImage(remoteImage); err != nil { - return fmt.Errorf("failed to append image: %w", err) + return "", err } - // record digest in analyzed.toml - analyzedMD, err := lifecycle.Config.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger) + digest, err := name.NewDigest(id.String()) if err != nil { - return fmt.Errorf("failed to read analyzed metadata: %w", err) + return "", err } - digestRef, err := name.NewDigest(fmt.Sprintf("%s@%s", ref.Context().Name(), buildImageDigest), name.WeakValidation) + digestRef, err := name.NewDigest(fmt.Sprintf("%s@%s", ir.Context().Name(), digest.DigestStr()), name.WeakValidation) if err != nil { - return fmt.Errorf("failed to get digest reference: %w", err) + return "", err } - analyzedMD.BuildImage = &platform.ImageIdentifier{Reference: digestRef.String()} - return encoding.WriteTOML(r.AnalyzedPath, analyzedMD) + return digestRef.String(), nil } func (r *restoreCmd) restoresLayerMetadata() bool { diff --git a/detector.go b/detector.go index 0cb8fb84a..2f31e38ed 100644 --- a/detector.go +++ b/detector.go @@ -237,7 +237,7 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem } else { for i := range bpDescriptor.Targets { d.Logger.Debugf("Checking for match against descriptor:", bpDescriptor.Targets[i]) - if d.AnalyzeMD.RunImage.Target.IsSatisfiedBy(&bpDescriptor.Targets[i]) { + if d.AnalyzeMD.RunImage.TargetMetadata.IsSatisfiedBy(&bpDescriptor.Targets[i]) { targetMatch = true break } @@ -249,7 +249,7 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem keyFor(groupEl), buildpack.DetectOutputs{ Code: -1, - Err: fmt.Errorf("unable to satisfy Target OS/Arch constriaints: %v", d.AnalyzeMD.RunImage.Target), + Err: fmt.Errorf("unable to satisfy Target OS/Arch constriaints: %v", d.AnalyzeMD.RunImage.TargetMetadata), }) continue } diff --git a/detector_test.go b/detector_test.go index 186350c6f..1801388eb 100644 --- a/detector_test.go +++ b/detector_test.go @@ -798,12 +798,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { when("A Target is provided from AnalyzeMD", func() { it("errors if the buildpacks don't share that target arch/os", func() { detector.AnalyzeMD.RunImage = &platform.RunImage{ - Target: &platform.TargetMetadata{ - TargetPartial: buildpack.TargetPartial{ - OS: "MacOS", - Arch: "ARM64", - }, - Distribution: &buildpack.DistributionMetadata{Name: "MacOS", Version: "some kind of big cat"}, + TargetMetadata: &platform.TargetMetadata{ + OS: "MacOS", + Arch: "ARM64", + Distribution: &platform.OSDistribution{Name: "MacOS", Version: "some kind of big cat"}, }, } @@ -811,11 +809,11 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { WithAPI: "0.12", Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}, Targets: []buildpack.TargetMetadata{ - {TargetPartial: buildpack.TargetPartial{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95"}, - Distributions: []buildpack.DistributionMetadata{ + {Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95", + Distributions: []buildpack.OSDistribution{ {Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}}, - {TargetPartial: buildpack.TargetPartial{Arch: "Pentium M", OS: "Win98"}, - Distributions: []buildpack.DistributionMetadata{{Name: "Windows 2000", Version: "Server"}}}, + {Arch: "Pentium M", OS: "Win98", + Distributions: []buildpack.OSDistribution{{Name: "Windows 2000", Version: "Server"}}}, }, } dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes() @@ -840,12 +838,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { }) it("totally works if the constraints are met", func() { detector.AnalyzeMD.RunImage = &platform.RunImage{ - Target: &platform.TargetMetadata{ - TargetPartial: buildpack.TargetPartial{ - OS: "MacOS", - Arch: "ARM64", - }, - Distribution: &buildpack.DistributionMetadata{Name: "MacOS", Version: "snow cheetah"}, + TargetMetadata: &platform.TargetMetadata{ + OS: "MacOS", + Arch: "ARM64", + Distribution: &platform.OSDistribution{Name: "MacOS", Version: "snow cheetah"}, }, } @@ -853,10 +849,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { WithAPI: "0.12", Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}, Targets: []buildpack.TargetMetadata{ - {TargetPartial: buildpack.TargetPartial{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95"}, - Distributions: []buildpack.DistributionMetadata{ + {Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95", + Distributions: []buildpack.OSDistribution{ {Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}}, - {TargetPartial: buildpack.TargetPartial{Arch: "ARM64", OS: "MacOS"}, Distributions: []buildpack.DistributionMetadata{{Name: "MacOS", Version: "snow cheetah"}}}}, + {Arch: "ARM64", OS: "MacOS", Distributions: []buildpack.OSDistribution{{Name: "MacOS", Version: "snow cheetah"}}}}, } dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes() executor.EXPECT().Detect(bpA1, gomock.Any(), gomock.Any()) @@ -878,12 +874,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { }) it("was born to be wildcard compliant", func() { detector.AnalyzeMD.RunImage = &platform.RunImage{ - Target: &platform.TargetMetadata{ - TargetPartial: buildpack.TargetPartial{ - OS: "MacOS", - Arch: "ARM64", - }, - Distribution: &buildpack.DistributionMetadata{Name: "MacOS", Version: "snow cheetah"}, + TargetMetadata: &platform.TargetMetadata{ + OS: "MacOS", + Arch: "ARM64", + Distribution: &platform.OSDistribution{Name: "MacOS", Version: "snow cheetah"}, }, } @@ -891,7 +885,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { WithAPI: "0.12", Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}, Targets: []buildpack.TargetMetadata{ - {TargetPartial: buildpack.TargetPartial{Arch: "*", OS: "*"}}}, + {Arch: "*", OS: "*"}}, } dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes() executor.EXPECT().Detect(bpA1, gomock.Any(), gomock.Any()) diff --git a/go.mod b/go.mod index 6128cedeb..cb40b0c1f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/GoogleContainerTools/kaniko v1.9.2-0.20220928141902-4d077e2a4084 github.com/apex/log v1.9.0 github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230110223219-40efa3093a22 - github.com/buildpacks/imgutil v0.0.0-20230309172806-f8366062a0e6 + github.com/buildpacks/imgutil v0.0.0-20230324153732-a6c0ed910692 github.com/chrismellard/docker-credential-acr-env v0.0.0-20221129204813-6a4d6ed5d396 github.com/containerd/containerd v1.6.15 github.com/docker/docker v20.10.23+incompatible diff --git a/go.sum b/go.sum index 1c6681497..e07051067 100644 --- a/go.sum +++ b/go.sum @@ -260,8 +260,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/buildpacks/imgutil v0.0.0-20230309172806-f8366062a0e6 h1:GIVcjzbq3BveerD0F0fn8itLL5QlcSmBz9rGvUCkdxk= -github.com/buildpacks/imgutil v0.0.0-20230309172806-f8366062a0e6/go.mod h1:zL5lZzgFuv9l36n52FjomVrUHpyuZf6r1UHKaZ4LeSQ= +github.com/buildpacks/imgutil v0.0.0-20230324153732-a6c0ed910692 h1:QaSg0ifVdMjapbMAuyGjzHTP1d7Jf3xOop3qDnzhhRg= +github.com/buildpacks/imgutil v0.0.0-20230324153732-a6c0ed910692/go.mod h1:zL5lZzgFuv9l36n52FjomVrUHpyuZf6r1UHKaZ4LeSQ= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= diff --git a/image/layout_handler.go b/image/layout_handler.go index 2b22648b2..6276cc2eb 100644 --- a/image/layout_handler.go +++ b/image/layout_handler.go @@ -13,12 +13,6 @@ type LayoutHandler struct { layoutDir string } -func NewLayoutImageHandler(layoutDir string) *LayoutHandler { - return &LayoutHandler{ - layoutDir: layoutDir, - } -} - func (h *LayoutHandler) InitImage(imageRef string) (imgutil.Image, error) { if imageRef == "" { return nil, nil diff --git a/platform/files.go b/platform/files.go index 3f8ed9ff5..4289b5d16 100644 --- a/platform/files.go +++ b/platform/files.go @@ -6,8 +6,6 @@ import ( "encoding/json" "os" - "github.com/buildpacks/imgutil" - "github.com/BurntSushi/toml" "github.com/google/go-containerregistry/pkg/name" "github.com/pkg/errors" @@ -36,14 +34,21 @@ func (amd AnalyzedMetadata) PreviousImageRef() string { return amd.PreviousImage.Reference } +func (amd AnalyzedMetadata) RunImageRef() string { + if amd.RunImage == nil { + return "" + } + return amd.RunImage.Reference +} + func (amd AnalyzedMetadata) RunImageTarget() TargetMetadata { if amd.RunImage == nil { return TargetMetadata{} } - if amd.RunImage.Target == nil { + if amd.RunImage.TargetMetadata == nil { return TargetMetadata{} } - return *amd.RunImage.Target + return *amd.RunImage.TargetMetadata } // FIXME: fix key names to be accurate in the daemon case @@ -52,15 +57,23 @@ type ImageIdentifier struct { } type RunImage struct { - Reference string `toml:"reference"` - Extend bool `toml:"extend,omitempty"` - Target *TargetMetadata `json:"target,omitempty" toml:"target,omitempty"` + Reference string `toml:"reference"` + Extend bool `toml:"extend,omitempty"` + TargetMetadata *TargetMetadata `json:"target,omitempty" toml:"target,omitempty"` } type TargetMetadata struct { - buildpack.TargetPartial - ID string `json:"id" toml:"id"` - Distribution *buildpack.DistributionMetadata `json:"distribution,omitempty" toml:"distribution,omitempty"` + ID string `json:"id" toml:"id"` + OS string `json:"os" toml:"os"` + Arch string `json:"arch" toml:"arch"` + ArchVariant string `json:"arch-variant" toml:"arch-variant"` + + Distribution *OSDistribution `json:"distribution,omitempty" toml:"distribution,omitempty"` +} + +type OSDistribution struct { + Name string `json:"name" toml:"name"` + Version string `json:"version" toml:"version"` } // Satisfies treats optional fields (ArchVariant and Distributions) as wildcards if empty, returns true if @@ -89,40 +102,6 @@ func (t *TargetMetadata) IsSatisfiedBy(o *buildpack.TargetMetadata) bool { return true } -func GetTargetFromImage(image imgutil.Image) (*TargetMetadata, error) { - tm := TargetMetadata{} - if !image.Found() { - return &tm, nil - } - var err error - tm.OS, err = image.OS() - if err != nil { - return &tm, err - } - tm.Arch, err = image.Architecture() - if err != nil { - return &tm, err - } - tm.ArchVariant, err = image.Variant() - if err != nil { - return &tm, err - } - labels, err := image.Labels() - if err != nil { - return &tm, err - } - distName, distNameExists := labels["io.buildpacks.distribution.name"] - distVersion, distVersionExists := labels["io.buildpacks.distribution.version"] - if distNameExists || distVersionExists { - tm.Distribution = &buildpack.DistributionMetadata{Name: distName, Version: distVersion} - } - if id, exists := labels["io.buildpacks.id"]; exists { - tm.ID = id - } - - return &tm, nil -} - func ReadAnalyzed(analyzedPath string, logger log.Logger) (AnalyzedMetadata, error) { var analyzedMD AnalyzedMetadata if _, err := toml.DecodeFile(analyzedPath, &analyzedMD); err != nil { diff --git a/platform/files_test.go b/platform/files_test.go index 90328de8c..0e3613e0b 100644 --- a/platform/files_test.go +++ b/platform/files_test.go @@ -217,8 +217,8 @@ func testFiles(t *testing.T, when spec.G, it spec.S) { amd := platform.AnalyzedMetadata{ PreviousImage: &platform.ImageIdentifier{Reference: "the image formerly known as prince"}, RunImage: &platform.RunImage{ - Reference: "librarian", - Target: &platform.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: "os/2 warp", Arch: "486"}}, + Reference: "librarian", + TargetMetadata: &platform.TargetMetadata{OS: "os/2 warp", Arch: "486"}, }, BuildImage: &platform.ImageIdentifier{Reference: "implementation"}, } @@ -233,32 +233,33 @@ func testFiles(t *testing.T, when spec.G, it spec.S) { }) when("TargetMetadata#IsSatisfiedBy", func() { it("requires equality of OS and Arch", func() { - d := platform.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: "Win95", Arch: "Pentium"}} + d := platform.TargetMetadata{OS: "Win95", Arch: "Pentium"} - if d.IsSatisfiedBy(&buildpack.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: "Win98", Arch: d.Arch}}) { + if d.IsSatisfiedBy(&buildpack.TargetMetadata{OS: "Win98", Arch: d.Arch}) { t.Fatal("TargetMetadata with different OS were equal") } - if d.IsSatisfiedBy(&buildpack.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: d.OS, Arch: "Pentium MMX"}}) { + if d.IsSatisfiedBy(&buildpack.TargetMetadata{OS: d.OS, Arch: "Pentium MMX"}) { t.Fatal("TargetMetadata with different Arch were equal") } - if !d.IsSatisfiedBy(&buildpack.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: d.OS, Arch: d.Arch, ArchVariant: "MMX"}}) { + if !d.IsSatisfiedBy(&buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, ArchVariant: "MMX"}) { t.Fatal("blank arch variant was not treated as wildcard") } if !d.IsSatisfiedBy(&buildpack.TargetMetadata{ - TargetPartial: buildpack.TargetPartial{OS: d.OS, Arch: d.Arch}, - Distributions: []buildpack.DistributionMetadata{{Name: "a", Version: "2"}}, + OS: d.OS, + Arch: d.Arch, + Distributions: []buildpack.OSDistribution{{Name: "a", Version: "2"}}, }) { t.Fatal("blank distributions list was not treated as wildcard") } - d.Distribution = &buildpack.DistributionMetadata{Name: "A", Version: "1"} - if d.IsSatisfiedBy(&buildpack.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: d.OS, Arch: d.Arch}, Distributions: []buildpack.DistributionMetadata{{Name: "g", Version: "2"}, {Name: "B", Version: "2"}}}) { + d.Distribution = &platform.OSDistribution{Name: "A", Version: "1"} + if d.IsSatisfiedBy(&buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, Distributions: []buildpack.OSDistribution{{Name: "g", Version: "2"}, {Name: "B", Version: "2"}}}) { t.Fatal("unsatisfactory distribution lists were treated as satisfying") } - if !d.IsSatisfiedBy(&buildpack.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: d.OS, Arch: d.Arch}, Distributions: []buildpack.DistributionMetadata{}}) { + if !d.IsSatisfiedBy(&buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, Distributions: []buildpack.OSDistribution{}}) { t.Fatal("blank distributions list was not treated as wildcard") } - if !d.IsSatisfiedBy(&buildpack.TargetMetadata{TargetPartial: buildpack.TargetPartial{OS: d.OS, Arch: d.Arch}, Distributions: []buildpack.DistributionMetadata{{Name: "B", Version: "2"}, {Name: "A", Version: "1"}}}) { + if !d.IsSatisfiedBy(&buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, Distributions: []buildpack.OSDistribution{{Name: "B", Version: "2"}, {Name: "A", Version: "1"}}}) { t.Fatal("distributions list including target's distribution not recognized as satisfying") } }) diff --git a/platform/run_image.go b/platform/run_image.go new file mode 100644 index 000000000..081cc44dc --- /dev/null +++ b/platform/run_image.go @@ -0,0 +1,45 @@ +package platform + +import ( + "github.com/buildpacks/imgutil" +) + +const ( + TargetLabel = "io.buildpacks.id" + OSDistributionNameLabel = "io.buildpacks.distribution.name" + OSDistributionVersionLabel = "io.buildpacks.distribution.version" +) + +func GetTargetFromImage(image imgutil.Image) (*TargetMetadata, error) { + tm := TargetMetadata{} + if !image.Found() { + return &tm, nil + } + var err error + tm.OS, err = image.OS() + if err != nil { + return &tm, err + } + tm.Arch, err = image.Architecture() + if err != nil { + return &tm, err + } + tm.ArchVariant, err = image.Variant() + if err != nil { + return &tm, err + } + labels, err := image.Labels() + if err != nil { + return &tm, err + } + distName, distNameExists := labels[OSDistributionNameLabel] + distVersion, distVersionExists := labels[OSDistributionVersionLabel] + if distNameExists || distVersionExists { + tm.Distribution = &OSDistribution{Name: distName, Version: distVersion} + } + if id, exists := labels[TargetLabel]; exists { + tm.ID = id + } + + return &tm, nil +}