Skip to content

Commit 445fb7b

Browse files
committed
add soci convert feature
Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent 7564040 commit 445fb7b

File tree

9 files changed

+210
-26
lines changed

9 files changed

+210
-26
lines changed

cmd/nerdctl/image/image_convert.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ func convertCommand() *cobra.Command {
8989
cmd.Flags().String("overlaybd-dbstr", "", "Database config string for overlaybd")
9090
// #endregion
9191

92+
// #region soci flags
93+
cmd.Flags().Bool("soci", false, "Convert image to SOCI Index V2 format.")
94+
cmd.Flags().Int64("soci-min-layer-size", -1, "The minimum size of layers that will be converted to SOCI Index V2 format")
95+
cmd.Flags().Int64("soci-span-size", -1, "The size of SOCI spans")
96+
// #endregion
97+
9298
// #region generic flags
9399
cmd.Flags().Bool("uncompress", false, "Convert tar.gz layers to uncompressed tar layers")
94100
cmd.Flags().Bool("oci", false, "Convert Docker media types to OCI media types")
@@ -213,6 +219,21 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
213219
}
214220
// #endregion
215221

222+
// #region soci flags
223+
soci, err := cmd.Flags().GetBool("soci")
224+
if err != nil {
225+
return types.ImageConvertOptions{}, err
226+
}
227+
sociMinLayerSize, err := cmd.Flags().GetInt64("soci-min-layer-size")
228+
if err != nil {
229+
return types.ImageConvertOptions{}, err
230+
}
231+
sociSpanSize, err := cmd.Flags().GetInt64("soci-span-size")
232+
if err != nil {
233+
return types.ImageConvertOptions{}, err
234+
}
235+
// #endregion
236+
216237
// #region generic flags
217238
uncompress, err := cmd.Flags().GetBool("uncompress")
218239
if err != nil {
@@ -277,6 +298,13 @@ func convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {
277298
OverlayFsType: overlaybdFsType,
278299
OverlaydbDBStr: overlaybdDbstr,
279300
},
301+
SociConvertOptions: types.SociConvertOptions{
302+
Soci: soci,
303+
SociOptions: types.SociOptions{
304+
SpanSize: sociSpanSize,
305+
MinLayerSize: sociMinLayerSize,
306+
},
307+
},
280308
Stdout: cmd.OutOrStdout(),
281309
}, nil
282310
}

cmd/nerdctl/image/image_convert_linux_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,24 @@ func TestImageConvert(t *testing.T) {
8989
},
9090
Expected: test.Expects(0, nil, nil),
9191
},
92+
{
93+
Description: "soci",
94+
Require: require.All(
95+
require.Not(nerdtest.Docker),
96+
nerdtest.Soci,
97+
nerdtest.SociVersion("0.10.0"),
98+
),
99+
Cleanup: func(data test.Data, helpers test.Helpers) {
100+
helpers.Anyhow("rmi", "-f", data.Identifier("converted-image"))
101+
},
102+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
103+
return helpers.Command("image", "convert", "--soci",
104+
"--soci-span-size", "2097152",
105+
"--soci-min-layer-size", "20971520",
106+
testutil.CommonImage, data.Identifier("converted-image"))
107+
},
108+
Expected: test.Expects(0, nil, nil),
109+
},
92110
},
93111
}
94112

docs/command-reference.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,11 @@ Flags:
979979
- `--oci` : convert Docker media types to OCI media types
980980
- `--platform=<PLATFORM>` : convert content for a specific platform
981981
- `--all-platforms` : convert content for all platforms (default: false)
982+
- `--soci` : generate SOCI v2 Indices to oci images.
983+
*[**Note**: content is converted for all platforms by default when using this flag, use the `--platorm` flag to limit this behavior]*
984+
- `--soci-span-size` : Span size in bytes that soci index uses to segment layer data. Default is 4 MiB.
985+
- `--soci-min-layer-size`: Minimum layer size in bytes to build zTOC for. Smaller layers won't have zTOC and not lazy pulled. Default is 10 MiB.
986+
982987

