Skip to content

Commit

Permalink
Validate stack during analyze phase
Browse files Browse the repository at this point in the history
During analyze, Platform 0.7 and above will validate the build and stack image if the data is available to do so.

Addresses: #471

Signed-off-by: Jesse Brown <jabrown85@gmail.com>
  • Loading branch information
jabrown85 committed May 12, 2021
1 parent 9ceda0d commit 53eee57
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 1 deletion.
243 changes: 243 additions & 0 deletions acceptance/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestAnalyzer(t *testing.T) {
analyzeDockerContext,
h.WithFlags(
"-f", filepath.Join(analyzeDockerContext, dockerfileName),
"--build-arg", "registry="+noAuthRegistry.Host+":"+noAuthRegistry.Port,
),
)
defer h.DockerImageRemove(t, analyzeImage)
Expand Down Expand Up @@ -1013,6 +1014,237 @@ func testAnalyzerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
assertAnalyzedMetadata(t, filepath.Join(copyDir, "some-other-layers", "analyzed.toml")) // analyzed.toml is written at the provided -layers directory: /some-other-layers
})
})

when("validating stack", func() {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).Compare(api.MustParse("0.7")) < 0, "Platform API < 0.7 does not validate stack")

// push test run-images into test registry
buildRunRegistryImage(
t,
"company/stack:bionic",
filepath.Join("testdata", "analyzer", "run-image"),
"--build-arg", "stackid=io.buildpacks.stacks.bionic",
)
buildRunRegistryImage(
t,
"company/stack:centos",
filepath.Join("testdata", "analyzer", "run-image"),
"--build-arg", "stackid=io.company.centos",
)

h.DockerBuild(
t,
"localcompany/stack:bionic",
filepath.Join("testdata", "analyzer", "run-image"),
h.WithArgs(
"--build-arg", "stackid=io.buildpacks.stacks.bionic",
),
)
})

when("stack metadata is not present anywhere", func() {
it("skips validation", func() {
execArgs := []string{ctrPath(analyzerPath)}

h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers/analyzed.toml"),
analyzeImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/bionic-stack.toml",
),
h.WithArgs(execArgs...),
)
})
})

when("stack metadata is present", func() {
when("stacks match", func() {
it("passes validation", func() {
execArgs := []string{ctrPath(analyzerPath)}

h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers/analyzed.toml"),
analyzeImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/bionic-stack.toml",
),
h.WithArgs(execArgs...),
)
})
})

when("stacks do not match", func() {
it("fails validation", func() {
cmd := exec.Command(
"docker", "run", "--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/mismatch-stack.toml",
analyzeImage,
ctrPath(analyzerPath),
) // #nosec G204
output, err := cmd.CombinedOutput()

h.AssertNotNil(t, err)
expected := "incompatible stack: 'io.company.centos' is not compatible with 'io.buildpacks.stacks.bionic'"
h.AssertStringContains(t, string(output), expected)
})
})

when("CNB_STACK_ID is present", func() {
it("uses CNB_STACK_ID for validation", func() {
execArgs := []string{ctrPath(analyzerPath)}

h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers/analyzed.toml"),
analyzeImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/mismatch-stack.toml",
"--env", "CNB_STACK_ID=io.company.centos",
),
h.WithArgs(execArgs...),
)
})
})

when("CNB_RUN_IMAGE is present", func() {
it("uses CNB_RUN_IMAGE for validation", func() {
execArgs := []string{ctrPath(analyzerPath)}

h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers/analyzed.toml"),
analyzeImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/mismatch-stack.toml",
"--env", "CNB_RUN_IMAGE="+noAuthRegistry.RepoName("company/stack:bionic"),
),
h.WithArgs(execArgs...),
)
})
})

when("stack metadata does not contain build-image.stack-id", func() {
it("fails validation", func() {
cmd := exec.Command(
"docker", "run", "--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/missing-build-image-stack.toml",
analyzeImage,
ctrPath(analyzerPath),
) // #nosec G204
output, err := cmd.CombinedOutput()

h.AssertNotNil(t, err)
expected := "CNB_STACK_ID is required when there is no stack metadata available"
h.AssertStringContains(t, string(output), expected)
})
})

when("stack metadata does not contain run-image.image", func() {
it("fails validation", func() {
cmd := exec.Command(
"docker", "run", "--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/missing-run-image.toml",
analyzeImage,
ctrPath(analyzerPath),
) // #nosec G204
output, err := cmd.CombinedOutput()

h.AssertNotNil(t, err)
expected := "-run-image is required when there is no stack metadata available"
h.AssertStringContains(t, string(output), expected)
})
})

