Skip to content

Commit 97afa35

Browse files
committed
feat(image): support --change on import
Apply Dockerfile-style instructions to the config of the image created by `nerdctl import`, matching `docker import --change`. Supported instructions are the ones representable in the OCI image config: CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, USER, VOLUME, WORKDIR, STOPSIGNAL. HEALTHCHECK, ONBUILD and SHELL only exist in Docker's config schema and are rejected with a clear error. --change applies to a filesystem (rootfs) import, which builds a fresh config; it is rejected for a standard image archive that already carries its own config. Part of #3867 Signed-off-by: Mayur Das <mayur.das@neevcloud.com>
1 parent 5638af9 commit 97afa35

7 files changed

Lines changed: 460 additions & 2 deletions

File tree

cmd/nerdctl/image/image_import.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func ImportCommand() *cobra.Command {
4545

4646
cmd.Flags().StringP("message", "m", "", "Set commit message for imported image")
4747
cmd.Flags().String("platform", "", "Set platform for imported image (e.g., linux/amd64)")
48+
cmd.Flags().StringArrayP("change", "c", nil, "Apply Dockerfile instruction to the created image (e.g. 'CMD [\"echo\"]')")
4849
return cmd
4950
}
5051

@@ -61,6 +62,10 @@ func importOptions(cmd *cobra.Command, args []string) (types.ImageImportOptions,
6162
if err != nil {
6263
return types.ImageImportOptions{}, err
6364
}
65+
changes, err := cmd.Flags().GetStringArray("change")
66+
if err != nil {
67+
return types.ImageImportOptions{}, err
68+
}
6469
var reference string
6570
if len(args) > 1 {
6671
reference = args[1]
@@ -97,6 +102,7 @@ func importOptions(cmd *cobra.Command, args []string) (types.ImageImportOptions,
97102
Reference: reference,
98103
Message: message,
99104
Platform: platform,
105+
Changes: changes,
100106
}, nil
101107
}
102108

cmd/nerdctl/image/image_import_linux_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"net/http"
2424
"os"
2525
"path/filepath"
26+
"slices"
2627
"strings"
2728
"testing"
2829

@@ -143,6 +144,38 @@ func TestImageImport(t *testing.T) {
143144
}
144145
},
145146
},
147+
{
148+
Description: "image import with change",
149+
Cleanup: func(data test.Data, helpers test.Helpers) {
150+
helpers.Anyhow("rmi", "-f", data.Identifier())
151+
},
152+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
153+
cmd := helpers.Command("import",
154+
"--change", `CMD ["echo","hi"]`,
155+
"--change", "ENV FOO=bar",
156+
"--change", "WORKDIR /srv",
157+
"--change", "EXPOSE 8080",
158+
"-", data.Identifier())
159+
cmd.Feed(bytes.NewReader(minimalRootfsTar(t).Bytes()))
160+
return cmd
161+
},
162+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
163+
identifier := data.Identifier() + ":latest"
164+
return &test.Expected{
165+
Output: expect.All(
166+
func(stdout string, t tig.T) {
167+
img := nerdtest.InspectImage(helpers, identifier)
168+
assert.Assert(t, img.Config != nil)
169+
assert.DeepEqual(t, img.Config.Cmd, []string{"echo", "hi"})
170+
assert.Assert(t, slices.Contains(img.Config.Env, "FOO=bar"))
171+
assert.Equal(t, img.Config.WorkingDir, "/srv")
172+
_, ok := img.Config.ExposedPorts["8080/tcp"]
173+
assert.Assert(t, ok)
174+
},
175+
),
176+
}
177+
},
178+
},
146179
{
147180
Description: "image import with platform",
148181
Cleanup: func(data test.Data, helpers test.Helpers) {

docs/command-reference.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,10 +944,9 @@ Usage: `nerdctl import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]`
944944
Flags:
945945

946946
- :whale: `-m, --message`: Set commit message for imported image
947+
- :whale: `-c, --change`: Apply a Dockerfile instruction to the created image, e.g. `--change 'CMD ["echo"]'`. Repeatable. Supported instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`, `LABEL`, `USER`, `VOLUME`, `WORKDIR`, `STOPSIGNAL`.
947948
- :nerd_face: `--platform=(linux/amd64|linux/arm64|...)`: Set platform for the imported image
948949

949-
Unimplemented `docker import` flags: `--change`
950-
951950
### :whale: nerdctl tag
952951

953952
Create a tag TARGET\_IMAGE that refers to SOURCE\_IMAGE.

pkg/api/types/import_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ type ImageImportOptions struct {
2828
Reference string
2929
Message string
3030
Platform string
31+
// Changes holds Dockerfile-style instructions (--change) applied to the
32+
// imported image's config, e.g. `CMD ["echo"]` or `ENV FOO=bar`.
33+
Changes []string
3134
}

pkg/cmd/image/import.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ func ensureOCIArchive(ctx context.Context, client *containerd.Client, r io.ReadC
111111

112112
combined := io.NopCloser(io.MultiReader(buf, r))
113113
if isStandardArchive {
114+
// A standard image archive already carries its own config; --change only
115+
// applies to a filesystem (rootfs) import, which builds a fresh config.
116+
if len(options.Changes) > 0 {
117+
r.Close()
118+
return nil, func() {}, fmt.Errorf("--change is only supported when importing a filesystem archive, not a standard image archive")
119+
}
114120
return combined, func() { r.Close() }, nil
115121
}
116122

@@ -268,6 +274,11 @@ func buildImageConfig(diffID digest.Digest, options types.ImageImportOptions) ([
268274
}},
269275
}
270276

277+
// Apply any --change instructions to the fresh config.
278+
if err := applyChanges(&imgConfig.Config, options.Changes); err != nil {
279+
return nil, "", err
280+
}
281+
271282
configJSON, err := json.Marshal(imgConfig)
272283
if err != nil {
273284
return nil, "", err

0 commit comments

Comments
 (0)