Skip to content

Commit a24aa11

Browse files
author
hejianshan
committed
fix: add --timeout=0 to use containerd's default 24h lease expiration
Previously, --timeout=0 passed leases.WithRandomID() to client.WithLease, which caused containerd to skip its default 24h expiration (only applied when len(opts)==0). This resulted in a lease that never expired. Fix by calling client.WithLease(ctx) with no opts when --timeout=0, so containerd correctly applies its default 24h lease expiration.
1 parent 6796cea commit a24aa11

6 files changed

Lines changed: 102 additions & 2 deletions

File tree

cmd/nerdctl/container/container_commit.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package container
1818

1919
import (
2020
"errors"
21+
"time"
2122

2223
"github.com/spf13/cobra"
2324

@@ -51,6 +52,7 @@ func CommitCommand() *cobra.Command {
5152
cmd.Flags().Bool("zstdchunked", false, "Convert the committed layer to zstd:chunked for lazy pulling")
5253
cmd.Flags().Int("zstdchunked-compression-level", 3, "zstd:chunked compression level")
5354
cmd.Flags().Int("zstdchunked-chunk-size", 0, "zstd:chunked chunk size")
55+
cmd.Flags().Duration("timeout", 1*time.Hour, "Maximum duration for the commit operation (default 1h, 0 for containerd's default 24h)")
5456
return cmd
5557
}
5658

@@ -123,6 +125,11 @@ func commitOptions(cmd *cobra.Command) (types.ContainerCommitOptions, error) {
123125
return types.ContainerCommitOptions{}, err
124126
}
125127

128+
timeout, err := cmd.Flags().GetDuration("timeout")
129+
if err != nil {
130+
return types.ContainerCommitOptions{}, err
131+
}
132+
126133
// estargz and zstdchunked are mutually exclusive
127134
if estargz && zstdchunked {
128135
return types.ContainerCommitOptions{}, errors.New("options --estargz and --zstdchunked lead to conflict, only one of them can be used")
@@ -137,6 +144,7 @@ func commitOptions(cmd *cobra.Command) (types.ContainerCommitOptions, error) {
137144
Change: change,
138145
Compression: types.CompressionType(com),
139146
Format: types.ImageFormat(format),
147+
Timeout: timeout,
140148
EstargzOptions: types.EstargzOptions{
141149
Estargz: estargz,
142150
EstargzCompressionLevel: estargzCompressionLevel,

cmd/nerdctl/container/container_commit_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,82 @@ func TestCommit(t *testing.T) {
9191
testCase.Run(t)
9292
}
9393

94+
func TestCommitWithTimeout(t *testing.T) {
95+
testCase := nerdtest.Setup()
96+
testCase.Require = require.All(
97+
require.Not(nerdtest.Docker),
98+
require.Not(require.Windows),
99+
nerdtest.CGroup,
100+
)
101+
102+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
103+
identifier := data.Identifier()
104+
helpers.Ensure("run", "-d", "--name", identifier, testutil.CommonImage, "sleep", nerdtest.Infinity)
105+
nerdtest.EnsureContainerStarted(helpers, identifier)
106+
helpers.Ensure("exec", identifier, "sh", "-euxc", `echo hello-test-commit-timeout > /foo`)
107+
}
108+
109+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
110+
helpers.Anyhow("rm", "-f", data.Identifier())
111+
helpers.Anyhow("rmi", "-f", data.Identifier())
112+
}
113+
114+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
115+
identifier := data.Identifier()
116+
helpers.Ensure(
117+
"commit",
118+
"--timeout=2h",
119+
"-c", `CMD ["/foo"]`,
120+
"-c", `ENTRYPOINT ["cat"]`,
121+
"--pause=false",
122+
identifier, identifier,
123+
)
124+
return helpers.Command("run", "--rm", identifier)
125+
}
126+
127+
testCase.Expected = test.Expects(0, nil, expect.Equals("hello-test-commit-timeout\n"))
128+
129+
testCase.Run(t)
130+
}
131+
132+
func TestCommitWithTimeoutZero(t *testing.T) {
133+
testCase := nerdtest.Setup()
134+
testCase.Require = require.All(
135+
require.Not(nerdtest.Docker),
136+
require.Not(require.Windows),
137+
nerdtest.CGroup,
138+
)
139+
140+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
141+
identifier := data.Identifier()
142+
helpers.Ensure("run", "-d", "--name", identifier, testutil.CommonImage, "sleep", nerdtest.Infinity)
143+
nerdtest.EnsureContainerStarted(helpers, identifier)
144+
helpers.Ensure("exec", identifier, "sh", "-euxc", `echo hello-test-commit-timeout0 > /foo`)
145+
}
146+
147+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
148+
helpers.Anyhow("rm", "-f", data.Identifier())
149+
helpers.Anyhow("rmi", "-f", data.Identifier())
150+
}
151+
152+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
153+
identifier := data.Identifier()
154+
helpers.Ensure(
155+
"commit",
156+
"--timeout=0s",
157+
"-c", `CMD ["/foo"]`,
158+
"-c", `ENTRYPOINT ["cat"]`,
159+
"--pause=false",
160+
identifier, identifier,
161+
)
162+
return helpers.Command("run", "--rm", identifier)
163+
}
164+
165+
testCase.Expected = test.Expects(0, nil, expect.Equals("hello-test-commit-timeout0\n"))
166+
167+
testCase.Run(t)
168+
}
169+
94170
func TestZstdCommit(t *testing.T) {
95171
testCase := nerdtest.Setup()
96172
testCase.Require = require.All(

docs/command-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ Flags:
816816
- :whale: `-m, --message`: Commit message
817817
- :whale: `-c, --change`: Apply Dockerfile instruction to the created image (supported directives: [CMD, ENTRYPOINT])
818818
- :whale: `-p, --pause`: Pause container during commit (default: true)
819+
- :nerd_face: `--timeout`: Maximum duration for the commit operation (default: 1h). Set to 0 to use containerd's default lease expiration (24h). Accepts Go duration format (e.g., `2h`, `90m`, `0s`).
819820
- :nerd_face: `--compression`: Commit compression algorithm (supported values: zstd or gzip) (default: gzip) (zstd is generally better for compression ratio but might not be as widely supported)
820821
- :nerd_face: `--format`: Format of the committed image (supported values: docker or oci) (default: docker) (docker uses Docker Schema2 media types for compatibility, oci uses OCI image format media types)
821822
- :nerd_face: `--estargz`: Convert the committed layer to eStargz for lazy pulling

pkg/api/types/container_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,9 @@ type ContainerCommitOptions struct {
421421
Compression CompressionType
422422
// Format specifies the image format for the committed image (docker or oci)
423423
Format ImageFormat
424+
// Timeout is the maximum duration for the commit operation (lease expiration).
425+
// Defaults to 1 hour. Set to 0 for no timeout (24h lease).
426+
Timeout time.Duration
424427
// Embed EstargzOptions for eStargz conversion options
425428
EstargzOptions
426429
// Embed ZstdChunkedOptions for zstd:chunked conversion options

pkg/cmd/container/commit.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func Commit(ctx context.Context, client *containerd.Client, rawRef string, req s
5151
Changes: changes,
5252
Compression: options.Compression,
5353
Format: options.Format,
54+
Timeout: options.Timeout,
5455
EstargzOptions: options.EstargzOptions,
5556
ZstdChunkedOptions: options.ZstdChunkedOptions,
5657
}

pkg/imgutil/commit/commit.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ type Opts struct {
6868
Changes Changes
6969
Compression types.CompressionType
7070
Format types.ImageFormat
71+
// Timeout is the maximum duration for the commit operation (lease expiration).
72+
// Defaults to 1 hour. Set to 0 to use containerd's default (24h).
73+
Timeout time.Duration
7174
types.EstargzOptions
7275
types.ZstdChunkedOptions
7376
}
@@ -174,8 +177,16 @@ func Commit(ctx context.Context, client *containerd.Client, container containerd
174177
sn = client.SnapshotService(snName)
175178
)
176179

177-
// Don't gc me and clean the dirty data after 1 hour!
178-
ctx, done, err := client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
180+
// Set lease expiration based on the configured timeout.
181+
// The CLI flag defaults to 1h for backward compatibility.
182+
// Set --timeout=0 to use containerd's default lease expiration (24h).
183+
var done func(context.Context) error
184+
if opts.Timeout <= 0 {
185+
// Use containerd's default lease expiration (24h) by passing no opts.
186+
ctx, done, err = client.WithLease(ctx)
187+
} else {
188+
ctx, done, err = client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(opts.Timeout))
189+
}
179190
if err != nil {
180191
return emptyDigest, fmt.Errorf("failed to create lease for commit: %w", err)
181192
}

0 commit comments

Comments
 (0)