Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dockerfiles phase 2 #896

Merged
merged 45 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6a86d08
Consolidate logic in the platform package
natalieparellano Jun 29, 2022
55548f4
Reinstate platform/launch package to keep the launcher binary smaller
natalieparellano Jun 29, 2022
76374c6
Fix constant
natalieparellano Jun 29, 2022
c91873d
Remove comment
natalieparellano Jun 29, 2022
6834805
WIP
natalieparellano Aug 9, 2022
e7e9b35
WIP
natalieparellano Aug 23, 2022
8aa4053
Don't mount layers fixture in container
natalieparellano Aug 23, 2022
2bb55f7
Set environment variables from the extended build image in the build …
natalieparellano Aug 23, 2022
f3f4bf3
Merge branch 'main' into extensions-phase-2
natalieparellano Aug 23, 2022
d6c713f
Fix format string
natalieparellano Aug 23, 2022
4ef290b
Restorer pulls builder manifest and config
natalieparellano Aug 23, 2022
aa47b3d
Copy extend-config.toml from extension output to /layers/generated
natalieparellano Aug 23, 2022
f9fb2a5
Merge branch 'main' into extensions-phase-2
natalieparellano Aug 23, 2022
22ee12a
Only import kaniko on linux
natalieparellano Aug 23, 2022
5023441
WIP: units pass
natalieparellano Aug 30, 2022
16f1b54
WIP: fixed some TODOs
natalieparellano Aug 30, 2022
7b4acfe
WIP: addressed some more TODOs, units pass
natalieparellano Aug 31, 2022
73e0e23
WIP: units pass
natalieparellano Aug 31, 2022
6dea34f
WIP: acceptance tests pass
jromero Aug 31, 2022
bcfca88
Merge branch 'main' into extensions-phase-2
natalieparellano Sep 1, 2022
13d5653
Address some minor TODOs
natalieparellano Sep 1, 2022
4667bed
When running extender acceptance, don't mount in /workspace directory
natalieparellano Sep 1, 2022
63bbaec
Don't try to check for specific curl version
natalieparellano Sep 1, 2022
55b7092
fixes from testing. (#902)
BarDweller Sep 14, 2022
61e894d
Lint
natalieparellano Sep 14, 2022
f2248ba
Add tests and TODO
natalieparellano Sep 14, 2022
23b9e4a
Change CNB_BUILDPACK_DIR -> CNB_EXTENSION_DIR
natalieparellano Sep 22, 2022
66e8cd1
Fill in default generated dir
natalieparellano Sep 22, 2022
73a860d
Ensure kaniko doesn't try to pull 'oci:/kaniko/cache/base/sha256:XXX'…
natalieparellano Sep 22, 2022
3ef3f38
Add test
natalieparellano Sep 22, 2022
be5f610
Merge branch 'main' into extensions-phase-2
natalieparellano Sep 22, 2022
b986d01
Fix panic
natalieparellano Sep 22, 2022
79284bd
Fix assertion
natalieparellano Sep 22, 2022
150cca1
Pass build_id as UUID to Dockerfile
natalieparellano Sep 27, 2022
9644a7f
Add tests for selective package
natalieparellano Sep 27, 2022
70ab2c8
Remove kaniko fork
natalieparellano Sep 28, 2022
5702077
Fix windows
natalieparellano Sep 28, 2022
1159464
Merge branch 'main' into extensions-phase-2
natalieparellano Sep 29, 2022
84e5355
Dockerfile validation (#918)
BarDweller Oct 4, 2022
a862921
Add units for Dockerfile validation
natalieparellano Oct 4, 2022
b2bfb16
Merge branch 'main' into extensions-phase-2
natalieparellano Oct 4, 2022
ecf8a98
Fix launcher
natalieparellano Oct 4, 2022
7e6ecad
Merge branch 'main' into extensions-phase-2
natalieparellano Oct 6, 2022
732bce0
Minor improvements
natalieparellano Oct 6, 2022
42a92fa
Skip image extensions tests on Windows
natalieparellano Oct 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ build-linux-amd64-symlinks:
ln -sf lifecycle $(OUT_DIR)/exporter
ln -sf lifecycle $(OUT_DIR)/rebaser
ln -sf lifecycle $(OUT_DIR)/creator
ln -sf lifecycle $(OUT_DIR)/extender

build-linux-arm64-symlinks: export GOOS:=linux
build-linux-arm64-symlinks: export GOARCH:=arm64
Expand All @@ -131,6 +132,7 @@ build-linux-arm64-symlinks:
ln -sf lifecycle $(OUT_DIR)/exporter
ln -sf lifecycle $(OUT_DIR)/rebaser
ln -sf lifecycle $(OUT_DIR)/creator
ln -sf lifecycle $(OUT_DIR)/extender

build-windows-amd64-lifecycle: $(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe

Expand Down
6 changes: 2 additions & 4 deletions acceptance/detector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
h.WithArgs(
"-analyzed=/layers/analyzed.toml",
"-extensions=/cnb/extensions",
"-log-level=debug",
"-generated=/layers/generated",
"-log-level=debug",
),
)

Expand All @@ -374,9 +374,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
var plan platform.BuildPlan
_, err = toml.DecodeFile(foundPlanTOML, &plan)
h.AssertNil(t, err)
h.AssertEq(t, plan.Entries[0].Requires[0].Name, "some_requirement")
h.AssertEq(t, plan.Entries[0].Providers[0].ID, "simple_extension")
h.AssertEq(t, plan.Entries[0].Providers[0].Extension, true)
h.AssertEq(t, len(plan.Entries), 0) // this shows that the plan was filtered to remove `requires` provided by extensions

t.Log("runs /bin/generate for extensions")
h.AssertStringContains(t, output, "simple_extension: output from /bin/generate")
Expand Down
146 changes: 146 additions & 0 deletions acceptance/extender_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//go:build acceptance
// +build acceptance

package acceptance

import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"

"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/auth"
"github.com/buildpacks/lifecycle/internal/selective"
h "github.com/buildpacks/lifecycle/testhelpers"
)

var (
extendImage string
extendRegAuthConfig string
extendRegNetwork string
extenderPath string
extendDaemonFixtures *daemonImageFixtures
extendRegFixtures *regImageFixtures
extendTest *PhaseTest
)

func TestExtender(t *testing.T) {
h.SkipIf(t, runtime.GOOS == "windows", "Extender is not supported on Windows")

rand.Seed(time.Now().UTC().UnixNano())

testImageDockerContext := filepath.Join("testdata", "extender")
extendTest = NewPhaseTest(t, "extender", testImageDockerContext)
extendTest.Start(t)
defer extendTest.Stop(t)

extendImage = extendTest.testImageRef
extenderPath = extendTest.containerBinaryPath
extendRegAuthConfig = extendTest.targetRegistry.authConfig
extendRegNetwork = extendTest.targetRegistry.network
extendDaemonFixtures = extendTest.targetDaemon.fixtures
extendRegFixtures = extendTest.targetRegistry.fixtures

for _, platformAPI := range api.Platform.Supported {
spec.Run(t, "acceptance-extender/"+platformAPI.String(), testExtenderFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{}))
}
}

