From 5010715ac860a77d91e60996493317f96a68d848 Mon Sep 17 00:00:00 2001 From: Forest Eckhardt Date: Wed, 16 Aug 2023 19:32:10 +0000 Subject: [PATCH] Adds the ability to add additional labels to stacks at build time --- commands/create_stack.go | 4 +++ integration/create_stack_test.go | 7 ++++++ internal/ihop/creator.go | 12 +++++++++ internal/ihop/creator_test.go | 42 ++++++++++++++++++++++++++++++++ internal/ihop/definition.go | 3 +++ 5 files changed, 68 insertions(+) diff --git a/commands/create_stack.go b/commands/create_stack.go index 33e49c2..ab514d9 100644 --- a/commands/create_stack.go +++ b/commands/create_stack.go @@ -24,6 +24,7 @@ type createStackFlags struct { publish bool buildReference string runReference string + labels []string } func createStack() *cobra.Command { @@ -43,6 +44,7 @@ func createStack() *cobra.Command { cmd.Flags().BoolVar(&flags.publish, "publish", false, "publish to a registry") cmd.Flags().StringVar(&flags.buildReference, "build-ref", "", "reference that specifies where to publish the build image (required)") cmd.Flags().StringVar(&flags.runReference, "run-ref", "", "reference that specifies where to publish the run image (required)") + cmd.Flags().StringSliceVar(&flags.labels, "label", nil, "additional image label to be added to build and run image") err := cmd.MarkFlagRequired("config") if err != nil { @@ -78,6 +80,8 @@ func createStackRun(flags createStackFlags) error { _, definition.IncludeExperimentalSBOM = os.LookupEnv("EXPERIMENTAL_ATTACH_RUN_IMAGE_SBOM") + definition.Labels = flags.labels + scratch, err := os.MkdirTemp("", "") if err != nil { return err diff --git a/integration/create_stack_test.go b/integration/create_stack_test.go index 38bad0b..3437def 100644 --- a/integration/create_stack_test.go +++ b/integration/create_stack_test.go @@ -47,6 +47,7 @@ func testCreateStack(t *testing.T, _ spec.G, it spec.S) { "--build-output", filepath.Join(tmpDir, "build.oci"), "--run-output", filepath.Join(tmpDir, "run.oci"), "--secret", "some-secret=my-secret-value", + "--label", "additional.label=label-value", ) command.Env = append(os.Environ(), "EXPERIMENTAL_ATTACH_RUN_IMAGE_SBOM=true") session, err := gexec.Start(command, buffer, buffer) @@ -116,6 +117,7 @@ func testCreateStack(t *testing.T, _ spec.G, it spec.S) { HaveKeyWithValue("io.buildpacks.stack.mixins", ContainSubstring(`"build:git"`)), HaveKeyWithValue("io.paketo.stack.packages", ContainSubstring(`"openssl"`)), HaveKeyWithValue("platform", "amd64"), + HaveKeyWithValue("additional.label", "label-value"), )) Expect(file.Config.Labels).NotTo(HaveKeyWithValue("io.buildpacks.stack.mixins", ContainSubstring("run:"))) @@ -189,6 +191,7 @@ func testCreateStack(t *testing.T, _ spec.G, it spec.S) { HaveKeyWithValue("io.buildpacks.stack.mixins", ContainSubstring(`"openssl"`)), HaveKeyWithValue("io.paketo.stack.packages", ContainSubstring(`"openssl"`)), HaveKeyWithValue("io.buildpacks.base.sbom", file.RootFS.DiffIDs[len(file.RootFS.DiffIDs)-1].String()), + HaveKeyWithValue("additional.label", "label-value"), )) Expect(file.Config.Labels).NotTo(HaveKeyWithValue("io.buildpacks.stack.mixins", ContainSubstring("build:"))) @@ -241,11 +244,13 @@ func testCreateStack(t *testing.T, _ spec.G, it spec.S) { " Adding io.buildpacks.stack.* labels", " Adding io.buildpacks.stack.mixins label", " Adding io.paketo.stack.packages label", + " Adding additional.label label", " Creating cnb user", " run: Decorating base image", " Adding io.buildpacks.stack.* labels", " Adding io.buildpacks.stack.mixins label", " Adding io.paketo.stack.packages label", + " Adding additional.label label", " Creating cnb user", " Updating /etc/os-release", " Attaching experimental SBOM", @@ -260,11 +265,13 @@ func testCreateStack(t *testing.T, _ spec.G, it spec.S) { " Adding io.buildpacks.stack.* labels", " Adding io.buildpacks.stack.mixins label", " Adding io.paketo.stack.packages label", + " Adding additional.label label", " Creating cnb user", " run: Decorating base image", " Adding io.buildpacks.stack.* labels", " Adding io.buildpacks.stack.mixins label", " Adding io.paketo.stack.packages label", + " Adding additional.label label", " Creating cnb user", " Updating /etc/os-release", " Attaching experimental SBOM", diff --git a/internal/ihop/creator.go b/internal/ihop/creator.go index 886f8f3..e2bce11 100644 --- a/internal/ihop/creator.go +++ b/internal/ihop/creator.go @@ -3,6 +3,7 @@ package ihop import ( "encoding/json" "fmt" + "strings" "time" "github.com/paketo-buildpacks/packit/v2/scribe" @@ -202,6 +203,17 @@ func (c Creator) mutate(image Image, def Definition, imageDef DefinitionImage, s } } + for _, label := range def.Labels { + kv := strings.Split(label, "=") + if len(kv) != 2 { + return Image{}, fmt.Errorf("label input %q malformed: should be in the form of =", label) + } + + c.logger.Action("Adding %s label", kv[0]) + + image.Labels[kv[0]] = kv[1] + } + // create and attach a layer that creates a cnb user in the container image // filesystem c.logger.Action("Creating cnb user") diff --git a/internal/ihop/creator_test.go b/internal/ihop/creator_test.go index 5fb03d1..1ff220c 100644 --- a/internal/ihop/creator_test.go +++ b/internal/ihop/creator_test.go @@ -752,6 +752,48 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { }) }) + context("when additional labels are given", func() { + it("adds those labels to the build and run image", func() { + _, err := creator.Execute(ihop.Definition{ + ID: "some-stack-id", + Homepage: "some-stack-homepage", + Maintainer: "some-stack-maintainer", + Platforms: []string{"some-platform"}, + IncludeExperimentalSBOM: true, + Build: ihop.DefinitionImage{ + Description: "some-stack-build-description", + Dockerfile: "test-base-build-dockerfile-path", + Args: map[string]any{ + "sources": "test-sources", + "packages": "test-build-packages", + }, + UID: 1234, + GID: 2345, + }, + Run: ihop.DefinitionImage{ + Description: "some-stack-run-description", + Dockerfile: "test-base-run-dockerfile-path", + Args: map[string]any{ + "sources": "test-sources", + "packages": "test-run-packages", + }, + UID: 3456, + GID: 4567, + }, + Labels: []string{"additional.label=label-value"}, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(imageClient.UpdateCall.CallCount).To(Equal(2)) + + Expect(imageUpdateInvocations[0].Image.Digest).To(Equal("image-digest-1")) + Expect(imageUpdateInvocations[0].Image.Labels).To(HaveKeyWithValue("additional.label", "label-value")) + + Expect(imageUpdateInvocations[1].Image.Digest).To(Equal("image-digest-2")) + Expect(imageUpdateInvocations[1].Image.Labels).To(HaveKeyWithValue("additional.label", "label-value")) + }) + }) + context("failure cases", func() { context("when the build image promise errors", func() { it.Before(func() { diff --git a/internal/ihop/definition.go b/internal/ihop/definition.go index 7f93129..47bf6ae 100644 --- a/internal/ihop/definition.go +++ b/internal/ihop/definition.go @@ -48,6 +48,9 @@ type Definition struct { // IncludeExperimentalSBOM can be used to attach an experimental SBOM layer // to the run image. IncludeExperimentalSBOM bool `toml:"-"` + + // Labels can be used to add custom labels to the build and run image. + Labels []string `toml:"-"` } type DefinitionImagePlatforms struct {