@@ -22,15 +22,63 @@ import (
2222 "fmt"
2323 "os"
2424 "os/exec"
25+ "regexp"
2526 "strconv"
2627 "strings"
2728
29+ "github.com/Masterminds/semver/v3"
2830 "github.com/containerd/containerd/v2/client"
2931 "github.com/containerd/log"
3032
3133 "github.com/containerd/nerdctl/v2/pkg/api/types"
3234)
3335
36+ // CheckSociVersion checks if the SOCI binary version is at least the required version
37+ // This function can be used by both production code and tests
38+ func CheckSociVersion (requiredVersion string ) error {
39+ sociExecutable , err := exec .LookPath ("soci" )
40+ if err != nil {
41+ log .L .WithError (err ).Error ("soci executable not found in path $PATH" )
42+ log .L .Info ("you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md" )
43+ return err
44+ }
45+
46+ cmd := exec .Command (sociExecutable , "--version" )
47+ output , err := cmd .CombinedOutput ()
48+ if err != nil {
49+ return fmt .Errorf ("failed to get SOCI version: %w" , err )
50+ }
51+
52+ // Parse the version string
53+ versionStr := string (output )
54+ // Handle format like "soci version v0.10.0 8bbfe951bbb411798ee85dbd908544df4a1619a8.m"
55+ re := regexp .MustCompile (`v?(\d+\.\d+\.\d+)` )
56+ matches := re .FindStringSubmatch (versionStr )
57+ if len (matches ) < 2 {
58+ return fmt .Errorf ("failed to parse SOCI version from output: %s" , versionStr )
59+ }
60+
61+ // Extract version number
62+ installedVersion := matches [1 ]
63+
64+ // Compare versions using semver
65+ v1 , err := semver .NewVersion (installedVersion )
66+ if err != nil {
67+ return fmt .Errorf ("failed to parse installed version %s: %v" , installedVersion , err )
68+ }
69+
70+ v2 , err := semver .NewVersion (requiredVersion )
71+ if err != nil {
72+ return fmt .Errorf ("failed to parse minimum required version %s: %v" , requiredVersion , err )
73+ }
74+
75+ if v1 .LessThan (v2 ) {
76+ return fmt .Errorf ("SOCI version %s is lower than the required version %s for the convert operation" , installedVersion , requiredVersion )
77+ }
78+
79+ return nil
80+ }
81+
3482// setupSociCommand creates and sets up a SOCI command with common configuration
3583func setupSociCommand (gOpts types.GlobalCommandOptions ) (* exec.Cmd , error ) {
3684 sociExecutable , err := exec .LookPath ("soci" )
@@ -56,6 +104,11 @@ func setupSociCommand(gOpts types.GlobalCommandOptions) (*exec.Cmd, error) {
56104
57105// ConvertSociIndexV2 converts an image to SOCI format and returns the converted image reference with digest
58106func ConvertSociIndexV2 (ctx context.Context , client * client.Client , srcRef string , destRef string , gOpts types.GlobalCommandOptions , platforms []string , sOpts types.SociOptions ) (string , error ) {
107+ // Check if SOCI version is at least 0.10.0 which is required for the convert operation
108+ if err := CheckSociVersion ("0.10.0" ); err != nil {
109+ return "" , err
110+ }
111+
59112 sociCmd , err := setupSociCommand (gOpts )
60113 if err != nil {
61114 return "" , err
0 commit comments