983988
### :nerd_face: nerdctl image encrypt
984989

docs/soci.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,18 @@ For images that already have SOCI indices, see https://gallery.ecr.aws/soci-work
4545
nerdctl push --snapshotter=soci --soci-span-size=2097152 --soci-min-layer-size=20971520 public.ecr.aws/my-registry/my-repo:latest
4646
```
4747
--soci-span-size and --soci-min-layer-size are two properties to customize the SOCI index. See [Command Reference](https://github.com/containerd/nerdctl/blob/377b2077bb616194a8ef1e19ccde32aa1ffd6c84/docs/command-reference.md?plain=1#L773) for further details.
48+
49+
50+
## Enable SOCI for `nerdctl image convert`
51+
52+
| :zap: Requirement | nerdctl >= 2.2.0 |
53+
| ----------------- | ---------------- |
54+
55+
| :zap: Requirement | soci-snapshotter >= 0.10.0 |
56+
| ----------------- | ---------------- |
57+
58+
- Convert an image to generate SOCI Index artifacts v2. Running the `nerdctl image convert` with the `--soci` flag and a `srcImg` and `dstImg`, `nerdctl` will create the SOCI v2 indices and the new image will be present in the `dstImg` address.
59+
```console
60+
nerdctl image convert --soci --soci-span-size=2097152 --soci-min-layer-size=20971520 public.ecr.aws/my-registry/my-repo:latest public.ecr.aws/my-registry/my-repo:soci
61+
```
62+
--soci-span-size and --soci-min-layer-size are two properties to customize the SOCI index. See [Command Reference](https://github.com/containerd/nerdctl/blob/377b2077bb616194a8ef1e19ccde32aa1ffd6c84/docs/command-reference.md?plain=1#L773) for further details.

pkg/api/types/image_types.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package types
1919
import (
2020
"io"
2121

22-
"github.com/opencontainers/image-spec/specs-go/v1"
22+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2323
)
2424

2525
// ImageListOptions specifies options for `nerdctl image list`.
@@ -73,6 +73,7 @@ type ImageConvertOptions struct {
7373
ZstdChunkedOptions
7474
NydusOptions
7575
OverlaybdOptions
76+
SociConvertOptions
7677
}
7778

7879
// EstargzOptions contains eStargz conversion options
@@ -135,6 +136,15 @@ type OverlaybdOptions struct {
135136
OverlayFsType string
136137
// OverlaydbDBStr database config string for overlaybd
137138
OverlaydbDBStr string
139+
// #endregion
140+
}
141+
142+
type SociConvertOptions struct {
143+
// Soci convert image to SOCI format.
144+
Soci bool
145+
// SociOptions contains SOCI-specific options
146+
SociOptions SociOptions
147+
// #endregion
138148
}
139149

140150
// ImageCryptOptions specifies options for `nerdctl image encrypt` and `nerdctl image decrypt`.
@@ -211,7 +221,7 @@ type ImagePullOptions struct {
211221
// If nil, it will unpack automatically if only 1 platform is specified.
212222
Unpack *bool
213223
// Content for specific platforms. Empty if `--all-platforms` is true
214-
OCISpecPlatform []v1.Platform
224+
OCISpecPlatform []ocispec.Platform
215225
// Pull mode
216226
Mode string
217227
// Suppress verbose output

pkg/cmd/image/convert.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
converterutil "github.com/containerd/nerdctl/v2/pkg/imgutil/converter"
4848
"github.com/containerd/nerdctl/v2/pkg/platformutil"
4949
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
50+
"github.com/containerd/nerdctl/v2/pkg/snapshotterutil"
5051
)
5152

5253
func Convert(ctx context.Context, client *containerd.Client, srcRawRef, targetRawRef string, options types.ImageConvertOptions) error {
@@ -86,8 +87,9 @@ func Convert(ctx context.Context, client *containerd.Client, srcRawRef, targetRa
8687
zstdchunked := options.ZstdChunked
8788
overlaybd := options.Overlaybd
8889
nydus := options.Nydus
90+
soci := options.Soci
8991
var finalize func(ctx context.Context, cs content.Store, ref string, desc *ocispec.Descriptor) (*images.Image, error)
90-
if estargz || zstd || zstdchunked || overlaybd || nydus {
92+
if estargz || zstd || zstdchunked || overlaybd || nydus || soci {
9193
convertCount := 0
9294
if estargz {
9395
convertCount++
@@ -104,9 +106,12 @@ func Convert(ctx context.Context, client *containerd.Client, srcRawRef, targetRa
104106
if nydus {
105107
convertCount++
106108
}
109+
if soci {
110+
convertCount++
111+
}
107112

108113
if convertCount > 1 {
109-
return errors.New("options --estargz, --zstdchunked, --overlaybd and --nydus lead to conflict, only one of them can be used")
114+
return errors.New("options --estargz, --zstdchunked, --overlaybd, --nydus and --soci lead to conflict, only one of them can be used")
110115
}
111116

112117
var convertFunc converter.ConvertFunc
@@ -164,6 +169,16 @@ func Convert(ctx context.Context, client *containerd.Client, srcRawRef, targetRa
164169
)),
165170
)
166171
convertType = "nydus"
172+
case soci:
173+
// Convert image to SOCI format
174+
convertedRef, err := snapshotterutil.ConvertSociIndexV2(ctx, client, srcRef, targetRef, options.GOptions, options.Platforms, options.SociOptions)
175+
if err != nil {
176+
return fmt.Errorf("failed to convert image to SOCI format: %w", err)
177+
}
178+
res := converterutil.ConvertedImageInfo{
179+
Image: convertedRef,
180+
}
181+
return printConvertedImage(options.Stdout, options, res)
167182
}
168183

169184
if convertType != "overlaybd" {

pkg/cmd/image/push.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func Push(ctx context.Context, client *containerd.Client, rawRef string, options
210210
return err
211211
}
212212
if options.GOptions.Snapshotter == "soci" {
213-
if err = snapshotterutil.CreateSoci(ref, options.GOptions, options.AllPlatforms, options.Platforms, options.SociOptions); err != nil {
213+
if err = snapshotterutil.CreateSociIndexV1(ref, options.GOptions, options.AllPlatforms, options.Platforms, options.SociOptions); err != nil {
214214
return err
215215
}
216216
if err = snapshotterutil.PushSoci(ref, options.GOptions, options.AllPlatforms, options.Platforms); err != nil {

pkg/snapshotterutil/sociutil.go

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,26 @@ package snapshotterutil
1818

1919
import (
2020
"bufio"
21+
"context"
22+
"fmt"
2123
"os"
2224
"os/exec"
2325
"strconv"
2426
"strings"
2527

28+
"github.com/containerd/containerd/v2/client"
2629
"github.com/containerd/log"
2730

2831
"github.com/containerd/nerdctl/v2/pkg/api/types"
2932
)
3033

31-
// CreateSoci creates a SOCI index(`rawRef`)
32-
func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string, sOpts types.SociOptions) error {
34+
// setupSociCommand creates and sets up a SOCI command with common configuration
35+
func setupSociCommand(gOpts types.GlobalCommandOptions) (*exec.Cmd, error) {
3336
sociExecutable, err := exec.LookPath("soci")
3437
if err != nil {
3538
log.L.WithError(err).Error("soci executable not found in path $PATH")
3639
log.L.Info("you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md")
37-
return err
40+
return nil, err
3841
}
3942

4043
sociCmd := exec.Command(sociExecutable)
@@ -47,7 +50,64 @@ func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform boo
4750
if gOpts.Namespace != "" {
4851
sociCmd.Args = append(sociCmd.Args, "--namespace", gOpts.Namespace)
4952
}
50-
// #endregion
53+
54+
return sociCmd, nil
55+
}
56+
57+
// ConvertSociIndexV2 converts an image to SOCI format and returns the converted image reference with digest
58+
func ConvertSociIndexV2(ctx context.Context, client *client.Client, srcRef string, destRef string, gOpts types.GlobalCommandOptions, platforms []string, sOpts types.SociOptions) (string, error) {
59+
sociCmd, err := setupSociCommand(gOpts)
60+
if err != nil {
61+
return "", err
62+
}
63+
64+
sociCmd.Args = append(sociCmd.Args, "convert")
65+
66+
if len(platforms) > 0 {
67+
// multiple values need to be passed as separate, repeating flags in soci as it uses urfave
68+
// https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag
69+
for _, p := range platforms {
70+
sociCmd.Args = append(sociCmd.Args, "--platform", p)
71+
}
72+
}
73+
74+
if sOpts.SpanSize != -1 {
75+
sociCmd.Args = append(sociCmd.Args, "--span-size", strconv.FormatInt(sOpts.SpanSize, 10))
76+
}
77+
78+
if sOpts.MinLayerSize != -1 {
79+
sociCmd.Args = append(sociCmd.Args, "--min-layer-size", strconv.FormatInt(sOpts.MinLayerSize, 10))
80+
}
81+
82+
sociCmd.Args = append(sociCmd.Args, srcRef, destRef)
83+
84+
log.L.Infof("Converting image from %s to %s using SOCI format", srcRef, destRef)
85+
86+
err = processSociIO(sociCmd)
87+
if err != nil {
88+
return "", err
89+
}
90+
err = sociCmd.Wait()
91+
if err != nil {
92+
return "", err
93+
}
94+
95+
// Get the converted image's digest
96+
img, err := client.GetImage(ctx, destRef)
97+
if err != nil {
98+
return "", fmt.Errorf("failed to get converted image: %w", err)
99+
}
100+
101+
// Return the full reference with digest
102+
return fmt.Sprintf("%s@%s", destRef, img.Target().Digest), nil
103+
}
104+
105+
// CreateSociIndexV1 creates a SOCI index(`rawRef`)
106+
func CreateSociIndexV1(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string, sOpts types.SociOptions) error {
107+
sociCmd, err := setupSociCommand(gOpts)
108+
if err != nil {
109+
return err
110+
}
51111

52112
// Global flags have to be put before subcommand before soci upgrades to urfave v3.
53113
// https://github.com/urfave/cli/issues/1113
@@ -73,7 +133,7 @@ func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform boo
73133
// --timeout, --debug, --content-store
74134
sociCmd.Args = append(sociCmd.Args, rawRef)
75135

76-
log.L.Debugf("running %s %v", sociExecutable, sociCmd.Args)
136+
log.L.Debugf("running soci %v", sociCmd.Args)
77137

78138
err = processSociIO(sociCmd)
79139
if err != nil {
@@ -88,25 +148,11 @@ func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform boo
88148
func PushSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string) error {
89149
log.L.Debugf("pushing SOCI index: %s", rawRef)
90150

91-
sociExecutable, err := exec.LookPath("soci")
151+
sociCmd, err := setupSociCommand(gOpts)
92152
if err != nil {
93-
log.L.WithError(err).Error("soci executable not found in path $PATH")
94-
log.L.Info("you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md")
95153
return err
96154
}
97155

98-
sociCmd := exec.Command(sociExecutable)
99-
sociCmd.Env = os.Environ()
100-
101-
// #region for global flags.
102-
if gOpts.Address != "" {
103-
sociCmd.Args = append(sociCmd.Args, "--address", gOpts.Address)
104-
}
105-
if gOpts.Namespace != "" {
106-
sociCmd.Args = append(sociCmd.Args, "--namespace", gOpts.Namespace)
107-
}
108-
// #endregion
109-
110156
// Global flags have to be put before subcommand before soci upgrades to urfave v3.
111157
// https://github.com/urfave/cli/issues/1113
112158
sociCmd.Args = append(sociCmd.Args, "push")
@@ -131,7 +177,7 @@ func PushSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool,
131177
}
132178
sociCmd.Args = append(sociCmd.Args, rawRef)
133179

134-
log.L.Debugf("running %s %v", sociExecutable, sociCmd.Args)
180+
log.L.Debugf("running soci %v", sociCmd.Args)
135181

136182
err = processSociIO(sociCmd)
137183
if err != nil {

0 commit comments

Comments
 (0)