@@ -18,23 +18,26 @@ package snapshotterutil
18
18
19
19
import (
20
20
"bufio"
21
+ "context"
22
+ "fmt"
21
23
"os"
22
24
"os/exec"
23
25
"strconv"
24
26
"strings"
25
27
28
+ "github.com/containerd/containerd/v2/client"
26
29
"github.com/containerd/log"
27
30
28
31
"github.com/containerd/nerdctl/v2/pkg/api/types"
29
32
)
30
33
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 ) {
33
36
sociExecutable , err := exec .LookPath ("soci" )
34
37
if err != nil {
35
38
log .L .WithError (err ).Error ("soci executable not found in path $PATH" )
36
39
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
38
41
}
39
42
40
43
sociCmd := exec .Command (sociExecutable )
@@ -47,7 +50,77 @@ func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform boo
47
50
if gOpts .Namespace != "" {
48
51
sociCmd .Args = append (sociCmd .Args , "--namespace" , gOpts .Namespace )
49
52
}
50
- // #endregion
53
+
54
+ return sociCmd , nil
55
+ }
56
+
57
+ // ConvertSoci converts an image to SOCI format and returns the converted image reference with digest
58
+ func ConvertSoci (ctx context.Context , client * client.Client , srcRef string , destRef string , gOpts types.GlobalCommandOptions , platforms []string , sOpts types.SociOptions ) (string , error ) {
59
+ // Check minimum required version
60
+ // const minRequiredVersion = "0.9.0"
61
+ // if err := checkSociVersion(minRequiredVersion); err != nil {
62
+ // return err
63
+ // }
64
+
65
+ sociCmd , err := setupSociCommand (gOpts )
66
+ if err != nil {
67
+ return "" , err
68
+ }
69
+
70
+ // TODO: Implement conversion logic
71
+ sociCmd .Args = append (sociCmd .Args , "convert" )
72
+
73
+ if len (platforms ) > 0 {
74
+ // multiple values need to be passed as separate, repeating flags in soci as it uses urfave
75
+ // https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag
76
+ for _ , p := range platforms {
77
+ sociCmd .Args = append (sociCmd .Args , "--platform" , p )
78
+ }
79
+ }
80
+
81
+ if sOpts .SpanSize != - 1 {
82
+ sociCmd .Args = append (sociCmd .Args , "--span-size" , strconv .FormatInt (sOpts .SpanSize , 10 ))
83
+ }
84
+
85
+ if sOpts .MinLayerSize != - 1 {
86
+ sociCmd .Args = append (sociCmd .Args , "--min-layer-size" , strconv .FormatInt (sOpts .MinLayerSize , 10 ))
87
+ }
88
+
89
+ sociCmd .Args = append (sociCmd .Args , srcRef , destRef )
90
+
91
+ log .L .Infof ("Converting image from %s to %s using SOCI format" , srcRef , destRef )
92
+
93
+ err = processSociIO (sociCmd )
94
+ if err != nil {
95
+ return "" , err
96
+ }
97
+ err = sociCmd .Wait ()
98
+ if err != nil {
99
+ return "" , err
100
+ }
101
+
102
+ // Get the converted image's digest
103
+ img , err := client .GetImage (ctx , destRef )
104
+ if err != nil {
105
+ return "" , fmt .Errorf ("failed to get converted image: %w" , err )
106
+ }
107
+
108
+ // Return the full reference with digest
109
+ return fmt .Sprintf ("%s@%s" , destRef , img .Target ().Digest ), nil
110
+ }
111
+
112
+ // CreateSoci creates a SOCI index(`rawRef`)
113
+ func CreateSoci (rawRef string , gOpts types.GlobalCommandOptions , allPlatform bool , platforms []string , sOpts types.SociOptions ) error {
114
+ // Check minimum required version
115
+ // const minRequiredVersion = "0.9.0"
116
+ // if err := checkSociVersion(minRequiredVersion); err != nil {
117
+ // return err
118
+ // }
119
+
120
+ sociCmd , err := setupSociCommand (gOpts )
121
+ if err != nil {
122
+ return err
123
+ }
51
124
52
125
// Global flags have to be put before subcommand before soci upgrades to urfave v3.
53
126
// https://github.com/urfave/cli/issues/1113
@@ -73,7 +146,7 @@ func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform boo
73
146
// --timeout, --debug, --content-store
74
147
sociCmd .Args = append (sociCmd .Args , rawRef )
75
148
76
- log .L .Debugf ("running %s %v" , sociExecutable , sociCmd .Args )
149
+ log .L .Debugf ("running soci %v" , sociCmd .Args )
77
150
78
151
err = processSociIO (sociCmd )
79
152
if err != nil {
@@ -88,25 +161,11 @@ func CreateSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform boo
88
161
func PushSoci (rawRef string , gOpts types.GlobalCommandOptions , allPlatform bool , platforms []string ) error {
89
162
log .L .Debugf ("pushing SOCI index: %s" , rawRef )
90
163
91
- sociExecutable , err := exec . LookPath ( "soci" )
164
+ sociCmd , err := setupSociCommand ( gOpts )
92
165
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" )
95
166
return err
96
167
}
97
168
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
-
110
169
// Global flags have to be put before subcommand before soci upgrades to urfave v3.
111
170
// https://github.com/urfave/cli/issues/1113
112
171
sociCmd .Args = append (sociCmd .Args , "push" )
@@ -131,7 +190,7 @@ func PushSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool,
131
190
}
132
191
sociCmd .Args = append (sociCmd .Args , rawRef )
133
192
134
- log .L .Debugf ("running %s %v" , sociExecutable , sociCmd .Args )
193
+ log .L .Debugf ("running soci %v" , sociCmd .Args )
135
194
136
195
err = processSociIO (sociCmd )
137
196
if err != nil {
@@ -140,6 +199,55 @@ func PushSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool,
140
199
return sociCmd .Wait ()
141
200
}
142
201
202
+ // checkSociVersion checks if the installed SOCI version meets the minimum required version
203
+ // func checkSociVersion(minVersion string) error {
204
+ // sociExecutable, err := exec.LookPath("soci")
205
+ // if err != nil {
206
+ // log.L.WithError(err).Error("soci executable not found in path $PATH")
207
+ // log.L.Info("you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md")
208
+ // return err
209
+ // }
210
+
211
+ // cmd := exec.Command(sociExecutable, "--version")
212
+ // output, err := cmd.Output()
213
+ // if err != nil {
214
+ // return fmt.Errorf("failed to get soci version: %w", err)
215
+ // }
216
+
217
+ // // Parse version from output
218
+ // // Example output format: "soci version v0.9.0 737f61a3db40c386f997c1f126344158aa3ad43c"
219
+ // versionStr := strings.TrimSpace(string(output))
220
+ // parts := strings.Fields(versionStr)
221
+ // if len(parts) < 3 {
222
+ // return fmt.Errorf("unexpected soci version output format: %s", versionStr)
223
+ // }
224
+
225
+ // // Extract version number without 'v' prefix
226
+ // installedVersion := strings.TrimPrefix(parts[2], "v")
227
+
228
+ // // Compare versions
229
+ // v1, err := semver.NewVersion(installedVersion)
230
+ // if err != nil {
231
+ // return fmt.Errorf("failed to parse installed version %s: %w", installedVersion, err)
232
+ // }
233
+
234
+ // v2, err := semver.NewVersion(minVersion)
235
+ // if err != nil {
236
+ // return fmt.Errorf("failed to parse minimum required version %s: %w", minVersion, err)
237
+ // }
238
+
239
+ // if v1.LessThan(v2) {
240
+ // return fmt.Errorf("installed soci version %s is older than required version %s", installedVersion, minVersion)
241
+ // }
242
+
243
+ // // Log the full version info including commit hash for debugging purposes
244
+ // if len(parts) > 3 {
245
+ // log.L.Debugf("soci version: %s (commit: %s)", installedVersion, parts[3])
246
+ // }
247
+
248
+ // return nil
249
+ // }
250
+
143
251
func processSociIO (sociCmd * exec.Cmd ) error {
144
252
stdout , err := sociCmd .StdoutPipe ()
145
253
if err != nil {
0 commit comments