package cli import ( "archive/tar" "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" ) // TestPullThroughFakeCrane runs pull against a crane stand-in that // records its arguments and writes a one-layer docker save tarball. func TestPullThroughFakeCrane(t *testing.T) { home := t.TempDir() t.Setenv("WERKDOCK_HOME", home) tarball := filepath.Join(home, "fixture.tar") if err := os.WriteFile(tarball, dockerSaveTarball(t), 0o644); err != nil { t.Fatal(err) } argsFile := filepath.Join(home, "crane.args") fake := filepath.Join(home, "crane") script := "#!/bin/sh\nprintf '%s\\n' \"$@\" > " + argsFile + "\ncp " + tarball + " \"$5\"\n" if err := os.WriteFile(fake, []byte(script), 0o755); err != nil { t.Fatal(err) } t.Setenv("WERKDOCK_CRANE", fake) if code := pullCmd([]string{"--platform", "linux/arm64", "example.org:5000/team/tool:1.0"}); code != 0 { t.Fatalf("pull exited %d", code) } args, _ := os.ReadFile(argsFile) want := "pull\n--platform\nlinux/arm64\nexample.org:5000/team/tool:1.0\n" if !strings.HasPrefix(string(args), want) { t.Errorf("crane args:\n%s", args) } if _, err := os.Stat(filepath.Join(home, "images", "example.org:5000", "team", "tool", "1.0", "rootfs", "etc", "hello")); err != nil { t.Errorf("rootfs not placed: %v", err) } if leftovers, _ := filepath.Glob(filepath.Join(home, ".pull-*")); len(leftovers) != 0 { t.Errorf("staging tarball left behind: %v", leftovers) } if code := pullCmd([]string{"example.org:5000/team/tool:1.0"}); code != exitCLIError { t.Errorf("second pull of an existing image: exited %d, want %d", code, exitCLIError) } } func TestPullReportsCraneFailure(t *testing.T) { home := t.TempDir() t.Setenv("WERKDOCK_HOME", home) fake := filepath.Join(home, "crane") if err := os.WriteFile(fake, []byte("#!/bin/sh\nexit 1\n"), 0o755); err != nil { t.Fatal(err) } t.Setenv("WERKDOCK_CRANE", fake) if code := pullCmd([]string{"busybox"}); code != exitCLIError { t.Errorf("exited %d, want %d", code, exitCLIError) } if leftovers, _ := filepath.Glob(filepath.Join(home, ".pull-*")); len(leftovers) != 0 { t.Errorf("staging tarball left behind: %v", leftovers) } } func TestPullArguments(t *testing.T) { for _, args := range [][]string{{}, {"a", "b"}, {"Upper:1"}, {"busybox@sha256:0000"}} { if code := pullCmd(args); code != exitCLIError { t.Errorf("pull %q: exited %d, want %d", args, code, exitCLIError) } } } // dockerSaveTarball builds the archive `crane pull` (format tarball) // writes: manifest.json, a config blob, one gzip-less layer. func dockerSaveTarball(t *testing.T) []byte { t.Helper() var layer bytes.Buffer lw := tar.NewWriter(&layer) for _, h := range []tar.Header{ {Name: "etc/", Typeflag: tar.TypeDir, Mode: 0o755}, {Name: "etc/hello", Typeflag: tar.TypeReg, Mode: 0o644, Size: 6}, } { if err := lw.WriteHeader(&h); err != nil { t.Fatal(err) } if h.Typeflag == tar.TypeReg { lw.Write([]byte("hello\n")) } } lw.Close() config, _ := json.Marshal(map[string]any{ "architecture": "arm64", "os": "linux", "config": map[string]any{"Cmd": []string{"/bin/sh"}}, }) manifest, _ := json.Marshal([]map[string]any{{ "Config": "config.json", "RepoTags": []string{"example.org:5000/team/tool:1.0"}, "Layers": []string{"layer.tar"}, }}) var out bytes.Buffer tw := tar.NewWriter(&out) for name, body := range map[string][]byte{"manifest.json": manifest, "config.json": config, "layer.tar": layer.Bytes()} { if err := tw.WriteHeader(&tar.Header{Name: name, Mode: 0o644, Size: int64(len(body))}); err != nil { t.Fatal(err) } tw.Write(body) } tw.Close() return out.Bytes() } func TestRmiContinuesPastMissingImages(t *testing.T) { home := t.TempDir() t.Setenv("WERKDOCK_HOME", home) for _, rel := range []string{"a/latest", "b/1"} { dir := filepath.Join(home, "images", rel) os.MkdirAll(filepath.Join(dir, "rootfs"), 0o755) os.WriteFile(filepath.Join(dir, "image.json"), []byte("{}\n"), 0o644) } if code := rmiCmd([]string{"a", "nosuch", "b:1"}); code != exitCLIError { t.Errorf("exited %d, want %d (one of three failed)", code, exitCLIError) } if entries, _ := os.ReadDir(filepath.Join(home, "images")); len(entries) != 0 { t.Errorf("images left: %v", entries) } if code := rmiCmd(nil); code != exitCLIError { t.Errorf("no argument: exited %d", code) } }