Skip to content

Commit c3c0bf0

Browse files
authored
Merge pull request #4562 from ChengyuZhu6/checkpoint-rm
checkpoint: support checkpoint rm command
2 parents f630b96 + 7d0091c commit c3c0bf0

File tree

10 files changed

+305
-9
lines changed

10 files changed

+305
-9
lines changed

cmd/nerdctl/checkpoint/checkpoint.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,23 @@ func Command() *cobra.Command {
3333
}
3434

3535
cmd.AddCommand(
36-
CreateCommand(),
37-
checkpointLsCommand(),
36+
createCommand(),
37+
lsCommand(),
38+
rmCommand(),
3839
)
3940

4041
return cmd
4142
}
4243

43-
func checkpointLsCommand() *cobra.Command {
44-
x := ListCommand()
44+
func lsCommand() *cobra.Command {
45+
x := listCommand()
4546
x.Use = "ls"
4647
x.Aliases = []string{"list"}
4748
return x
4849
}
50+
func rmCommand() *cobra.Command {
51+
x := removeCommand()
52+
x.Use = "rm"
53+
x.Aliases = []string{"remove"}
54+
return x
55+
}

cmd/nerdctl/checkpoint/checkpoint_create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint"
2929
)
3030

31-
func CreateCommand() *cobra.Command {
31+
func createCommand() *cobra.Command {
3232
var cmd = &cobra.Command{
3333
Use: "create [OPTIONS] CONTAINER CHECKPOINT",
3434
Short: "Create a checkpoint from a running container",

cmd/nerdctl/checkpoint/checkpoint_create_linux_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func TestCheckpointCreate(t *testing.T) {
8383
// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.
8484
require.Not(nerdtest.Docker),
8585
)
86+
testCase.NoParallel = true
8687
testCase.SubTests = []*test.Case{
8788
{
8889
Description: "leave-running=true",

cmd/nerdctl/checkpoint/checkpoint_list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint"
3030
)
3131

32-
func ListCommand() *cobra.Command {
32+
func listCommand() *cobra.Command {
3333
var cmd = &cobra.Command{
3434
Use: "list [OPTIONS] CONTAINER",
3535
Short: "List checkpoints for a container",

cmd/nerdctl/checkpoint/checkpoint_list_linux_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,14 @@ func TestCheckpointList(t *testing.T) {
8080
// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.
8181
require.Not(nerdtest.Docker),
8282
)
83-
83+
testCase.NoParallel = true
8484
testCase.Setup = func(data test.Data, helpers test.Helpers) {
8585
helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "infinity")
8686
helpers.Ensure("checkpoint", "create", data.Identifier(), checkpointName)
8787
}
8888

8989
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
9090
helpers.Anyhow("rm", "-f", data.Identifier())
91-
helpers.Anyhow("rmi", "-f", testutil.CommonImage)
9291
}
9392

9493
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package checkpoint
18+
19+
import (
20+
"path/filepath"
21+
22+
"github.com/spf13/cobra"
23+
24+
"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
25+
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
26+
"github.com/containerd/nerdctl/v2/pkg/api/types"
27+
"github.com/containerd/nerdctl/v2/pkg/clientutil"
28+
"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint"
29+
)
30+
31+
func removeCommand() *cobra.Command {
32+
var cmd = &cobra.Command{
33+
Use: "rm [OPTIONS] CONTAINER CHECKPOINT",
34+
Short: "Remove a checkpoint",
35+
Args: cobra.ExactArgs(2),
36+
RunE: removeAction,
37+
ValidArgsFunction: removeShellComplete,
38+
SilenceUsage: true,
39+
SilenceErrors: true,
40+
}
41+
cmd.Flags().String("checkpoint-dir", "", "Checkpoint directory")
42+
return cmd
43+
}
44+
45+
func processRemoveFlags(cmd *cobra.Command) (types.CheckpointRemoveOptions, error) {
46+
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
47+
if err != nil {
48+
return types.CheckpointRemoveOptions{}, err
49+
}
50+
51+
checkpointDir, err := cmd.Flags().GetString("checkpoint-dir")
52+
if err != nil {
53+
return types.CheckpointRemoveOptions{}, err
54+
}
55+
if checkpointDir == "" {
56+
checkpointDir = filepath.Join(globalOptions.DataRoot, "checkpoints")
57+
}
58+
59+
return types.CheckpointRemoveOptions{
60+
Stdout: cmd.OutOrStdout(),
61+
GOptions: globalOptions,
62+
CheckpointDir: checkpointDir,
63+
}, nil
64+
}
65+
66+
func removeAction(cmd *cobra.Command, args []string) error {
67+
removeOptions, err := processRemoveFlags(cmd)
68+
if err != nil {
69+
return err
70+
}
71+
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), removeOptions.GOptions.Namespace, removeOptions.GOptions.Address)
72+
if err != nil {
73+
return err
74+
}
75+
defer cancel()
76+
77+
err = checkpoint.Remove(ctx, client, args[0], args[1], removeOptions)
78+
if err != nil {
79+
return err
80+
}
81+
82+
return nil
83+
}
84+
85+
func removeShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
86+
return completion.ImageNames(cmd)
87+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package checkpoint
18+
19+
import (
20+
"errors"
21+
"testing"
22+
23+
"github.com/containerd/nerdctl/mod/tigron/expect"
24+
"github.com/containerd/nerdctl/mod/tigron/require"
25+
"github.com/containerd/nerdctl/mod/tigron/test"
26+
27+
"github.com/containerd/nerdctl/v2/pkg/testutil"
28+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
29+
)
30+
31+
func TestCheckpointRemoveErrors(t *testing.T) {
32+
testCase := nerdtest.Setup()
33+
34+
testCase.Require = require.All(
35+
require.Not(nerdtest.Rootless),
36+
// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.
37+
// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.
38+
require.Not(nerdtest.Docker),
39+
)
40+
testCase.SubTests = []*test.Case{
41+
{
42+
Description: "too-few-arguments",
43+
Command: test.Command("checkpoint", "rm", "too-few-arguments"),
44+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
45+
return &test.Expected{
46+
ExitCode: 1,
47+
}
48+
},
49+
},
50+
{
51+
Description: "too-many-arguments",
52+
Command: test.Command("checkpoint", "rm", "too", "many", "arguments"),
53+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
54+
return &test.Expected{
55+
ExitCode: 1,
56+
}
57+
},
58+
},
59+
{
60+
Description: "invalid-container-id",
61+
Command: test.Command("checkpoint", "rm", "foo", "bar"),
62+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
63+
return &test.Expected{
64+
ExitCode: 1,
65+
Errors: []error{errors.New("error removing checkpoint for container: foo")},
66+
}
67+
},
68+
},
69+
}
70+
71+
testCase.Run(t)
72+
}
73+
74+
func TestCheckpointRemove(t *testing.T) {
75+
const (
76+
checkpointName = "checkpoint-remove"
77+
checkpointDir = "/dir/remove"
78+
)
79+
testCase := nerdtest.Setup()
80+
testCase.Require = require.All(
81+
require.Not(nerdtest.Rootless),
82+
// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.
83+
// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.
84+
require.Not(nerdtest.Docker),
85+
)
86+
testCase.NoParallel = true
87+
testCase.SubTests = []*test.Case{
88+
{
89+
Description: "remove-existing",
90+
Setup: func(data test.Data, helpers test.Helpers) {
91+
helpers.Ensure("run", "-d", "--name", data.Identifier("container-running-remove"), testutil.CommonImage, "sleep", "infinity")
92+
helpers.Ensure("checkpoint", "create", "--checkpoint-dir", checkpointDir, data.Identifier("container-running-remove"), checkpointName)
93+
},
94+
Cleanup: func(data test.Data, helpers test.Helpers) {
95+
helpers.Anyhow("rm", "-f", data.Identifier("container-running-remove"))
96+
},
97+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
98+
return helpers.Command("checkpoint", "rm", "--checkpoint-dir", checkpointDir, data.Identifier("container-running-remove"), checkpointName)
99+
},
100+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
101+
return &test.Expected{
102+
ExitCode: 0,
103+
Output: expect.Equals(""),
104+
}
105+
},
106+
},
107+
{
108+
Description: "remove-nonexistent-checkpoint",
109+
Setup: func(data test.Data, helpers test.Helpers) {
110+
helpers.Ensure("run", "-d", "--name", data.Identifier("container-clean-remove"), testutil.CommonImage, "sleep", "infinity")
111+
},
112+
Cleanup: func(data test.Data, helpers test.Helpers) {
113+
helpers.Anyhow("rm", "-f", data.Identifier("container-clean-remove"))
114+
},
115+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
116+
return helpers.Command("checkpoint", "rm", "--checkpoint-dir", checkpointDir, data.Identifier("container-clean-remove"), checkpointName)
117+
},
118+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
119+
return &test.Expected{
120+
ExitCode: 1,
121+
Errors: []error{errors.New("checkpoint " + checkpointName + " does not exist for container")},
122+
}
123+
},
124+
},
125+
}
126+
127+
testCase.Run(t)
128+
}