func testExtenderFunc(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
return func(t *testing.T, when spec.G, it spec.S) {
it.Before(func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.10"), "")
})

when("kaniko case", func() {
var kanikoDir, buildImageDigest string

it.Before(func() {
var err error
kanikoDir, err = ioutil.TempDir("", "lifecycle-acceptance")
h.AssertNil(t, err)

// push "builder" image to test registry
h.Run(t, exec.Command("docker", "tag", extendImage, extendTest.RegRepoName(extendImage)))
h.AssertNil(t, h.PushImage(h.DockerCli(t), extendTest.RegRepoName(extendImage), extendTest.targetRegistry.registry.EncodedLabeledAuth()))

// warm kaniko cache - this mimics what the analyzer or restorer would have done
os.Setenv("DOCKER_CONFIG", extendTest.targetRegistry.dockerConfigDir)
ref, auth, err := auth.ReferenceForRepoName(authn.DefaultKeychain, extendTest.RegRepoName(extendImage))
h.AssertNil(t, err)
remoteImage, err := remote.Image(ref, remote.WithAuth(auth))
h.AssertNil(t, err)
buildImageHash, err := remoteImage.Digest()
h.AssertNil(t, err)
buildImageDigest = buildImageHash.String()
baseCacheDir := filepath.Join(kanikoDir, "cache", "base")
h.AssertNil(t, os.MkdirAll(baseCacheDir, 0755))
layoutPath, err := selective.Write(filepath.Join(baseCacheDir, buildImageDigest), empty.Index)
h.AssertNil(t, err)
h.AssertNil(t, layoutPath.AppendImage(remoteImage))
})

it.After(func() {
_ = os.RemoveAll(kanikoDir)
})

when("extending the build image", func() {
it("succeeds", func() {
extendArgs := []string{
ctrPath(extenderPath),
"-generated", "/layers/generated",
"-log-level", "debug",
"-gid", "1000",
"-uid", "1234",
"oci:/kaniko/cache/base/" + buildImageDigest,
}

t.Log("first build extends the build image by running Dockerfile commands")
firstOutput := h.DockerRunWithCombinedOutput(t,
extendImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--volume", fmt.Sprintf("%s:/kaniko", kanikoDir),
),
h.WithArgs(extendArgs...),
)
h.AssertStringDoesNotContain(t, firstOutput, "Did not find cache key, pulling remote image...")
h.AssertStringContains(t, firstOutput, "ca-certificates")
h.AssertStringContains(t, firstOutput, "Hello Extensions buildpack\ncurl") // output by buildpack, shows that curl was installed on the build image
t.Log("sets environment variables from the extended build image in the build context")
h.AssertStringContains(t, firstOutput, "CNB_STACK_ID for buildpack: stack-id-from-ext-tree")

t.Log("cleans the kaniko directory")
fis, err := ioutil.ReadDir(kanikoDir)
h.AssertNil(t, err)
h.AssertEq(t, len(fis), 1) // 1: /kaniko/cache

t.Log("second build extends the build image by pulling from the cache directory")
secondOutput := h.DockerRunWithCombinedOutput(t,
extendImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--volume", fmt.Sprintf("%s:/kaniko", kanikoDir),
),
h.WithArgs(extendArgs...),
)
h.AssertStringDoesNotContain(t, secondOutput, "Did not find cache key, pulling remote image...")
h.AssertStringDoesNotContain(t, secondOutput, "ca-certificates")
h.AssertStringContains(t, secondOutput, "Hello Extensions buildpack\ncurl") // output by buildpack, shows that curl is still installed in the unpacked cached layer
})
})
})
}
}
110 changes: 64 additions & 46 deletions acceptance/restorer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ import (
)

