Skip to content

Commit 9030dba

Browse files
test(actions/docker): unit coverage for runArgs parser and overlays
Covers: - nil and empty input return nil, nil - every supported flag parses into the expected field, including repeated -v/--volume, -e/--env, --cap-add, --cap-drop - unknown bare flags (e.g. --rm) are rejected with a clear message - non-flag bare values are rejected with the space-separated hint - empty/whitespace entries are skipped - ApplyToContainerConfig is a no-op on nil receiver or nil cfg - ApplyToContainerConfig sets User and appends Env preserving existing - ApplyToHostConfig is a no-op on nil receiver or nil hc - ApplyToHostConfig overrides NetworkMode only when provided, appends Binds / ExtraHosts / CapAdd / CapDrop, unions Tmpfs, and honours Privileged=true
1 parent d46cd70 commit 9030dba

2 files changed

Lines changed: 146 additions & 0 deletions

File tree

pkg/skaffold/actions/docker/runargs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ func ParseRunArgs(args []string) (*RunArgs, error) {
7373
}
7474
key, val, ok := strings.Cut(arg, "=")
7575
if !ok {
76+
// No '=' means either an unknown bare flag or the space-separated form.
77+
if strings.HasPrefix(arg, "--") || strings.HasPrefix(arg, "-") {
78+
return nil, fmt.Errorf("runArgs[%d] %q: unsupported flag %q (only --flag=value form is supported; allowed: --network, -v/--volume, -e/--env, --user, --add-host, --tmpfs, --privileged, --cap-add, --cap-drop)", i, raw, arg)
79+
}
7680
return nil, fmt.Errorf("runArgs[%d] %q: only --flag=value form is supported (no space-separated values)", i, raw)
7781
}
7882
switch key {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
Copyright 2026 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package docker
18+
19+
import (
20+
"strings"
21+
"testing"
22+
23+
"github.com/docker/docker/api/types/container"
24+
25+
"github.com/GoogleContainerTools/skaffold/v2/testutil"
26+
)
27+
28+
func TestParseRunArgs_Empty(t *testing.T) {
29+
got, err := ParseRunArgs(nil)
30+
testutil.CheckError(t, false, err)
31+
if got != nil {
32+
t.Fatalf("expected nil, got %+v", got)
33+
}
34+
35+
got, err = ParseRunArgs([]string{})
36+
testutil.CheckError(t, false, err)
37+
if got != nil {
38+
t.Fatalf("expected nil, got %+v", got)
39+
}
40+
}
41+
42+
func TestParseRunArgs_Supported(t *testing.T) {
43+
got, err := ParseRunArgs([]string{
44+
"--network=host",
45+
"-v=/host:/container:ro",
46+
"--volume=/data:/data",
47+
"-e=FOO=bar",
48+
"--env=BAZ=qux",
49+
"--user=1000:1000",
50+
"--add-host=db:127.0.0.1",
51+
"--tmpfs=/tmp:size=64m",
52+
"--privileged",
53+
"--cap-add=NET_ADMIN",
54+
"--cap-drop=AUDIT_WRITE",
55+
})
56+
testutil.CheckError(t, false, err)
57+
testutil.CheckDeepEqual(t, "host", got.NetworkMode)
58+
testutil.CheckDeepEqual(t, []string{"/host:/container:ro", "/data:/data"}, got.Binds)
59+
testutil.CheckDeepEqual(t, []string{"FOO=bar", "BAZ=qux"}, got.Env)
60+
testutil.CheckDeepEqual(t, "1000:1000", got.User)
61+
testutil.CheckDeepEqual(t, []string{"db:127.0.0.1"}, got.ExtraHosts)
62+
testutil.CheckDeepEqual(t, map[string]string{"/tmp": "size=64m"}, got.Tmpfs)
63+
testutil.CheckDeepEqual(t, true, got.Privileged)
64+
testutil.CheckDeepEqual(t, []string{"NET_ADMIN"}, got.CapAdd)
65+
testutil.CheckDeepEqual(t, []string{"AUDIT_WRITE"}, got.CapDrop)
66+
}
67+
68+
func TestParseRunArgs_UnsupportedFlag(t *testing.T) {
69+
_, err := ParseRunArgs([]string{"--rm"})
70+
if err == nil || !strings.Contains(err.Error(), "unsupported flag") {
71+
t.Fatalf("expected unsupported flag error, got %v", err)
72+
}
73+
}
74+
75+
func TestParseRunArgs_SpaceSeparated(t *testing.T) {
76+
_, err := ParseRunArgs([]string{"plain value"})
77+
if err == nil || !strings.Contains(err.Error(), "only --flag=value form") {
78+
t.Fatalf("expected only --flag=value error, got %v", err)
79+
}
80+
}
81+
82+
func TestParseRunArgs_SkipsEmptyEntries(t *testing.T) {
83+
got, err := ParseRunArgs([]string{"", " ", "--network=host"})
84+
testutil.CheckError(t, false, err)
85+
testutil.CheckDeepEqual(t, "host", got.NetworkMode)
86+
}
87+
88+
func TestApplyToContainerConfig_NilSafe(t *testing.T) {
89+
var r *RunArgs
90+
r.ApplyToContainerConfig(nil) // nil config, nil receiver
91+
r.ApplyToContainerConfig(&container.Config{})
92+
(&RunArgs{}).ApplyToContainerConfig(nil)
93+
}
94+
95+
func TestApplyToContainerConfig_SetsUserAndAppendsEnv(t *testing.T) {
96+
r := &RunArgs{User: "1000", Env: []string{"A=1", "B=2"}}
97+
cfg := &container.Config{Env: []string{"PRE=existing"}}
98+
r.ApplyToContainerConfig(cfg)
99+
testutil.CheckDeepEqual(t, "1000", cfg.User)
100+
testutil.CheckDeepEqual(t, []string{"PRE=existing", "A=1", "B=2"}, cfg.Env)
101+
}
102+
103+
func TestApplyToHostConfig_NilSafe(t *testing.T) {
104+
var r *RunArgs
105+
r.ApplyToHostConfig(nil)
106+
r.ApplyToHostConfig(&container.HostConfig{})
107+
(&RunArgs{}).ApplyToHostConfig(nil)
108+
}
109+
110+
func TestApplyToHostConfig_OverridesAndAppends(t *testing.T) {
111+
r := &RunArgs{
112+
NetworkMode: "host",
113+
Binds: []string{"/a:/a"},
114+
ExtraHosts: []string{"h:1.2.3.4"},
115+
Tmpfs: map[string]string{"/tmp": "size=16m"},
116+
Privileged: true,
117+
CapAdd: []string{"NET_ADMIN"},
118+
CapDrop: []string{"AUDIT_WRITE"},
119+
}
120+
hc := &container.HostConfig{
121+
NetworkMode: container.NetworkMode("bridge"),
122+
Binds: []string{"/pre:/pre"},
123+
ExtraHosts: []string{"pre:0.0.0.0"},
124+
Tmpfs: map[string]string{"/run": "size=8m"},
125+
CapAdd: []string{"SYS_TIME"},
126+
}
127+
r.ApplyToHostConfig(hc)
128+
testutil.CheckDeepEqual(t, "host", string(hc.NetworkMode))
129+
testutil.CheckDeepEqual(t, []string{"/pre:/pre", "/a:/a"}, hc.Binds)
130+
testutil.CheckDeepEqual(t, []string{"pre:0.0.0.0", "h:1.2.3.4"}, hc.ExtraHosts)
131+
testutil.CheckDeepEqual(t, map[string]string{"/run": "size=8m", "/tmp": "size=16m"}, hc.Tmpfs)
132+
testutil.CheckDeepEqual(t, true, hc.Privileged)
133+
testutil.CheckDeepEqual(t, []string{"SYS_TIME", "NET_ADMIN"}, []string(hc.CapAdd))
134+
testutil.CheckDeepEqual(t, []string{"AUDIT_WRITE"}, []string(hc.CapDrop))
135+
}
136+
137+
func TestApplyToHostConfig_EmptyRunArgsDoesNotOverrideNetwork(t *testing.T) {
138+
r := &RunArgs{}
139+
hc := &container.HostConfig{NetworkMode: container.NetworkMode("bridge")}
140+
r.ApplyToHostConfig(hc)
141+
testutil.CheckDeepEqual(t, "bridge", string(hc.NetworkMode))
142+
}

0 commit comments

Comments
 (0)