Add docker-args flag to pass arguments to docker create command
This change allows users to pass additional arguments to the docker create command,
which can be useful for customizing the container environment (e.g. adding volumes,
setting resource limits, etc).
Co-Authored-By: sketch <hello@sketch.dev>
Update docker-args flag to handle space-separated arguments with escaping
- Created parseDockerArgs function to handle space-separated arguments with quotes and escaping
- Added comprehensive tests for argument parsing
- Updated CLI help text to reflect the new format
Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index 0a070cf..5e49392 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -91,6 +91,9 @@
// Verbose enables verbose output
Verbose bool
+
+ // DockerArgs are additional arguments to pass to the docker create command
+ DockerArgs string
}
// LaunchContainer creates a docker container for a project, installs sketch and opens a connection to it.
@@ -472,6 +475,17 @@
if config.OneShot {
cmdArgs = append(cmdArgs, "-one-shot")
}
+
+ // Add additional docker arguments if provided
+ if config.DockerArgs != "" {
+ // Parse space-separated docker arguments with support for quotes and escaping
+ args := parseDockerArgs(config.DockerArgs)
+ // Insert arguments after "create" but before other arguments
+ for i := len(args) - 1; i >= 0; i-- {
+ cmdArgs = append(cmdArgs[:1], append([]string{args[i]}, cmdArgs[1:]...)...)
+ }
+ }
+
if out, err := combinedOutput(ctx, "docker", cmdArgs...); err != nil {
return fmt.Errorf("docker create: %s, %w", out, err)
}
@@ -800,3 +814,69 @@
}
return envVars
}
+
+// parseDockerArgs parses a string containing space-separated Docker arguments into an array of strings.
+// It handles quoted arguments and escaped characters.
+//
+// Examples:
+//
+// --memory=2g --cpus=2 -> ["--memory=2g", "--cpus=2"]
+// --label="my label" --env=FOO=bar -> ["--label=my label", "--env=FOO=bar"]
+// --env="KEY=\"quoted value\"" -> ["--env=KEY=\"quoted value\""]
+func parseDockerArgs(args string) []string {
+ if args = strings.TrimSpace(args); args == "" {
+ return []string{}
+ }
+
+ var result []string
+ var current strings.Builder
+ inQuotes := false
+ escapeNext := false
+ quoteChar := rune(0)
+
+ for _, char := range args {
+ if escapeNext {
+ current.WriteRune(char)
+ escapeNext = false
+ continue
+ }
+
+ if char == '\\' {
+ escapeNext = true
+ continue
+ }
+
+ if char == '"' || char == '\'' {
+ if !inQuotes {
+ inQuotes = true
+ quoteChar = char
+ continue
+ } else if char == quoteChar {
+ inQuotes = false
+ quoteChar = rune(0)
+ continue
+ }
+ // Non-matching quote character inside quotes
+ current.WriteRune(char)
+ continue
+ }
+
+ // Space outside of quotes is an argument separator
+ if char == ' ' && !inQuotes {
+ if current.Len() > 0 {
+ result = append(result, current.String())
+ current.Reset()
+ }
+ continue
+ }
+
+ current.WriteRune(char)
+ }
+
+ // Add the last argument if there is one
+ if current.Len() > 0 {
+ result = append(result, current.String())
+ }
+
+ return result
+}
diff --git a/dockerimg/parse_args_test.go b/dockerimg/parse_args_test.go
new file mode 100644
index 0000000..7badcb7
--- /dev/null
+++ b/dockerimg/parse_args_test.go
@@ -0,0 +1,69 @@
+package dockerimg
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestParseDockerArgs(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected []string
+ }{
+ {
+ name: "empty string",
+ input: "",
+ expected: []string{},
+ },
+ {
+ name: "single argument",
+ input: "--memory=2g",
+ expected: []string{"--memory=2g"},
+ },
+ {
+ name: "multiple arguments",
+ input: "--memory=2g --cpus=2",
+ expected: []string{"--memory=2g", "--cpus=2"},
+ },
+ {
+ name: "arguments with double quotes",
+ input: "--label=\"my label\" --env=FOO=bar",
+ expected: []string{"--label=my label", "--env=FOO=bar"},
+ },
+ {
+ name: "arguments with single quotes",
+ input: "--label='my label' --env=FOO=bar",
+ expected: []string{"--label=my label", "--env=FOO=bar"},
+ },
+ {
+ name: "nested quotes",
+ input: "--env=\"KEY=\\\"quoted value\\\"\"",
+ expected: []string{"--env=KEY=\"quoted value\""},
+ },
+ {
+ name: "mixed quotes",
+ input: "--env=\"mixed 'quotes'\" --label='single \"quotes\"'",
+ expected: []string{"--env=mixed 'quotes'", "--label=single \"quotes\""},
+ },
+ {
+ name: "escaped spaces",
+ input: "--label=my\\ label --env=FOO=bar",
+ expected: []string{"--label=my label", "--env=FOO=bar"},
+ },
+ {
+ name: "multiple spaces",
+ input: " --memory=2g --cpus=2 ",
+ expected: []string{"--memory=2g", "--cpus=2"},
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result := parseDockerArgs(tc.input)
+ if !reflect.DeepEqual(result, tc.expected) {
+ t.Errorf("Expected %v, got %v", tc.expected, result)
+ }
+ })
+ }
+}