var (
restoreDockerContext = filepath.Join("testdata", "restorer")
restorerBinaryDir = filepath.Join("testdata", "restorer", "container", "cnb", "lifecycle")
restorerImage = "lifecycle/acceptance/restorer"
restoreImage string
restoreRegAuthConfig string
restoreRegNetwork string
restorerPath string
restoreDaemonFixtures *daemonImageFixtures
restoreRegFixtures *regImageFixtures
restoreTest *PhaseTest
)

func TestRestorer(t *testing.T) {
Expand All @@ -32,9 +36,17 @@ func TestRestorer(t *testing.T) {

rand.Seed(time.Now().UTC().UnixNano())

h.MakeAndCopyLifecycle(t, "linux", "amd64", restorerBinaryDir)
h.DockerBuild(t, restorerImage, restoreDockerContext)
defer h.DockerImageRemove(t, restorerImage)
testImageDockerContext := filepath.Join("testdata", "restorer")
restoreTest = NewPhaseTest(t, "restorer", testImageDockerContext)
restoreTest.Start(t)
defer restoreTest.Stop(t)

restoreImage = restoreTest.testImageRef
restorerPath = restoreTest.containerBinaryPath
restoreRegAuthConfig = restoreTest.targetRegistry.authConfig
restoreRegNetwork = restoreTest.targetRegistry.network
restoreDaemonFixtures = restoreTest.targetDaemon.fixtures
restoreRegFixtures = restoreTest.targetRegistry.fixtures

for _, platformAPI := range api.Platform.Supported {
spec.Run(t, "acceptance-restorer/"+platformAPI.String(), testRestorerFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{}))
Expand All @@ -43,9 +55,24 @@ func TestRestorer(t *testing.T) {

func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
return func(t *testing.T, when spec.G, it spec.S) {
var copyDir, containerName string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = ioutil.TempDir("", "test-docker-copy-")
h.AssertNil(t, err)
})

it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
_ = os.RemoveAll(copyDir)
})