when("run image inaccessible", func() {
it("fails validation", func() {
cmd := exec.Command(
"docker", "run", "--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/bionic-stack.toml",
"--env", "CNB_RUN_IMAGE=fake.example.com/company/example:20",
analyzeImage,
ctrPath(analyzerPath),
) // #nosec G204
output, err := cmd.CombinedOutput()

h.AssertNotNil(t, err)
expected := "access run image"
h.AssertStringContains(t, string(output), expected)
})
})

when("run image does not have io.buildpacks.stack.id", func() {
it("fails validation", func() {
cmd := exec.Command(
"docker", "run", "--rm",
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/bionic-stack.toml",
"--env", "CNB_RUN_IMAGE=company/stack:missing-labels",
analyzeImage,
ctrPath(analyzerPath),
) // #nosec G204
output, err := cmd.CombinedOutput()

h.AssertNotNil(t, err)
expected := "empty run image io.buildpacks.stack.id"
h.AssertStringContains(t, string(output), expected)
})
})

when("run image has mirrors", func() {
it("uses expected mirror for run-image", func() {
execArgs := []string{ctrPath(analyzerPath), "--previous-image=" + noAuthRegistry.RepoName("apprepo/myapp")}

h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers/analyzed.toml"),
analyzeImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/run-mirror-stack.toml",
),
h.WithArgs(execArgs...),
)
})
})

when("daemon case", func() {
when("stacks match", func() {
it("passes validation", func() {
execArgs := []string{ctrPath(analyzerPath), "-daemon"}

h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers/analyzed.toml"),
analyzeImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "CNB_STACK_PATH=/cnb/local-bionic-stack.toml",
)...),
h.WithArgs(execArgs...),
)
})
})
})
})
})
}
}

Expand All @@ -1028,6 +1260,17 @@ func minifyMetadata(t *testing.T, path string, metadataStruct interface{}) strin
return string(flatMetadata)
}

func buildRunRegistryImage(t *testing.T, repoName, context string, buildArgs ...string) string {
// Build image
regRepoName := registry.RepoName(repoName)
h.DockerBuild(t, regRepoName, context, h.WithArgs(buildArgs...))

// Push image
h.AssertNil(t, h.PushImage(h.DockerCli(t), regRepoName, registry.EncodedLabeledAuth()))

return regRepoName
}

func buildAuthRegistryImage(t *testing.T, repoName, context string, buildArgs ...string) (string, string) {
// Build image
regRepoName := registry.RepoName(repoName)
Expand Down
8 changes: 8 additions & 0 deletions acceptance/testdata/analyzer/analyze-image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ RUN chown -R $CNB_USER_ID:$CNB_GROUP_ID /layers

# ensure docker config directory is root owned and NOT world readable
RUN chown -R root /docker-config; chmod -R 700 /docker-config

ARG registry

# replace company/stack -> 192.../company/stack
RUN sed -i "s/company\/stack/${registry}\/company\/stack/g" /cnb/bionic-stack.toml
RUN sed -i "s/company\/stack/${registry}\/company\/stack/g" /cnb/mismatch-stack.toml
RUN sed -i "s/company\/stack/${registry}\/company\/stack/g" /cnb/missing-build-image-stack.toml
RUN sed -i "s/company\/stack/${registry}\/company\/stack/g" /cnb/run-mirror-stack.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[run-images
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run-image]
image = "company/stack:bionic"
mirrors = []
[build-image]
stack-id = "io.buildpacks.stacks.bionic"
mixins = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run-image]
image = "localcompany/stack:bionic"
mirrors = []
[build-image]
stack-id = "io.buildpacks.stacks.bionic"
mixins = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run-image]
image = "company/stack:centos"
mirrors = []
[build-image]
stack-id = "io.buildpacks.stacks.bionic"
mixins = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[run-image]
image = "company/stack:bionic"
mirrors = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-image]
stack-id = "io.buildpacks.stacks.bionic"
mixins = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run-image]
image = "gcr.io/paketobuildpacks/invalidimg:20"
mirrors = ["company/stack:bionic"]
[build-image]
stack-id = "io.buildpacks.stacks.bionic"
mixins = []
4 changes: 4 additions & 0 deletions acceptance/testdata/analyzer/run-image/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM scratch

ARG stackid
LABEL io.buildpacks.stack.id=${stackid}
Loading

0 comments on commit 53eee57

Please sign in to comment.