From f5d1d6d5c11627b5d1a23d432f76e69d633079e2 Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Thu, 15 Aug 2024 14:52:17 +0000 Subject: [PATCH] Add builder prune --all and --force flag support This change adds support for builder prune --all and --force. A refactor of previous tests which used builder prune functionality was required to handle the new confirmation prompt. A test utility was added to handle this for reuse by current and future tests. Signed-off-by: Austin Vazquez --- cmd/nerdctl/builder.go | 82 +++++++++++++++---- cmd/nerdctl/builder_build_test.go | 38 ++++----- cmd/nerdctl/builder_linux_test.go | 36 +++++++- cmd/nerdctl/compose_build_linux_test.go | 2 +- cmd/nerdctl/compose_create_linux_test.go | 2 +- cmd/nerdctl/compose_run_linux_test.go | 2 +- cmd/nerdctl/compose_up_linux_test.go | 2 +- cmd/nerdctl/container_run_mount_linux_test.go | 10 +-- cmd/nerdctl/container_run_test.go | 2 +- .../container_run_verify_linux_test.go | 2 +- cmd/nerdctl/image_encrypt_linux_test.go | 2 +- cmd/nerdctl/image_prune_test.go | 4 +- cmd/nerdctl/image_pull_linux_test.go | 6 +- cmd/nerdctl/ipfs_build_linux_test.go | 2 +- cmd/nerdctl/ipfs_compose_linux_test.go | 2 +- cmd/nerdctl/multi_platform_linux_test.go | 6 +- docs/command-reference.md | 6 +- pkg/api/types/builder_types.go | 2 + pkg/testutil/testutil.go | 8 ++ 19 files changed, 158 insertions(+), 58 deletions(-) diff --git a/cmd/nerdctl/builder.go b/cmd/nerdctl/builder.go index cec6ddb8597..a5f82727ecf 100644 --- a/cmd/nerdctl/builder.go +++ b/cmd/nerdctl/builder.go @@ -22,11 +22,11 @@ import ( "os/exec" "strings" + "github.com/docker/go-units" "github.com/spf13/cobra" - "github.com/containerd/log" - - "github.com/containerd/nerdctl/v2/pkg/buildkitutil" + "github.com/containerd/nerdctl/v2/pkg/api/types" + "github.com/containerd/nerdctl/v2/pkg/cmd/builder" ) func newBuilderCommand() *cobra.Command { @@ -58,29 +58,83 @@ func newBuilderPruneCommand() *cobra.Command { } AddStringFlag(buildPruneCommand, "buildkit-host", nil, "", "BUILDKIT_HOST", "BuildKit address") + + buildPruneCommand.Flags().BoolP("all", "a", false, "Remove all unused build cache, not just dangling ones") + buildPruneCommand.Flags().BoolP("force", "f", false, "Do not prompt for confirmation") return buildPruneCommand } func builderPruneAction(cmd *cobra.Command, _ []string) error { - globalOptions, err := processRootCmdFlags(cmd) + options, err := processBuilderPruneOptions(cmd) if err != nil { return err } - buildkitHost, err := getBuildkitHost(cmd, globalOptions.Namespace) + + if !options.Force { + var ( + confirm string + msg string + ) + + if options.All { + msg = "This will remove all build cache." + } else { + msg = "This will remove any dangling build cache." + } + msg += " Are you sure you want to continue? [y/N] " + + fmt.Fprintf(cmd.OutOrStdout(), "WARNING! %s", msg) + fmt.Fscanf(cmd.InOrStdin(), "%s", &confirm) + + if strings.ToLower(confirm) != "y" { + return nil + } + } + + prunedObjects, err := builder.Prune(cmd.Context(), options) if err != nil { return err } - buildctlBinary, err := buildkitutil.BuildctlBinary() + + var totalReclaimedSpace int64 + + for _, prunedObject := range prunedObjects { + totalReclaimedSpace += prunedObject.Size + } + + fmt.Fprintf(cmd.OutOrStdout(), "Total: %s\n", units.BytesSize(float64(totalReclaimedSpace))) + + return nil +} + +func processBuilderPruneOptions(cmd *cobra.Command) (types.BuilderPruneOptions, error) { + globalOptions, err := processRootCmdFlags(cmd) + if err != nil { + return types.BuilderPruneOptions{}, err + } + + buildkitHost, err := getBuildkitHost(cmd, globalOptions.Namespace) + if err != nil { + return types.BuilderPruneOptions{}, err + } + + all, err := cmd.Flags().GetBool("all") if err != nil { - return err + return types.BuilderPruneOptions{}, err } - buildctlArgs := buildkitutil.BuildctlBaseArgs(buildkitHost) - buildctlArgs = append(buildctlArgs, "prune") - log.L.Debugf("running %s %v", buildctlBinary, buildctlArgs) - buildctlCmd := exec.Command(buildctlBinary, buildctlArgs...) - buildctlCmd.Env = os.Environ() - buildctlCmd.Stdout = cmd.OutOrStdout() - return buildctlCmd.Run() + + force, err := cmd.Flags().GetBool("force") + if err != nil { + return types.BuilderPruneOptions{}, err + } + + return types.BuilderPruneOptions{ + Stderr: cmd.OutOrStderr(), + GOptions: globalOptions, + BuildKitHost: buildkitHost, + All: all, + Force: force, + }, nil } func newBuilderDebugCommand() *cobra.Command { diff --git a/cmd/nerdctl/builder_build_test.go b/cmd/nerdctl/builder_build_test.go index 9da239c786b..f187fe6441b 100644 --- a/cmd/nerdctl/builder_build_test.go +++ b/cmd/nerdctl/builder_build_test.go @@ -32,8 +32,8 @@ import ( func TestBuild(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -57,8 +57,8 @@ CMD ["echo", "nerdctl-build-test-string"] func TestBuildIsShareableForCompatiblePlatform(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -87,8 +87,8 @@ CMD ["echo", "nerdctl-build-test-string"] // This isn't currently supported by nerdctl with BuildKit OCI worker. func TestBuildBaseImage(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() imageName2 := imageName + "-2" @@ -122,8 +122,8 @@ CMD ["cat", "/hello2"] func TestBuildFromContainerd(t *testing.T) { testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() imageName2 := imageName + "-2" @@ -152,8 +152,8 @@ CMD ["cat", "/hello2"] func TestBuildFromStdin(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -166,8 +166,8 @@ CMD ["echo", "nerdctl-build-test-stdin"] func TestBuildWithDockerfile(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -198,8 +198,8 @@ CMD ["echo", "nerdctl-build-test-dockerfile"] func TestBuildLocal(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() const testFileName = "nerdctl-build-test" const testContent = "nerdctl" outputDir := t.TempDir() @@ -243,8 +243,8 @@ func createBuildContext(t *testing.T, dockerfile string) string { func TestBuildWithBuildArg(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -288,8 +288,8 @@ CMD echo $TEST_STRING func TestBuildWithIIDFile(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -312,8 +312,8 @@ CMD ["echo", "nerdctl-build-test-string"] func TestBuildWithLabels(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) dockerfile := fmt.Sprintf(`FROM %s @@ -330,8 +330,8 @@ LABEL name=nerdctl-build-test-label func TestBuildMultipleTags(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() img := testutil.Identifier(t) imgWithNoTag, imgWithCustomTag := fmt.Sprintf("%s%d", img, 2), fmt.Sprintf("%s%d:hello", img, 3) defer base.Cmd("rmi", img).AssertOK() @@ -354,10 +354,10 @@ func TestBuildMultipleTags(t *testing.T) { } func TestBuildWithContainerfile(t *testing.T) { - testutil.RequiresBuild(t) testutil.DockerIncompatible(t) + testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -375,8 +375,8 @@ CMD ["echo", "nerdctl-build-test-string"] func TestBuildWithDockerFileAndContainerfile(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -404,8 +404,8 @@ CMD ["echo", "dockerfile"] func TestBuildNoTag(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() base.Cmd("image", "prune", "--force", "--all").AssertOK() dockerfile := fmt.Sprintf(`FROM %s @@ -420,8 +420,8 @@ CMD ["echo", "nerdctl-build-notag-string"] func TestBuildContextDockerImageAlias(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() base.Cmd("image", "prune", "--force", "--all").AssertOK() dockerfile := `FROM myorg/myapp @@ -435,8 +435,8 @@ CMD ["echo", "nerdctl-build-myorg/myapp"]` func TestBuildContextWithCopyFromDir(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() base.Cmd("image", "prune", "--force", "--all").AssertOK() content := "hello_from_dir_2" @@ -487,8 +487,8 @@ CMD ["cat", "/source-date-epoch"] func TestBuildNetwork(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() dockerfile := fmt.Sprintf(`FROM %s RUN apk add --no-cache curl @@ -540,6 +540,7 @@ func buildWithNamedBuilder(base *testutil.Base, builderName string, args ...stri func TestBuildAttestation(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) builderName := testutil.Identifier(t) if testutil.GetTarget() == testutil.Docker { @@ -547,7 +548,6 @@ func TestBuildAttestation(t *testing.T) { defer base.Cmd("buildx", "rm", builderName).AssertOK() base.Cmd("buildx", "create", "--name", builderName, "--bootstrap", "--use").AssertOK() } - defer base.Cmd("builder", "prune").Run() dockerfile := "FROM " + testutil.NginxAlpineImage buildCtx := createBuildContext(t, dockerfile) diff --git a/cmd/nerdctl/builder_linux_test.go b/cmd/nerdctl/builder_linux_test.go index bca3a4649ba..cc70bd640de 100644 --- a/cmd/nerdctl/builder_linux_test.go +++ b/cmd/nerdctl/builder_linux_test.go @@ -30,6 +30,39 @@ import ( "github.com/containerd/nerdctl/v2/pkg/testutil" ) +func TestBuilderPrune(t *testing.T) { + testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) + + base := testutil.NewBase(t) + + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-test-builder-prune"]`, testutil.CommonImage) + + buildCtx := createBuildContext(t, dockerfile) + + testCases := []struct { + name string + commandArgs []string + }{ + { + name: "TestBuilderPruneForce", + commandArgs: []string{"builder", "prune", "--force"}, + }, + { + name: "TestBuilderPruneForceAll", + commandArgs: []string{"builder", "prune", "--force", "--all"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + base.Cmd("build", buildCtx).AssertOK() + base.Cmd(tc.commandArgs...).AssertOK() + }) + } +} + func TestBuilderDebug(t *testing.T) { testutil.DockerIncompatible(t) base := testutil.NewBase(t) @@ -49,6 +82,7 @@ func TestBuildWithPull(t *testing.T) { t.Skipf("skipped because the test needs a custom buildkitd config") } testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) oldImage := testutil.BusyboxImage oldImageSha := "141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47" @@ -86,8 +120,8 @@ namespace = "%s"`, testutil.Namespace) for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() base.Cmd("image", "prune", "--force", "--all").AssertOK() base.Cmd("pull", oldImage).Run() diff --git a/cmd/nerdctl/compose_build_linux_test.go b/cmd/nerdctl/compose_build_linux_test.go index 9e5cfbecc53..69046355e6a 100644 --- a/cmd/nerdctl/compose_build_linux_test.go +++ b/cmd/nerdctl/compose_build_linux_test.go @@ -46,8 +46,8 @@ services: dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() comp := testutil.NewComposeDir(t, dockerComposeYAML) defer comp.CleanUp() diff --git a/cmd/nerdctl/compose_create_linux_test.go b/cmd/nerdctl/compose_create_linux_test.go index 829e3ebded5..9ad9ea59429 100644 --- a/cmd/nerdctl/compose_create_linux_test.go +++ b/cmd/nerdctl/compose_create_linux_test.go @@ -116,8 +116,8 @@ services: dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() comp := testutil.NewComposeDir(t, dockerComposeYAML) defer comp.CleanUp() diff --git a/cmd/nerdctl/compose_run_linux_test.go b/cmd/nerdctl/compose_run_linux_test.go index bdbe95a9760..cd8d353bc9a 100644 --- a/cmd/nerdctl/compose_run_linux_test.go +++ b/cmd/nerdctl/compose_run_linux_test.go @@ -426,6 +426,7 @@ func TestComposePushAndPullWithCosignVerify(t *testing.T) { testutil.RequireExecutable(t, "cosign") testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) t.Parallel() base := testutil.NewBase(t) @@ -435,7 +436,6 @@ func TestComposePushAndPullWithCosignVerify(t *testing.T) { reg := testregistry.NewWithNoAuth(base, 0, false) t.Cleanup(func() { keyPair.cleanup() - base.Cmd("builder", "prune").Run() reg.Cleanup(nil) }) diff --git a/cmd/nerdctl/compose_up_linux_test.go b/cmd/nerdctl/compose_up_linux_test.go index f6a0f0a35ba..cbcdbb7e43b 100644 --- a/cmd/nerdctl/compose_up_linux_test.go +++ b/cmd/nerdctl/compose_up_linux_test.go @@ -125,8 +125,8 @@ func testComposeUp(t *testing.T, base *testutil.Base, dockerComposeYAML string, func TestComposeUpBuild(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() const dockerComposeYAML = ` services: diff --git a/cmd/nerdctl/container_run_mount_linux_test.go b/cmd/nerdctl/container_run_mount_linux_test.go index d91d37971af..5a6a64a2f20 100644 --- a/cmd/nerdctl/container_run_mount_linux_test.go +++ b/cmd/nerdctl/container_run_mount_linux_test.go @@ -115,8 +115,8 @@ func TestRunAnonymousVolumeWithTypeMountFlag(t *testing.T) { func TestRunAnonymousVolumeWithBuild(t *testing.T) { t.Parallel() testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -134,8 +134,8 @@ VOLUME /foo func TestRunCopyingUpInitialContentsOnVolume(t *testing.T) { t.Parallel() testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() volName := testutil.Identifier(t) + "-vol" @@ -161,8 +161,8 @@ CMD ["cat", "/mnt/initial_file"] func TestRunCopyingUpInitialContentsOnDockerfileVolume(t *testing.T) { t.Parallel() testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() volName := testutil.Identifier(t) + "-vol" @@ -195,8 +195,8 @@ CMD ["cat", "/mnt/initial_file"] func TestRunCopyingUpInitialContentsOnVolumeShouldRetainSymlink(t *testing.T) { t.Parallel() testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() @@ -218,8 +218,8 @@ CMD ["readlink", "/mnt/passwd"] func TestRunCopyingUpInitialContentsShouldNotResetTheCopiedContents(t *testing.T) { t.Parallel() testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() tID := testutil.Identifier(t) imageName := tID + "-img" volumeName := tID + "-vol" diff --git a/cmd/nerdctl/container_run_test.go b/cmd/nerdctl/container_run_test.go index 940eb6e5743..53103317451 100644 --- a/cmd/nerdctl/container_run_test.go +++ b/cmd/nerdctl/container_run_test.go @@ -40,8 +40,8 @@ import ( func TestRunEntrypointWithBuild(t *testing.T) { t.Parallel() testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).Run() diff --git a/cmd/nerdctl/container_run_verify_linux_test.go b/cmd/nerdctl/container_run_verify_linux_test.go index 9ef955dc986..3522f12cfa6 100644 --- a/cmd/nerdctl/container_run_verify_linux_test.go +++ b/cmd/nerdctl/container_run_verify_linux_test.go @@ -28,6 +28,7 @@ func TestRunVerifyCosign(t *testing.T) { testutil.RequireExecutable(t, "cosign") testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) t.Parallel() base := testutil.NewBase(t) @@ -37,7 +38,6 @@ func TestRunVerifyCosign(t *testing.T) { reg := testregistry.NewWithNoAuth(base, 0, false) t.Cleanup(func() { keyPair.cleanup() - base.Cmd("builder", "prune").Run() reg.Cleanup(nil) }) diff --git a/cmd/nerdctl/image_encrypt_linux_test.go b/cmd/nerdctl/image_encrypt_linux_test.go index e8998a83060..e6809d29f5b 100644 --- a/cmd/nerdctl/image_encrypt_linux_test.go +++ b/cmd/nerdctl/image_encrypt_linux_test.go @@ -78,7 +78,7 @@ func rmiAll(base *testutil.Base) { base.T.Logf("Pruning build caches") if _, err := buildkitutil.GetBuildkitHost(testutil.Namespace); err == nil { - base.Cmd("builder", "prune").AssertOK() + base.Cmd("builder", "prune", "--force").AssertOK() } // For BuildKit >= 0.11, pruning cache isn't enough to remove manifest blobs that are referred by build history blobs diff --git a/cmd/nerdctl/image_prune_test.go b/cmd/nerdctl/image_prune_test.go index 560c0df8e37..e2babfbde24 100644 --- a/cmd/nerdctl/image_prune_test.go +++ b/cmd/nerdctl/image_prune_test.go @@ -25,9 +25,9 @@ import ( func TestImagePrune(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() imageName := testutil.Identifier(t) defer base.Cmd("rmi", imageName).AssertOK() @@ -47,9 +47,9 @@ func TestImagePrune(t *testing.T) { func TestImagePruneAll(t *testing.T) { testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").AssertOK() imageName := testutil.Identifier(t) dockerfile := fmt.Sprintf(`FROM %s diff --git a/cmd/nerdctl/image_pull_linux_test.go b/cmd/nerdctl/image_pull_linux_test.go index edfc5af1854..dbb0ff4d8e8 100644 --- a/cmd/nerdctl/image_pull_linux_test.go +++ b/cmd/nerdctl/image_pull_linux_test.go @@ -63,9 +63,9 @@ func TestImageVerifyWithCosign(t *testing.T) { testutil.RequireExecutable(t, "cosign") testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) base.Env = append(base.Env, "COSIGN_PASSWORD=1") - defer base.Cmd("builder", "prune").Run() keyPair := newCosignKeyPair(t, "cosign-key-pair", "1") defer keyPair.cleanup() tID := testutil.Identifier(t) @@ -91,8 +91,8 @@ CMD ["echo", "nerdctl-build-test-string"] func TestImagePullPlainHttpWithDefaultPort(t *testing.T) { testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() reg := testregistry.NewWithNoAuth(base, 80, false) defer reg.Cleanup(nil) testImageRef := fmt.Sprintf("%s/%s:%s", @@ -113,11 +113,11 @@ func TestImageVerifyWithCosignShouldFailWhenKeyIsNotCorrect(t *testing.T) { testutil.RequireExecutable(t, "cosign") testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) base.Env = append(base.Env, "COSIGN_PASSWORD=1") keyPair := newCosignKeyPair(t, "cosign-key-pair", "1") defer keyPair.cleanup() - defer base.Cmd("builder", "prune").Run() tID := testutil.Identifier(t) reg := testregistry.NewWithNoAuth(base, 0, false) defer reg.Cleanup(nil) diff --git a/cmd/nerdctl/ipfs_build_linux_test.go b/cmd/nerdctl/ipfs_build_linux_test.go index 0623fee2b19..c3373715fa9 100644 --- a/cmd/nerdctl/ipfs_build_linux_test.go +++ b/cmd/nerdctl/ipfs_build_linux_test.go @@ -31,8 +31,8 @@ import ( func TestIPFSBuild(t *testing.T) { testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() ipfsCID := pushImageToIPFS(t, base, testutil.AlpineImage) ipfsCIDBase := strings.TrimPrefix(ipfsCID, "ipfs://") diff --git a/cmd/nerdctl/ipfs_compose_linux_test.go b/cmd/nerdctl/ipfs_compose_linux_test.go index e0646cc2632..3257fef88d3 100644 --- a/cmd/nerdctl/ipfs_compose_linux_test.go +++ b/cmd/nerdctl/ipfs_compose_linux_test.go @@ -118,8 +118,8 @@ volumes: func TestIPFSComposeUpBuild(t *testing.T) { testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() ipfsCID := pushImageToIPFS(t, base, testutil.NginxAlpineImage) ipfsCIDBase := strings.TrimPrefix(ipfsCID, "ipfs://") diff --git a/cmd/nerdctl/multi_platform_linux_test.go b/cmd/nerdctl/multi_platform_linux_test.go index 638b265eae5..944bcf48e28 100644 --- a/cmd/nerdctl/multi_platform_linux_test.go +++ b/cmd/nerdctl/multi_platform_linux_test.go @@ -54,9 +54,9 @@ func TestMultiPlatformRun(t *testing.T) { func TestMultiPlatformBuildPush(t *testing.T) { testutil.DockerIncompatible(t) // non-buildx version of `docker build` lacks multi-platform. Also, `docker push` lacks --platform. testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7") base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() tID := testutil.Identifier(t) reg := testregistry.NewWithNoAuth(base, 0, false) defer reg.Cleanup(nil) @@ -81,9 +81,9 @@ RUN echo dummy func TestMultiPlatformBuildPushNoRun(t *testing.T) { testutil.DockerIncompatible(t) // non-buildx version of `docker build` lacks multi-platform. Also, `docker push` lacks --platform. testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7") base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() tID := testutil.Identifier(t) reg := testregistry.NewWithNoAuth(base, 0, false) defer reg.Cleanup(nil) @@ -121,9 +121,9 @@ func TestMultiPlatformPullPushAllPlatforms(t *testing.T) { func TestMultiPlatformComposeUpBuild(t *testing.T) { testutil.DockerIncompatible(t) testutil.RequiresBuild(t) + testutil.RegisterBuildCacheCleanup(t) testutil.RequireExecPlatform(t, "linux/amd64", "linux/arm64", "linux/arm/v7") base := testutil.NewBase(t) - defer base.Cmd("builder", "prune").Run() const dockerComposeYAML = ` services: diff --git a/docs/command-reference.md b/docs/command-reference.md index 459d4c1bba5..caffd51e457 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -1242,6 +1242,10 @@ Usage: `nerdctl builder prune` Flags: - :nerd_face: `--buildkit-host=`: BuildKit address +- :whale: `--all`: Remove all unused build cache, not just dangling ones +- :whale: `--force`: Do not prompt for confirmation + +Unimplemented `docker builder prune` flags: `--filter`, `--keep-storage` ### :nerd_face: nerdctl builder debug @@ -1260,8 +1264,6 @@ Flags: - :nerd_face: `--target`: Set the target build stage to build - :nerd_face: `--build-arg`: Set build-time variables -Unimplemented `docker builder prune` flags: `--all`, `--filter`, `--force`, `--keep-storage` - ## System ### :whale: nerdctl events diff --git a/pkg/api/types/builder_types.go b/pkg/api/types/builder_types.go index fd8f7d87ca9..c68ec7c44b7 100644 --- a/pkg/api/types/builder_types.go +++ b/pkg/api/types/builder_types.go @@ -82,4 +82,6 @@ type BuilderPruneOptions struct { BuildKitHost string // All will remove all unused images and all build cache, not just dangling ones All bool + // Force will not prompt for confirmation. + Force bool } diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 296e6859866..492ecd79d04 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -747,3 +747,11 @@ func ImageRepo(s string) string { repo, _ := imgutil.ParseRepoTag(s) return repo } + +// RegisterBuildCacheCleanup adds a 'builder prune --all --force' cleanup function +// to run on test teardown. +func RegisterBuildCacheCleanup(t *testing.T) { + t.Cleanup(func() { + NewBase(t).Cmd("builder", "prune", "--all", "--force").Run() + }) +}