Repository: buildpack/pack (go)

Issue:
Show mixins in `inspect-builder`
Include Stack Mixins in output of `inspect-builder`

Example output:

```
Inspecting builder: example/builder:tag

REMOTE:
...
Stack:
  ID: remote-builder-stack-id
  Mixins:
    mixinA
    build:mixinB
...

LOCAL:
...
Stack:
  ID: local-builder-stack-id
  Mixins:
    mixinX
    build:mixinY
...
```

Pull request:
Adds mixins to inspect-builder output
* Fixes #379

Signed-off-by: Simon Jones <sijones@pivotal.io>
Signed-off-by: Emily Casey <ecasey@pivotal.io>

Patch:
diff --git a/inspect_builder.go b/inspect_builder.go
index e8c41b01..45ce888f 100644
--- a/inspect_builder.go
+++ b/inspect_builder.go
@@ -2,6 +2,7 @@ package pack
 
 import (
 	"context"
+	"strings"
 
 	"github.com/pkg/errors"
 
@@ -14,6 +15,7 @@ import (
 type BuilderInfo struct {
 	Description     string
 	Stack           string
+	Mixins          []string
 	RunImage        string
 	RunImageMirrors []string
 	Buildpacks      []builder.BuildpackMetadata
@@ -41,9 +43,20 @@ func (c *Client) InspectBuilder(name string, daemon bool) (*BuilderInfo, error)
 		return nil, errors.Wrapf(err, "invalid builder %s", style.Symbol(name))
 	}
 
+	var commonMixins, buildMixins []string
+	commonMixins = []string{}
+	for _, mixin := range bldr.Mixins() {
+		if strings.HasPrefix(mixin, "build:") {
+			buildMixins = append(buildMixins, mixin)
+		} else {
+			commonMixins = append(commonMixins, mixin)
+		}
+	}
+
 	return &BuilderInfo{
 		Description:     bldr.Description(),
 		Stack:           bldr.StackID,
+		Mixins:          append(commonMixins, buildMixins...),
 		RunImage:        bldr.Stack().RunImage.Image,
 		RunImageMirrors: bldr.Stack().RunImage.Mirrors,
 		Buildpacks:      bldr.Buildpacks(),
diff --git a/internal/commands/inspect_builder.go b/internal/commands/inspect_builder.go
index 862cc320..c8e12a9a 100644
--- a/internal/commands/inspect_builder.go
+++ b/internal/commands/inspect_builder.go
@@ -107,7 +107,14 @@ Created By:
 
 {{ end -}}
 
-Stack: {{ .Info.Stack }}
+Stack:
+  ID: {{ .Info.Stack }}
+{{- if ne (len .Info.Mixins) 0 }}
+  Mixins:
+{{- end }}
+{{- range $index, $mixin := .Info.Mixins }}
+    {{ $mixin }}
+{{- end }}
 
 Lifecycle:
   Version: {{ .Info.Lifecycle.Info.Version }}


Test patch:
diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go
index e64f9d75..72925ec1 100644
--- a/acceptance/acceptance_test.go
+++ b/acceptance/acceptance_test.go
@@ -1384,11 +1384,10 @@ func createBuilder(t *testing.T, runImageMirror, builderTOMLPath, packPath, life
 func createStack(t *testing.T, dockerCli *client.Client, runImageMirror string) error {
 	t.Log("creating stack images...")
 
-	if err := createStackImage(dockerCli, runImage, filepath.Join("testdata", "mock_stack")); err != nil {
+	if err := createStackImage(dockerCli, runImage, filepath.Join("testdata", "mock_stack", "run")); err != nil {
 		return err
 	}
-
-	if err := dockerCli.ImageTag(context.Background(), runImage, buildImage); err != nil {
+	if err := createStackImage(dockerCli, buildImage, filepath.Join("testdata", "mock_stack", "build")); err != nil {
 		return err
 	}
 
diff --git a/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/buildpack.toml b/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/buildpack.toml
index 6380dca9..91c8a8f6 100644
--- a/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/buildpack.toml
+++ b/acceptance/testdata/mock_buildpacks/0.2/simple-layers-buildpack/buildpack.toml
@@ -6,4 +6,5 @@ api = "0.2"
   name = "Simple Layers Buildpack"
 
 [[stacks]]
-  id = "pack.test.stack"
\ No newline at end of file
+  id = "pack.test.stack"
+  mixins = ["netcat"]
diff --git a/acceptance/testdata/mock_stack/build/Dockerfile b/acceptance/testdata/mock_stack/build/Dockerfile
new file mode 100644
index 00000000..577efed5
--- /dev/null
+++ b/acceptance/testdata/mock_stack/build/Dockerfile
@@ -0,0 +1,14 @@
+FROM ubuntu:bionic
+
+ENV CNB_USER_ID=2222
+ENV CNB_GROUP_ID=3333
+
+RUN \
+  groupadd pack --gid 3333 && \
+  useradd --uid 2222 --gid 3333 -m -s /bin/bash pack
+
+RUN apt-get update && apt-get -yq install netcat
+LABEL io.buildpacks.stack.id=pack.test.stack
+LABEL io.buildpacks.stack.mixins="[\"mixinA\", \"build:mixinTwo\", \"netcat\", \"mixin3\"]"
+
+USER pack
diff --git a/acceptance/testdata/mock_stack/create-stack.sh b/acceptance/testdata/mock_stack/create-stack.sh
index 878c1017..2a230594 100755
--- a/acceptance/testdata/mock_stack/create-stack.sh
+++ b/acceptance/testdata/mock_stack/create-stack.sh
@@ -2,5 +2,5 @@
 
 dir="$(cd $(dirname $0) && pwd)"
 
-docker build --tag pack-test/build "$dir"
-docker tag pack-test/build pack-test/run
\ No newline at end of file
+docker build --tag pack-test/build "$dir"/build
+docker build --tag pack-test/run "$dir"/run
diff --git a/acceptance/testdata/mock_stack/Dockerfile b/acceptance/testdata/mock_stack/run/Dockerfile
similarity index 78%
rename from acceptance/testdata/mock_stack/Dockerfile
rename to acceptance/testdata/mock_stack/run/Dockerfile
index c868039f..c812c31d 100644
--- a/acceptance/testdata/mock_stack/Dockerfile
+++ b/acceptance/testdata/mock_stack/run/Dockerfile
@@ -9,7 +9,6 @@ RUN \
 
 RUN apt-get update && apt-get -yq install netcat
 LABEL io.buildpacks.stack.id=pack.test.stack
+LABEL io.buildpacks.stack.mixins="[\"mixinA\", \"netcat\", \"mixin3\"]"
 
 USER pack
-
-
diff --git a/acceptance/testdata/pack_current/inspect_builder_output.txt b/acceptance/testdata/pack_current/inspect_builder_output.txt
index 56c8786b..d8039a29 100644
--- a/acceptance/testdata/pack_current/inspect_builder_output.txt
+++ b/acceptance/testdata/pack_current/inspect_builder_output.txt
@@ -6,7 +6,13 @@ Created By:
   Name: Pack CLI
   Version: {{.pack_version}}
 
-Stack: pack.test.stack
+Stack:
+  ID: pack.test.stack
+  Mixins:
+    mixinA
+    netcat
+    mixin3
+    build:mixinTwo
 
 Lifecycle:
   Version: {{.lifecycle_version}}
@@ -35,7 +41,13 @@ Created By:
   Name: Pack CLI
   Version: {{.pack_version}}
 
-Stack: pack.test.stack
+Stack:
+  ID: pack.test.stack
+  Mixins:
+    mixinA
+    netcat
+    mixin3
+    build:mixinTwo
 
 Lifecycle:
   Version: {{.lifecycle_version}}
diff --git a/inspect_builder_test.go b/inspect_builder_test.go
index 5ec4fa88..e5e4e248 100644
--- a/inspect_builder_test.go
+++ b/inspect_builder_test.go
@@ -46,6 +46,10 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) {
 
 		builderImage = fakes.NewImage("some/builder", "", nil)
 		h.AssertNil(t, builderImage.SetLabel("io.buildpacks.stack.id", "test.stack.id"))
+		h.AssertNil(t, builderImage.SetLabel(
+			"io.buildpacks.stack.mixins",
+			`["mixinOne", "build:mixinTwo", "mixinThree", "build:mixinFour"]`,
+		))
 		h.AssertNil(t, builderImage.SetEnv("CNB_USER_ID", "1234"))
 		h.AssertNil(t, builderImage.SetEnv("CNB_GROUP_ID", "4321"))
 	})
@@ -120,6 +124,14 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) {
 						h.AssertEq(t, builderInfo.Stack, "test.stack.id")
 					})
 
+					it("sets the stack mixins", func() {
+						builderInfo, err := subject.InspectBuilder("some/builder", useDaemon)
+						h.AssertNil(t, err)
+						h.AssertEq(t,
+							builderInfo.Mixins, []string{"mixinOne", "mixinThree", "build:mixinTwo", "build:mixinFour"},
+						)
+					})
+
 					it("set the defaults run image mirrors", func() {
 						builderInfo, err := subject.InspectBuilder("some/builder", useDaemon)
 						h.AssertNil(t, err)
@@ -162,6 +174,18 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) {
 						h.AssertNil(t, err)
 						h.AssertEq(t, builderInfo.Lifecycle.Info.Version.String(), "1.2.3")
 					})
+
+					when("the image has no mixins", func() {
+						it.Before(func() {
+							h.AssertNil(t, builderImage.SetLabel("io.buildpacks.stack.mixins", ""))
+						})
+
+						it("sets empty stack mixins", func() {
+							builderInfo, err := subject.InspectBuilder("some/builder", useDaemon)
+							h.AssertNil(t, err)
+							h.AssertEq(t, builderInfo.Mixins, []string{})
+						})
+					})
 				})
 			})
 		}
