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

Add builder prune --all and --force flag support #3316

Merged
merged 1 commit into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 68 additions & 14 deletions cmd/nerdctl/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
38 changes: 19 additions & 19 deletions cmd/nerdctl/builder_build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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()

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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()

Expand All @@ -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()

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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()

Expand All @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -540,14 +540,14 @@ 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 {
// create named builder for docker
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)
Expand Down
36 changes: 35 additions & 1 deletion cmd/nerdctl/builder_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cmd/nerdctl/compose_build_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cmd/nerdctl/compose_create_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cmd/nerdctl/compose_run_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
})

Expand Down
Loading
Loading