package podman import ( "bytes" "context" "os" "os/exec" "path/filepath" "reflect" "strings" "testing" "time" ) func TestCLIUsesSocketAsContainerHost(t *testing.T) { dir := t.TempDir() bin := filepath.Join(dir, "podman") shell, err := exec.LookPath("sh") if err != nil { t.Fatal(err) } if err := os.WriteFile(bin, []byte("#!"+shell+"\nprintf '%s' \"$CONTAINER_HOST\"\n"), 0o755); err != nil { t.Fatal(err) } t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) var stdout bytes.Buffer if err := (CLI{Socket: "unix:///tmp/podman.sock"}).Run(context.Background(), []string{"version"}, &stdout, &stdout); err != nil { t.Fatal(err) } if stdout.String() != "unix:///tmp/podman.sock" { t.Fatalf("CONTAINER_HOST = %q", stdout.String()) } } func TestStepScriptMarksAndTimesSteps(t *testing.T) { script := StepScript([]string{"echo first", "echo 'it works'"}) for _, want := range []string{ "TOTAL=2", "step 'echo first'", `step 'echo '\''it works'\'''`, "[luci] step %d/%d", } { if !strings.Contains(script, want) { t.Fatalf("script missing %q:\n%s", want, script) } } if strings.Contains(script, "step 'echo 'it works''") { t.Fatal("unescaped quote broke script") } } func TestStepScriptShell(t *testing.T) { if testing.Short() { t.Skip("shell round trip") } script := StepScript([]string{"X=set", "test \"$X\" = set", "true"}) cmd := exec.Command("/bin/sh", "-c", script) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("err=%v output=%s", err, output) } if !strings.Contains(string(output), "[luci] step 3/3 done in 0s") { t.Fatalf("output=%s", output) } failing := StepScript([]string{"false"}) cmd = exec.Command("/bin/sh", "-c", failing) output, err = cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), "[luci] step 1/1 failed after") { t.Fatalf("err=%v output=%s", err, output) } } func TestBuildArgsUsesBaselineIsolation(t *testing.T) { got := BuildArgs(Spec{ Image: "docker.io/library/alpine:latest", Workspace: "/tmp/work", Memory: "2g", CPUs: "2", Env: map[string]string{"B": "2", "A": "1"}, Commands: []string{"echo ok"}, Timeout: time.Minute, }) want := []string{ "run", "--rm", "--pull=always", "--userns=keep-id", "--cap-drop=all", "--security-opt=no-new-privileges", "--pids-limit=1024", "--memory=2g", "--cpus=2", "--timeout=60", "--workdir=/work", "--volume", "/tmp/work:/work:Z", "--env", "A", "--env", "B", "--", "docker.io/library/alpine:latest", "/bin/sh", "-c", StepScript([]string{"echo ok"}), } if !reflect.DeepEqual(got, want) { t.Fatalf("args = %#v", got) } if strings.Contains(strings.Join(got, "\x00"), "A=1") || strings.Contains(strings.Join(got, "\x00"), "B=2") { t.Fatalf("secret env value leaked into argv: %#v", got) } } func TestBuildArgsUseDocumentedResourceIsolationAndNoSecrets(t *testing.T) { args, err := BuildArgsForContainerfile(BuildSpec{Containerfile: "/stage/Containerfile", Context: "/stage/context", Tag: "luci-build-42-001", IIDFile: "/stage/iid", AuthFile: "/stage/auth.json", Memory: "2g", CPUs: "1.5"}) if err != nil { t.Fatal(err) } for _, want := range []string{"--pull=always", "--layers", "--network=private", "--http-proxy=false", "--memory=2g", "--cpu-period=100000", "--cpu-quota=150000", "--ulimit=nproc=1024:1024", "--security-opt=no-new-privileges", "--label=" + BuildCacheLabel, "--layer-label=" + BuildCacheLabel, "--iidfile=/stage/iid", "--authfile=/stage/auth.json", "--file=/stage/Containerfile", "/stage/context"} { if !contains(args, want) { t.Fatalf("missing %q in %#v", want, args) } } for _, forbidden := range []string{"--volume", "--secret", "--ssh", "--authfile", "--env"} { if contains(args, forbidden) { t.Fatalf("forbidden %q in %#v", forbidden, args) } } if _, err := BuildArgsForContainerfile(BuildSpec{CPUs: "no"}); err == nil { t.Fatal("invalid CPU value accepted") } } func contains(values []string, want string) bool { for _, value := range values { if value == want { return true } } return false } func TestEnvironmentPassesValuesOnlyThroughChildEnvironment(t *testing.T) { dir := t.TempDir() bin := filepath.Join(dir, "podman") shell, err := exec.LookPath("sh") if err != nil { t.Fatal(err) } if err := os.WriteFile(bin, []byte("#!"+shell+"\nprintf '%s' \"$TOKEN\"\n"), 0o755); err != nil { t.Fatal(err) } t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) spec := Spec{Env: map[string]string{"TOKEN": "secret-value"}} args := BuildArgs(spec) if strings.Contains(strings.Join(args, "\x00"), "secret-value") { t.Fatalf("secret env value leaked into argv: %#v", args) } var stdout bytes.Buffer if err := (CLI{}).RunWithEnv(context.Background(), args, Environment(spec), &stdout, &stdout); err != nil { t.Fatal(err) } if stdout.String() != "secret-value" { t.Fatalf("child TOKEN = %q", stdout.String()) } }