diff --git a/internal/commands/inspect_builder_test.go b/internal/commands/inspect_builder_test.go
index ecf378bc..ad44b80f 100644
--- a/internal/commands/inspect_builder_test.go
+++ b/internal/commands/inspect_builder_test.go
@@ -107,6 +107,11 @@ func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) {
 				h.AssertNotContains(t, outBuf.String(), "Description:")
 			})
 
+			it("missing stack mixins are skipped", func() {
+				h.AssertNil(t, command.Execute())
+				h.AssertNotContains(t, outBuf.String(), "Mixins")
+			})
+
 			it("missing buildpacks logs a warning", func() {
 				h.AssertNil(t, command.Execute())
 				h.AssertContains(t, outBuf.String(), "Buildpacks:\n  (none)")
@@ -145,6 +150,7 @@ func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) {
 				remoteInfo = &pack.BuilderInfo{
 					Description:     "Some remote description",
 					Stack:           "test.stack.id",
+					Mixins:          []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"},
 					RunImage:        "some/run-image",
 					RunImageMirrors: []string{"first/default", "second/default"},
 					Buildpacks:      buildpacks,
@@ -172,6 +178,7 @@ func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) {
 				localInfo = &pack.BuilderInfo{
 					Description:     "Some local description",
 					Stack:           "test.stack.id",
+					Mixins:          []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"},
 					RunImage:        "some/run-image",
 					RunImageMirrors: []string{"first/local-default", "second/local-default"},
 					Buildpacks:      buildpacks,
@@ -217,7 +224,13 @@ Created By:
   Name: Pack CLI
   Version: 1.2.3
 
-Stack: test.stack.id
+Stack:
+  ID: test.stack.id
+  Mixins:
+    mixin1
+    mixin2
+    build:mixin3
+    build:mixin4
 
 Lifecycle:
   Version: 6.7.8
@@ -251,7 +264,13 @@ Created By:
   Name: Pack CLI
   Version: 4.5.6
 
-Stack: test.stack.id
+Stack:
+  ID: test.stack.id
+  Mixins:
+    mixin1
+    mixin2
+    build:mixin3
+    build:mixin4
 
 Lifecycle:
   Version: 4.5.6
@@ -298,7 +317,13 @@ Created By:
   Name: Pack CLI
   Version: 1.2.3
 
-Stack: test.stack.id
+Stack:
+  ID: test.stack.id
+  Mixins:
+    mixin1
+    mixin2
+    build:mixin3
+    build:mixin4
 
 Lifecycle:
   Version: 6.7.8
@@ -332,7 +357,13 @@ Created By:
   Name: Pack CLI
   Version: 4.5.6
 
-Stack: test.stack.id
+Stack:
+  ID: test.stack.id
+  Mixins:
+    mixin1
+    mixin2
+    build:mixin3
+    build:mixin4
 
 Lifecycle:
   Version: 4.5.6