From 1057837baeb9cdce4733b668d5bac3a5d87b3fba Mon Sep 17 00:00:00 2001 From: Joe Kimmel <86852107+joe-kimmel-vmw@users.noreply.github.com> Date: Thu, 11 May 2023 11:42:38 -0700 Subject: [PATCH] Target envvars (#1078) * CNB_TARGET_ env vars - allowed to be passed through in the env vars allow-list - threaded through builder - threaded through detector - threaded through generator Signed-off-by: Joe Kimmel * Update buildpack/generate.go Co-authored-by: Natalie Arellano Signed-off-by: Joe Kimmel <86852107+joe-kimmel-vmw@users.noreply.github.com> * Update buildpack/generate.go Co-authored-by: Natalie Arellano Signed-off-by: Joe Kimmel <86852107+joe-kimmel-vmw@users.noreply.github.com> * Update cmd/lifecycle/builder.go Co-authored-by: Natalie Arellano Signed-off-by: Joe Kimmel <86852107+joe-kimmel-vmw@users.noreply.github.com> --------- Signed-off-by: Joe Kimmel Signed-off-by: Joe Kimmel <86852107+joe-kimmel-vmw@users.noreply.github.com> Co-authored-by: Natalie Arellano --- acceptance/builder_test.go | 22 ++++++ .../buildpacks/hello_world_3/0.0.3/bin/build | 34 +++++++++ .../hello_world_3/0.0.3/buildpack.toml | 7 ++ .../always_detect_plan_buildpack_3.toml | 10 +++ .../container/layers/03_layer/analyzed.toml | 9 +++ .../container/layers/03_layer/group.toml | 4 ++ .../container/layers/03_layer/plan.toml | 6 ++ .../builder/container/layers/analyzed.toml | 6 ++ builder.go | 8 ++- builder_test.go | 72 +++++++++++++++++++ cmd/lifecycle/builder.go | 19 ++--- cmd/lifecycle/creator.go | 2 +- cmd/lifecycle/detector.go | 1 + detector.go | 6 +- detector_test.go | 26 +++++++ env/build.go | 6 ++ env/build_test.go | 12 ++++ generator.go | 8 +++ generator_test.go | 44 +++++++++++- go.sum | 14 ---- platform/platform.go | 4 +- platform/run_image.go | 18 +++++ platform/run_image_test.go | 31 +++++++- 23 files changed, 340 insertions(+), 29 deletions(-) create mode 100755 acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/bin/build create mode 100644 acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/buildpack.toml create mode 100644 acceptance/testdata/builder/container/cnb/plan_tomls/always_detect_plan_buildpack_3.toml create mode 100644 acceptance/testdata/builder/container/layers/03_layer/analyzed.toml create mode 100644 acceptance/testdata/builder/container/layers/03_layer/group.toml create mode 100644 acceptance/testdata/builder/container/layers/03_layer/plan.toml create mode 100644 acceptance/testdata/builder/container/layers/analyzed.toml diff --git a/acceptance/builder_test.go b/acceptance/builder_test.go index 5ac81c6ee..ce3982e21 100644 --- a/acceptance/builder_test.go +++ b/acceptance/builder_test.go @@ -507,6 +507,28 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertStringContains(t, string(output), expected) }) }) + + when("It runs", func() { + it("sets CNB_TARGET_* vars", func() { + command := exec.Command( + "docker", + "run", + "--rm", + "--env", "CNB_PLATFORM_API="+latestPlatformAPI, + "--env", "CNB_LAYERS_DIR=/layers/03_layer", + "--env", "CNB_PLAN_PATH=/cnb/plan_tomls/always_detect_plan_buildpack_3.toml", + builderImage, + ) + output, err := command.CombinedOutput() + fmt.Println(string(output)) + h.AssertNil(t, err) + h.AssertStringContains(t, string(output), "CNB_TARGET_ARCH: amd64") + h.AssertStringContains(t, string(output), "CNB_TARGET_OS: linux") + h.AssertStringContains(t, string(output), "CNB_TARGET_VARIANT: some-variant") + h.AssertStringContains(t, string(output), "CNB_TARGET_DISTRO_NAME: ubuntu") + h.AssertStringContains(t, string(output), "CNB_TARGET_DISTRO_VERSION: some-cute-version") + }) + }) } func getBuilderMetadata(t *testing.T, path string) (string, *platform.BuildMetadata) { diff --git a/acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/bin/build b/acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/bin/build new file mode 100755 index 000000000..2d08fbd88 --- /dev/null +++ b/acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/bin/build @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -eo pipefail + +echo "---> Hello World 3 buildpack" + +# INPUT ARGUMENTS +platform_dir=$2 +env_dir=${platform_dir}/env +layers_dir=$1 +plan_path=$3 + +# CNB_APP_DIR +echo "CNB_APP_DIR: ${PWD}" + +# PLATFORM DIR +echo "PLATFORM_DIR: ${platform_dir}" + +# LAYERS +echo "LAYERS_DIR: ${layers_dir}" + +# PLAN +echo "PLAN_PATH: ${plan_path}" +echo "plan contents:" +cat ${plan_path} +echo + +echo "CNB_TARGET_ARCH:" `printenv CNB_TARGET_ARCH` +echo "CNB_TARGET_OS:" `printenv CNB_TARGET_OS` +echo "CNB_TARGET_ID:" `printenv CNB_TARGET_ID` +echo "CNB_TARGET_VARIANT:" `printenv CNB_TARGET_VARIANT` +echo "CNB_TARGET_DISTRO_NAME:" `printenv CNB_TARGET_DISTRO_NAME` +echo "CNB_TARGET_DISTRO_VERSION:" `printenv CNB_TARGET_DISTRO_VERSION` + +echo "---> Done" diff --git a/acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/buildpack.toml b/acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/buildpack.toml new file mode 100644 index 000000000..cd5ba0df1 --- /dev/null +++ b/acceptance/testdata/builder/container/cnb/buildpacks/hello_world_3/0.0.3/buildpack.toml @@ -0,0 +1,7 @@ +# Buildpack API version +api = "0.10" + +# Buildpack ID and metadata +[buildpack] +id = "hello_world_3" +version = "0.0.3" diff --git a/acceptance/testdata/builder/container/cnb/plan_tomls/always_detect_plan_buildpack_3.toml b/acceptance/testdata/builder/container/cnb/plan_tomls/always_detect_plan_buildpack_3.toml new file mode 100644 index 000000000..362415153 --- /dev/null +++ b/acceptance/testdata/builder/container/cnb/plan_tomls/always_detect_plan_buildpack_3.toml @@ -0,0 +1,10 @@ +[[entries]] + + [[entries.providers]] + id = "hello_world_3" + version = "0.0.3" + + [[entries.requires]] + name = "03_plan.toml_requires_subset_content_idk" + [entries.requires.metadata] + # arbitrary data describing the required dependency diff --git a/acceptance/testdata/builder/container/layers/03_layer/analyzed.toml b/acceptance/testdata/builder/container/layers/03_layer/analyzed.toml new file mode 100644 index 000000000..46d768ee0 --- /dev/null +++ b/acceptance/testdata/builder/container/layers/03_layer/analyzed.toml @@ -0,0 +1,9 @@ +[run-image.target] + id = "my id" + os = "linux" + arch = "amd64" + arch-variant = "some-variant" + [run-image.target.distribution] + name = "ubuntu" + version = "some-cute-version" + diff --git a/acceptance/testdata/builder/container/layers/03_layer/group.toml b/acceptance/testdata/builder/container/layers/03_layer/group.toml new file mode 100644 index 000000000..485817b8e --- /dev/null +++ b/acceptance/testdata/builder/container/layers/03_layer/group.toml @@ -0,0 +1,4 @@ +[[group]] + api = "0.10" + id = "hello_world_3" + version = "0.0.3" diff --git a/acceptance/testdata/builder/container/layers/03_layer/plan.toml b/acceptance/testdata/builder/container/layers/03_layer/plan.toml new file mode 100644 index 000000000..3f7a343a0 --- /dev/null +++ b/acceptance/testdata/builder/container/layers/03_layer/plan.toml @@ -0,0 +1,6 @@ +[[entries]] + + [[entries.providers]] + id = "hello_world_3" + version = "0.0.3" + diff --git a/acceptance/testdata/builder/container/layers/analyzed.toml b/acceptance/testdata/builder/container/layers/analyzed.toml new file mode 100644 index 000000000..dce58315b --- /dev/null +++ b/acceptance/testdata/builder/container/layers/analyzed.toml @@ -0,0 +1,6 @@ +[run-image] + [target] + id = "software" + os = "linux" + arch = "amd64" + diff --git a/builder.go b/builder.go index 40cc3cffe..eb5ed26a6 100644 --- a/builder.go +++ b/builder.go @@ -44,6 +44,7 @@ type Builder struct { Out, Err io.Writer Plan platform.BuildPlan PlatformAPI *api.Version + AnalyzeMD platform.AnalyzedMetadata } func (b *Builder) Build() (*platform.BuildMetadata, error) { @@ -63,7 +64,12 @@ func (b *Builder) Build() (*platform.BuildMetadata, error) { ) processMap := newProcessMap() inputs := b.getBuildInputs() - inputs.Env = env.NewBuildEnv(os.Environ()) + if b.AnalyzeMD.RunImage != nil && b.AnalyzeMD.RunImage.TargetMetadata != nil && b.PlatformAPI.AtLeast("0.12") { + inputs.Env = env.NewBuildEnv(append(os.Environ(), platform.EnvVarsFor(b.AnalyzeMD.RunImage.TargetMetadata)...)) + } else { + inputs.Env = env.NewBuildEnv(os.Environ()) + } + filteredPlan := b.Plan for _, bp := range b.Group.Group { diff --git a/builder_test.go b/builder_test.go index ff0333409..10cbf29c5 100644 --- a/builder_test.go +++ b/builder_test.go @@ -177,9 +177,81 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { _, err := builder.Build() h.AssertNil(t, err) }) + it("gets the correct env vars", func() { + builder.AnalyzeMD.RunImage = &platform.RunImage{Reference: "foo", TargetMetadata: &platform.TargetMetadata{ + OS: "linux", + Arch: "amd64", + }} + + bpA := &buildpack.BpDescriptor{Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}} + + executor.EXPECT().Build(*bpA, gomock.Any(), gomock.Any()).DoAndReturn( + func(_ buildpack.BpDescriptor, inputs buildpack.BuildInputs, logger llog.Logger) (buildpack.BuildOutputs, error) { + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + return buildpack.BuildOutputs{}, nil + }, + ) + bpB := &buildpack.BpDescriptor{Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "B", Version: "v1"}}} + + dirStore.EXPECT().LookupBp("A", "v1").Return(bpA, nil) + dirStore.EXPECT().LookupBp("B", "v2").Return(bpB, nil) + executor.EXPECT().Build(*bpB, gomock.Any(), gomock.Any()).Do( + func(_ buildpack.BpDescriptor, inputs buildpack.BuildInputs, _ llog.Logger) (buildpack.BuildOutputs, error) { + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + return buildpack.BuildOutputs{}, nil + }) + + _, err := builder.Build() + h.AssertNil(t, err) + }) + it("doesnt gets the new env vars if its old", func() { + builder.PlatformAPI = api.MustParse("0.8") + builder.AnalyzeMD.RunImage = &platform.RunImage{Reference: "foo", TargetMetadata: &platform.TargetMetadata{ + OS: "linux", + Arch: "amd64", + }} + + bpA := &buildpack.BpDescriptor{Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}} + + executor.EXPECT().Build(*bpA, gomock.Any(), gomock.Any()).DoAndReturn( + func(_ buildpack.BpDescriptor, inputs buildpack.BuildInputs, logger llog.Logger) (buildpack.BuildOutputs, error) { + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + return buildpack.BuildOutputs{}, nil + }, + ) + bpB := &buildpack.BpDescriptor{Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "B", Version: "v1"}}} + + dirStore.EXPECT().LookupBp("A", "v1").Return(bpA, nil) + dirStore.EXPECT().LookupBp("B", "v2").Return(bpB, nil) + executor.EXPECT().Build(*bpB, gomock.Any(), gomock.Any()).Do( + func(_ buildpack.BpDescriptor, inputs buildpack.BuildInputs, _ llog.Logger) (buildpack.BuildOutputs, error) { + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertDoesNotContain(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + return buildpack.BuildOutputs{}, nil + }) + + _, err := builder.Build() + h.AssertNil(t, err) + }) it("provides the updated environment to the next buildpack", func() { bpA := &buildpack.BpDescriptor{Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}} + executor.EXPECT().Build(*bpA, gomock.Any(), gomock.Any()).DoAndReturn( func(_ buildpack.BpDescriptor, inputs buildpack.BuildInputs, logger llog.Logger) (buildpack.BuildOutputs, error) { envPtr := inputs.Env.(*env.Env) diff --git a/cmd/lifecycle/builder.go b/cmd/lifecycle/builder.go index b78023219..37d82e6ce 100644 --- a/cmd/lifecycle/builder.go +++ b/cmd/lifecycle/builder.go @@ -22,14 +22,12 @@ type buildCmd struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (b *buildCmd) DefineFlags() { switch { + case b.PlatformAPI.AtLeast("0.12"): + cli.FlagAnalyzedPath(&b.AnalyzedPath) + fallthrough case b.PlatformAPI.AtLeast("0.11"): - cli.FlagAppDir(&b.AppDir) cli.FlagBuildConfigDir(&b.BuildConfigDir) - cli.FlagBuildpacksDir(&b.BuildpacksDir) - cli.FlagGroupPath(&b.GroupPath) - cli.FlagLayersDir(&b.LayersDir) - cli.FlagPlanPath(&b.PlanPath) - cli.FlagPlatformDir(&b.PlatformDir) + fallthrough default: cli.FlagAppDir(&b.AppDir) cli.FlagBuildpacksDir(&b.BuildpacksDir) @@ -67,10 +65,14 @@ func (b *buildCmd) Exec() error { if err = verifyBuildpackApis(group); err != nil { return err } - return b.build(group, plan) + amd, err := platform.ReadAnalyzed(b.AnalyzedPath, cmd.DefaultLogger) + if err != nil { + return unwrapErrorFailWithMessage(err, "reading analyzed.toml") + } + return b.build(group, plan, amd) } -func (b *buildCmd) build(group buildpack.Group, plan platform.BuildPlan) error { +func (b *buildCmd) build(group buildpack.Group, plan platform.BuildPlan, analyzedMD platform.AnalyzedMetadata) error { builder := &lifecycle.Builder{ AppDir: b.AppDir, BuildConfigDir: b.BuildConfigDir, @@ -84,6 +86,7 @@ func (b *buildCmd) build(group buildpack.Group, plan platform.BuildPlan) error { Err: cmd.Stderr, Plan: plan, PlatformAPI: b.PlatformAPI, + AnalyzeMD: analyzedMD, } md, err := builder.Build() if err != nil { diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go index 907f9ff17..68fe8ad13 100644 --- a/cmd/lifecycle/creator.go +++ b/cmd/lifecycle/creator.go @@ -230,7 +230,7 @@ func (c *createCmd) Exec() error { stopPinging := startPinging(c.docker) // send pings to docker daemon while building to prevent connection closure cmd.DefaultLogger.Phase("BUILDING") buildCmd := &buildCmd{Platform: c.Platform} - err = buildCmd.build(group, plan) + err = buildCmd.build(group, plan, analyzedMD) stopPinging() if err != nil { return err diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go index 68108610d..52252a229 100644 --- a/cmd/lifecycle/detector.go +++ b/cmd/lifecycle/detector.go @@ -103,6 +103,7 @@ func (d *detectCmd) Exec() error { group.GroupExtensions, d.GeneratedDir, plan, + d.PlatformAPI, d.PlatformDir, d.RunPath, cmd.Stdout, cmd.Stderr, diff --git a/detector.go b/detector.go index 0aaed8b2d..eef2bb9ef 100644 --- a/detector.go +++ b/detector.go @@ -281,7 +281,11 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem AppDir: d.AppDir, BuildConfigDir: d.BuildConfigDir, PlatformDir: d.PlatformDir, - Env: env.NewBuildEnv(os.Environ()), + } + if d.AnalyzeMD.RunImage != nil && d.AnalyzeMD.RunImage.TargetMetadata != nil && d.PlatformAPI.AtLeast("0.12") { + inputs.Env = env.NewBuildEnv(append(os.Environ(), platform.EnvVarsFor(d.AnalyzeMD.RunImage.TargetMetadata)...)) + } else { + inputs.Env = env.NewBuildEnv(os.Environ()) } d.Runs.Store(key, d.Executor.Detect(descriptor, inputs, d.Logger)) // this is where we finally invoke bin/detect } diff --git a/detector_test.go b/detector_test.go index bbc88fff5..92d67b8ff 100644 --- a/detector_test.go +++ b/detector_test.go @@ -199,6 +199,32 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { _, _, _ = detector.Detect() }) + it("passes through the CNB_TARGET_* env vars", func() { + bpA1 := &buildpack.BpDescriptor{ + WithAPI: "0.8", + Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}}, + } + detector.AnalyzeMD = platform.AnalyzedMetadata{RunImage: &platform.RunImage{TargetMetadata: &platform.TargetMetadata{OS: "linux", Arch: "amd64"}}} + dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes() + executor.EXPECT().Detect(bpA1, gomock.Any(), gomock.Any()).Do( + func(_ buildpack.Descriptor, inputs buildpack.DetectInputs, _ log.Logger) buildpack.DetectOutputs { + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + return buildpack.DetectOutputs{} + }) + + group := []buildpack.GroupElement{ + {ID: bpA1.Buildpack.ID, Version: bpA1.Buildpack.Version, API: bpA1.WithAPI, Optional: true}, + } + resolver.EXPECT().Resolve(group, detector.Runs) + + detector.Order = buildpack.Order{{Group: group}} + _, _, _ = detector.Detect() + }) + it("expands order-containing buildpack IDs", func() { // This test doesn't use gomock.InOrder() because each call to Detect() happens in a go func. // The order that other calls are written in is the order that they happen in. diff --git a/env/build.go b/env/build.go index ee2e7f25a..10ee0d324 100644 --- a/env/build.go +++ b/env/build.go @@ -8,6 +8,12 @@ import ( // BuildEnvIncludelist are env vars that, if set in the lifecycle's execution environment - either in a builder or by the platform, are passed-through to buildpack executables var BuildEnvIncludelist = []string{ "CNB_STACK_ID", // deprecated as of api 0.12.0 + "CNB_TARGET_OS", + "CNB_TARGET_ARCH", + "CNB_TARGET_ID", + "CNB_TARGET_VARIANT", + "CNB_TARGET_DISTRO_NAME", + "CNB_TARGET_DISTRO_VERSION", "HOSTNAME", "HOME", "HTTPS_PROXY", diff --git a/env/build_test.go b/env/build_test.go index 07db7e6cd..93e64a7b9 100644 --- a/env/build_test.go +++ b/env/build_test.go @@ -35,6 +35,12 @@ func testBuildEnv(t *testing.T, when spec.G, it spec.S) { it("includes expected vars", func() { benv := env.NewBuildEnv([]string{ "CNB_STACK_ID=some-stack-id", + "CNB_TARGET_ARCH=st-louis", + "CNB_TARGET_OS=BeOS", + "CNB_TARGET_ID=tahr-jzay", + "CNB_TARGET_VARIANT=suburban", + "CNB_TARGET_DISTRO_NAME=web", + "CNB_TARGET_DISTRO_VERSION=3.0", "HOSTNAME=some-hostname", "HOME=some-home", "HTTPS_PROXY=some-https-proxy", @@ -54,6 +60,12 @@ func testBuildEnv(t *testing.T, when spec.G, it spec.S) { sort.Strings(out) expectedVars := []string{ "CNB_STACK_ID=some-stack-id", + "CNB_TARGET_ARCH=st-louis", + "CNB_TARGET_DISTRO_NAME=web", + "CNB_TARGET_DISTRO_VERSION=3.0", + "CNB_TARGET_ID=tahr-jzay", + "CNB_TARGET_OS=BeOS", + "CNB_TARGET_VARIANT=suburban", "CPATH=some-cpath", "HOME=some-home", "HOSTNAME=some-hostname", diff --git a/generator.go b/generator.go index c849909ab..13dc9bc71 100644 --- a/generator.go +++ b/generator.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/env" "github.com/buildpacks/lifecycle/internal/fsutil" @@ -18,6 +20,7 @@ type Generator struct { AppDir string BuildConfigDir string GeneratedDir string // e.g., /generated + PlatformAPI *api.Version PlatformDir string AnalyzedMD platform.AnalyzedMetadata DirStore DirStore @@ -54,6 +57,7 @@ func (f *GeneratorFactory) NewGenerator( extensions []buildpack.GroupElement, generatedDir string, plan platform.BuildPlan, + platformAPI *api.Version, platformDir string, runPath string, stdout, stderr io.Writer, @@ -63,6 +67,7 @@ func (f *GeneratorFactory) NewGenerator( AppDir: appDir, BuildConfigDir: buildConfigDir, GeneratedDir: generatedDir, + PlatformAPI: platformAPI, PlatformDir: platformDir, DirStore: f.dirStore, Executor: &buildpack.DefaultGenerateExecutor{}, @@ -134,6 +139,9 @@ func (g *Generator) Generate() (GenerateResult, error) { g.Logger.Debug("Finding plan") inputs.Plan = filteredPlan.Find(buildpack.KindExtension, ext.ID) + if g.AnalyzedMD.RunImage != nil && g.AnalyzedMD.RunImage.TargetMetadata != nil && g.PlatformAPI.AtLeast("0.12") { + inputs.Env = env.NewBuildEnv(append(inputs.Env.List(), platform.EnvVarsFor(g.AnalyzedMD.RunImage.TargetMetadata)...)) + } g.Logger.Debug("Invoking command") result, err := g.Executor.Generate(*descriptor, inputs, g.Logger) if err != nil { diff --git a/generator_test.go b/generator_test.go index e25ebb939..6527cf527 100644 --- a/generator_test.go +++ b/generator_test.go @@ -86,6 +86,7 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) { }, "some-output-dir", providedPlan, + api.Platform.Latest(), "some-platform-dir", "some-run-path", stdout, stderr, @@ -154,6 +155,7 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) { Logger: &log.Logger{Handler: logHandler}, GeneratedDir: generatedDir, Plan: platform.BuildPlan{}, + PlatformAPI: api.Platform.Latest(), PlatformDir: platformDir, Err: stderr, Out: stdout, @@ -248,7 +250,47 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) { _, err := generator.Generate() h.AssertNil(t, err) }) - + it("passes through CNB_TARGET environment variables", func() { + generator.AnalyzedMD = platform.AnalyzedMetadata{ + RunImage: &platform.RunImage{ + TargetMetadata: &platform.TargetMetadata{ + OS: "linux", + Arch: "amd64", + }, + }, + } + // mock generate for extensions - these are tested elsewhere so we just need to return anything... + dirStore.EXPECT().LookupExt(gomock.Any(), gomock.Any()).Return(&extA, nil) + dirStore.EXPECT().LookupExt(gomock.Any(), gomock.Any()).Return(&extB, nil) + // extension A has a build.Dockerfile and an extend-config.toml + h.Mkdir(t, filepath.Join(tmpDir, "A")) + buildDockerfilePathA := filepath.Join(tmpDir, "A", "build.Dockerfile") + h.Mkfile(t, "some-build.Dockerfile-content-A", buildDockerfilePathA) + extendConfigPathA := filepath.Join(tmpDir, "A", "extend-config.toml") + h.Mkfile(t, "some-extend-config.toml-content-A", extendConfigPathA) + executor.EXPECT().Generate(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(d buildpack.ExtDescriptor, inputs buildpack.GenerateInputs, _ *log.Logger) (buildpack.GenerateOutputs, error) { + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + return buildpack.GenerateOutputs{Dockerfiles: []buildpack.DockerfileInfo{{ExtensionID: d.Extension.ID, + Kind: "build", Path: buildDockerfilePathA}}}, nil + }) + executor.EXPECT().Generate(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(d buildpack.ExtDescriptor, inputs buildpack.GenerateInputs, _ *log.Logger) (buildpack.GenerateOutputs, error) { + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_ARCH=amd64") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_NAME=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_DISTRO_VERSION=") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_OS=linux") + h.AssertContains(t, inputs.Env.List(), "CNB_TARGET_VARIANT=") + return buildpack.GenerateOutputs{Dockerfiles: []buildpack.DockerfileInfo{{ExtensionID: d.Extension.ID, + Kind: "build", Path: buildDockerfilePathA}}}, nil + }) + _, err := generator.Generate() + h.AssertNil(t, err) + }) it("copies Dockerfiles and extend-config.toml files to the correct locations", func() { // mock generate for extension A dirStore.EXPECT().LookupExt("A", "v1").Return(&extA, nil) diff --git a/go.sum b/go.sum index 1316ecad0..22b83530e 100644 --- a/go.sum +++ b/go.sum @@ -123,10 +123,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/buildpacks/imgutil v0.0.0-20230412223147-81015c668834 h1:V3xhD9kbougG1QgtpA60UK6JnBPW7gX17zZ2ekXwUpo= -github.com/buildpacks/imgutil v0.0.0-20230412223147-81015c668834/go.mod h1:hgxVR7UpPvT5gATbRGM582oy048sUocDg6R6PMWAxow= -github.com/buildpacks/imgutil v0.0.0-20230420161652-580610d0124b h1:TsOLD4J7TcHQ7aaEYTDd+nEMSku3vPqq6UIyOR8IG9Q= -github.com/buildpacks/imgutil v0.0.0-20230420161652-580610d0124b/go.mod h1:hgxVR7UpPvT5gATbRGM582oy048sUocDg6R6PMWAxow= github.com/buildpacks/imgutil v0.0.0-20230428141433-24db5a78c900 h1:f6SrGzyotuJxn+BuIQC3ZBXQiNKgeXhWZLnAJEavaxI= github.com/buildpacks/imgutil v0.0.0-20230428141433-24db5a78c900/go.mod h1:/xuDxsWO9JE/s95g+OfXB8C+G5TeHznq7vURY2s1yPM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -180,8 +176,6 @@ github.com/docker/cli v23.0.4+incompatible h1:xClB7PsiATttDHj8ce5qvJcikiApNy7teR github.com/docker/cli v23.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0AA2Ibj+nNo4ek= -github.com/docker/docker v23.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k= github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= @@ -329,8 +323,6 @@ github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1q github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -393,8 +385,6 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= @@ -451,7 +441,6 @@ github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -485,10 +474,7 @@ github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKw github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa h1:XOFp/3aBXlqmOFAg3r6e0qQjPnK5I970LilqX+Is1W8= github.com/tonistiigi/fsutil v0.0.0-20230105215944-fb433841cbfa/go.mod h1:AvLEd1LEIl64G2Jpgwo7aVV5lGH0ePcKl0ygGIHNYl8= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= -github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= diff --git a/platform/platform.go b/platform/platform.go index f97532fb3..8820ab187 100644 --- a/platform/platform.go +++ b/platform/platform.go @@ -1,6 +1,8 @@ package platform -import "github.com/buildpacks/lifecycle/api" +import ( + "github.com/buildpacks/lifecycle/api" +) type LifecyclePhase int diff --git a/platform/run_image.go b/platform/run_image.go index 999dee7ad..8d70aed11 100644 --- a/platform/run_image.go +++ b/platform/run_image.go @@ -82,3 +82,21 @@ func GetTargetFromImage(image imgutil.Image) (*TargetMetadata, error) { return &tm, nil } + +// Fulfills the prophecy set forth in https://github.com/buildpacks/rfcs/blob/b8abe33f2bdc58792acf0bd094dc4ce3c8a54dbb/text/0096-remove-stacks-mixins.md?plain=1#L97 +// by returning an array of "VARIABLE=value" strings suitable for inclusion in your environment or complete breakfast. +func EnvVarsFor(tm *TargetMetadata) []string { + ret := []string{"CNB_TARGET_OS=" + tm.OS, "CNB_TARGET_ARCH=" + tm.Arch} + ret = append(ret, "CNB_TARGET_VARIANT="+tm.ArchVariant) + var distName, distVersion string + if tm.Distribution != nil { + distName = tm.Distribution.Name + distVersion = tm.Distribution.Version + } + ret = append(ret, "CNB_TARGET_DISTRO_NAME="+distName) + ret = append(ret, "CNB_TARGET_DISTRO_VERSION="+distVersion) + if tm.ID != "" { + ret = append(ret, "CNB_TARGET_ID="+tm.ID) + } + return ret +} diff --git a/platform/run_image_test.go b/platform/run_image_test.go index 4d96c8eb4..1d9f5b6b4 100644 --- a/platform/run_image_test.go +++ b/platform/run_image_test.go @@ -4,12 +4,13 @@ import ( "path/filepath" "testing" + "github.com/buildpacks/lifecycle/platform" + h "github.com/buildpacks/lifecycle/testhelpers" + "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle/api" - "github.com/buildpacks/lifecycle/platform" - h "github.com/buildpacks/lifecycle/testhelpers" ) func TestRunImage(t *testing.T) { @@ -130,4 +131,30 @@ func testRunImage(t *testing.T, when spec.G, it spec.S) { }) }) }) + + when("we want to get EnvVarsFor a platform.TargetMetadata", func() { + it("returns the right thing", func() { + tm := platform.TargetMetadata{Arch: "pentium", ArchVariant: "mmx", ID: "my-id", OS: "linux", Distribution: &platform.OSDistribution{Name: "nix", Version: "22.11"}} + observed := platform.EnvVarsFor(&tm) + h.AssertContains(t, observed, "CNB_TARGET_ARCH="+tm.Arch) + h.AssertContains(t, observed, "CNB_TARGET_VARIANT="+tm.ArchVariant) + h.AssertContains(t, observed, "CNB_TARGET_DISTRO_NAME="+tm.Distribution.Name) + h.AssertContains(t, observed, "CNB_TARGET_DISTRO_VERSION="+tm.Distribution.Version) + h.AssertContains(t, observed, "CNB_TARGET_ID="+tm.ID) + h.AssertContains(t, observed, "CNB_TARGET_OS="+tm.OS) + h.AssertEq(t, len(observed), 6) + }) + it("does not return the wrong thing", func() { + tm := platform.TargetMetadata{Arch: "pentium", OS: "linux"} + observed := platform.EnvVarsFor(&tm) + h.AssertContains(t, observed, "CNB_TARGET_ARCH="+tm.Arch) + h.AssertContains(t, observed, "CNB_TARGET_OS="+tm.OS) + // note: per the spec only the ID field is optional, so I guess the others should always be set: https://github.com/buildpacks/rfcs/blob/main/text/0096-remove-stacks-mixins.md#runtime-metadata + // the empty ones: + h.AssertContains(t, observed, "CNB_TARGET_VARIANT=") + h.AssertContains(t, observed, "CNB_TARGET_DISTRO_NAME=") + h.AssertContains(t, observed, "CNB_TARGET_DISTRO_VERSION=") + h.AssertEq(t, len(observed), 5) + }) + }) }