docs/command-reference.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
- [Checkpoint management](#checkpoint-management)
5757
- [:whale: nerdctl checkpoint create](#whale-nerdctl-checkpoint-create)
5858
- [:whale: nerdctl checkpoint list](#whale-nerdctl-checkpoint-list)
59+
- [:whale: nerdctl checkpoint remove](#whale-nerdctl-checkpoint-remove)
5960
- [Manifest management](#manifest-management)
6061
- [:whale: nerdctl manifest annotate](#whale-nerdctl-manifest-annotate)
6162
- [:whale: nerdctl manifest create](#whale-nerdctl-manifest-create)
@@ -1086,6 +1087,15 @@ Usage: `nerdctl checkpoint list/ls [OPTIONS] CONTAINER`
10861087
Flags:
10871088
- :whale: `checkpoint-dir`: Use a custom checkpoint storage directory
10881089

1090+
### :whale: nerdctl checkpoint remove
1091+
1092+
Remove a checkpoint for a container
1093+
1094+
Usage: `nerdctl checkpoint remove/rm [OPTIONS] CONTAINER CHECKPOINT`
1095+
1096+
Flags:
1097+
- :whale: `checkpoint-dir`: Use a custom checkpoint storage directory
1098+
10891099
## Manifest management
10901100

10911101
### :whale: nerdctl manifest annotate
@@ -1958,7 +1968,6 @@ See [`./config.md`](./config.md).
19581968
Container management:
19591969

19601970
- `docker diff`
1961-
- `docker checkpoint *`
19621971

19631972
Image:
19641973

pkg/api/types/checkpoint_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ type CheckpointListOptions struct {
3535
CheckpointDir string
3636
}
3737

38+
// CheckpointRemoveOptions specifies options for `nerdctl checkpoint rm`.
39+
type CheckpointRemoveOptions struct {
40+
Stdout io.Writer
41+
GOptions GlobalCommandOptions
42+
// Checkpoint directory
43+
CheckpointDir string
44+
}
3845
type CheckpointSummary struct {
3946
// Name is the name of the checkpoint.
4047
Name string

pkg/cmd/checkpoint/remove.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package checkpoint
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
24+
containerd "github.com/containerd/containerd/v2/client"
25+
26+
"github.com/containerd/nerdctl/v2/pkg/api/types"
27+
"github.com/containerd/nerdctl/v2/pkg/checkpointutil"
28+
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
29+
)
30+
31+
func Remove(ctx context.Context, client *containerd.Client, containerID string, checkpointName string, options types.CheckpointRemoveOptions) error {
32+
var container containerd.Container
33+
34+
walker := &containerwalker.ContainerWalker{
35+
Client: client,
36+
OnFound: func(ctx context.Context, found containerwalker.Found) error {
37+
if found.MatchCount > 1 {
38+
return fmt.Errorf("multiple containers found with provided prefix: %s", found.Req)
39+
}
40+
container = found.Container
41+
return nil
42+
},
43+
}
44+
45+
n, err := walker.Walk(ctx, containerID)
46+
if err != nil {
47+
return err
48+
} else if n == 0 {
49+
return fmt.Errorf("error removing checkpoint for container: %s, no such container", containerID)
50+
}
51+
52+
targetPath, err := checkpointutil.GetCheckpointDir(options.CheckpointDir, checkpointName, container.ID(), false)
53+
if err != nil {
54+
return err
55+
}
56+
57+
return os.RemoveAll(targetPath)
58+
}

0 commit comments

Comments
 (0)