@@ -6,7 +6,9 @@ package main
6
6
import (
7
7
"bufio"
8
8
"fmt"
9
+ "os"
9
10
"path/filepath"
11
+ "sort"
10
12
"strings"
11
13
12
14
dockerops "github.com/docker/docker/opts"
@@ -85,8 +87,12 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
85
87
var (
86
88
nerdctlArgs , envs , fileEnvs []string
87
89
skip bool
90
+ volumes []string
88
91
)
89
92
93
+ dockerCompat := isDockerCompatEnvSet (nc .systemDeps )
94
+ firstVolumeIndex := - 1
95
+
90
96
for i , arg := range args {
91
97
// parsing environment values from the command line may pre-fetch and
92
98
// consume the next argument; this loop variable will skip these pre-consumed
@@ -121,10 +127,34 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
121
127
arg = fmt .Sprintf ("%s%s" , arg [0 :11 ], resolvedIP )
122
128
}
123
129
nerdctlArgs = append (nerdctlArgs , arg )
130
+ case strings .HasPrefix (arg , "-v" ) || strings .HasPrefix (arg , "--volume" ):
131
+ // TODO: remove the case branch when the Nerdctl fix is released to Finch.
132
+ // Nerdctl tracking issue: https://github.com/containerd/nerdctl/issues/2254.
133
+ if dockerCompat {
134
+ if firstVolumeIndex == - 1 {
135
+ firstVolumeIndex = i
136
+ }
137
+ volume , shouldSkip := getVolume (arg , args [i + 1 ])
138
+ volumes = append (volumes , volume )
139
+ skip = shouldSkip
140
+ } else {
141
+ nerdctlArgs = append (nerdctlArgs , arg )
142
+ }
124
143
default :
125
144
nerdctlArgs = append (nerdctlArgs , arg )
126
145
}
127
146
}
147
+
148
+ if dockerCompat {
149
+ // Need to insert to the middle to prevent messing up the subcommands at the beginning and the image ref at the end.
150
+ // The first volume index is a good middle position.
151
+ volumes = sortVolumes (volumes )
152
+ for i , vol := range volumes {
153
+ position := firstVolumeIndex + i * 2
154
+ nerdctlArgs = append (nerdctlArgs [:position ], append ([]string {"-v" , vol }, nerdctlArgs [position :]... )... )
155
+ }
156
+ }
157
+
128
158
// to handle environment variables properly, we add all entries found via
129
159
// env-file includes to the map first and then all command line environment
130
160
// flags, making sure that command line overrides environment file options,
@@ -300,6 +330,20 @@ func handleEnvFile(fs afero.Fs, systemDeps NerdctlCommandSystemDeps, arg, arg2 s
300
330
return skip , envs , nil
301
331
}
302
332
333
+ // getVolume extracts the volume string from a command line argument.
334
+ // It handles both "--volume=" and "-v=" prefixes.
335
+ // The function returns the extracted volume string and a boolean value indicating whether the volume was found in the next argument.
336
+ // If the volume string was found in the provided 'arg' string (with "--volume=" or "-v=" prefix), it returns the volume and 'false'.
337
+ // If the volume string wasn't in the 'arg', it assumes it's in the 'nextArg' string and returns 'nextArg' and 'true'.
338
+ func getVolume (arg , nextArg string ) (string , bool ) {
339
+ for _ , prefix := range []string {"--volume=" , "-v=" } {
340
+ if strings .HasPrefix (arg , prefix ) {
341
+ return arg [len (prefix ):], false
342
+ }
343
+ }
344
+ return nextArg , true
345
+ }
346
+
303
347
func resolveIP (host string , logger flog.Logger ) string {
304
348
parts := strings .SplitN (host , ":" , 2 )
305
349
// If the IP Address is a string called "host-gateway", replace this value with the IP address that can be used to
@@ -313,6 +357,50 @@ func resolveIP(host string, logger flog.Logger) string {
313
357
return host
314
358
}
315
359
360
+ func sortVolumes (volumes []string ) []string {
361
+ type volume struct {
362
+ original string
363
+ destination string
364
+ }
365
+
366
+ volumeSlices := make ([]volume , 0 )
367
+ for _ , vol := range volumes {
368
+ splitVol := strings .Split (vol , ":" )
369
+ if len (splitVol ) >= 2 {
370
+ volumeSlices = append (volumeSlices , volume {
371
+ original : vol ,
372
+ destination : splitVol [1 ],
373
+ })
374
+ } else {
375
+ // Still need to put the volume string back if it is in other format.
376
+ volumeSlices = append (volumeSlices , volume {
377
+ original : vol ,
378
+ destination : "" ,
379
+ })
380
+ }
381
+ }
382
+ sort .Slice (volumeSlices , func (i , j int ) bool {
383
+ // Consistent with the less function in Docker.
384
+ // https://github.com/moby/moby/blob/0db417451313474133c5ed62bbf95e2d3c92444d/daemon/volumes.go#L45
385
+ c1 := strings .Count (filepath .Clean (volumeSlices [i ].destination ), string (os .PathSeparator ))
386
+ c2 := strings .Count (filepath .Clean (volumeSlices [j ].destination ), string (os .PathSeparator ))
387
+ return c1 < c2
388
+ })
389
+ sortedVolumes := make ([]string , 0 )
390
+ for _ , vol := range volumeSlices {
391
+ sortedVolumes = append (sortedVolumes , vol .original )
392
+ }
393
+ return sortedVolumes
394
+ }
395
+
396
+ // isDockerCompatEnvSet checks if the FINCH_DOCKER_COMPAT environment variable is set, so that callers can
397
+ // modify behavior to match docker instead of nerdctl.
398
+ // Intended to be used for temporary workarounds until changes are merged upstream.
399
+ func isDockerCompatEnvSet (systemDeps NerdctlCommandSystemDeps ) bool {
400
+ _ , s := systemDeps .LookupEnv ("FINCH_DOCKER_COMPAT" )
401
+ return s
402
+ }
403
+
316
404
var nerdctlCmds = map [string ]string {
317
405
"build" : "Build an image from Dockerfile" ,
318
406
"builder" : "Manage builds" ,
0 commit comments