when("called with arguments", func() {
it("errors", func() {
command := exec.Command("docker", "run", "--rm", restorerImage, "some-arg")
command := exec.Command("docker", "run", "--rm", restoreImage, "some-arg")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "failed to parse arguments: received unexpected Args"
Expand All @@ -56,7 +83,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
when("called with -analyzed", 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", restorerImage, "-analyzed some-file-location")
command := exec.Command("docker", "run", "--rm", restoreImage, "-analyzed some-file-location")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "flag provided but not defined: -analyzed"
Expand All @@ -67,7 +94,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
when("called with -skip-layers", 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", restorerImage, "-skip-layers true")
command := exec.Command("docker", "run", "--rm", restoreImage, "-skip-layers true")
output, err := command.CombinedOutput()
h.AssertNotNil(t, err)
expected := "flag provided but not defined: -skip-layers"
Expand All @@ -77,7 +104,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe

when("called without any cache flag", func() {
it("outputs it will not restore cache layer data", func() {
command := exec.Command("docker", "run", "--rm", "--env", "CNB_PLATFORM_API="+platformAPI, restorerImage)
command := exec.Command("docker", "run", "--rm", "--env", "CNB_PLATFORM_API="+platformAPI, restoreImage)
output, err := command.CombinedOutput()
h.AssertNil(t, err)
expected := "Not restoring cached layer data, no cache flag specified"
Expand All @@ -86,28 +113,13 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
})

when("analyzed.toml exists with app metadata", func() {
var copyDir, containerName string
it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = ioutil.TempDir("", "test-docker-copy-")
h.AssertNil(t, err)
})

it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})

it("restores app metadata", func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.7"), "Platform API < 0.7 does not restore app metadata")
output := h.DockerRunAndCopy(t,
containerName,
copyDir,
ctrPath("/layers"),
restorerImage,
restoreImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_PLATFORM_API="+platformAPI,
Expand All @@ -117,33 +129,16 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe

h.AssertStringContains(t, output, "Restoring metadata for \"some-buildpack-id:launch-layer\"")
})

})

when("using cache-dir", func() {
when("there is cache present from a previous build", func() {
var copyDir, containerName string

it.Before(func() {
containerName = "test-container-" + h.RandString(10)
var err error
copyDir, err = ioutil.TempDir("", "test-docker-copy-")
h.AssertNil(t, err)
})

it.After(func() {
if h.DockerContainerExists(t, containerName) {
h.Run(t, exec.Command("docker", "rm", containerName))
}
os.RemoveAll(copyDir)
})

it("restores cached layer data", func() {
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/layers",
restorerImage,
restoreImage,
h.WithFlags("--env", "CNB_PLATFORM_API="+platformAPI),
h.WithArgs("-cache-dir", "/cache"),
)
Expand All @@ -163,7 +158,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
containerName,
copyDir,
"/layers",
restorerImage,
restoreImage,
h.WithFlags("--env", "CNB_PLATFORM_API="+platformAPI),
h.WithArgs("-cache-dir", "/cache"),
)
Expand All @@ -181,7 +176,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
containerName,
copyDir,
"/layers",
restorerImage,
restoreImage,
h.WithFlags("--env", "CNB_PLATFORM_API="+platformAPI),
h.WithArgs("-cache-dir", "/cache"),
)
Expand All @@ -192,5 +187,28 @@ 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")
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/kaniko",
restoreImage,
h.WithFlags(
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "DOCKER_CONFIG=/docker-config",
"--network", restoreRegNetwork,
),
h.WithArgs("-build-image", restoreRegFixtures.SomeCacheImage), // some-cache-image simulates a builder image in a registry
)
t.Log("writes builder manifest and config to the kaniko cache")
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"))
})
})
}
}
3 changes: 3 additions & 0 deletions acceptance/testdata/extender/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM ubuntu:bionic

COPY ./container/ /
Loading