Session C groundwork: Werkator's git-metadata mask needs a tmpfs BETWEEN binds (ro-bind .git, tmpfs .git/werkator, bind workspace), so -v and --tmpfs now collect into one ordered mount list and RunSpec carries Mounts instead of Binds. --tmpfs DEST is the docker flag of the same name. `werkdock images` lists loaded image names one per line, so a consumer can check existence through the CLI. -v accepts the explicit :rw docker default instead of refusing it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
150 lines
4.3 KiB
Go
150 lines
4.3 KiB
Go
package store
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// writeTestArchive builds a minimal rootfs .tar.gz with the stdlib, so
|
|
// the tests need no zstd; Load unpacks it with the system tar.
|
|
func writeTestArchive(t *testing.T, path string) {
|
|
t.Helper()
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gz := gzip.NewWriter(f)
|
|
tw := tar.NewWriter(gz)
|
|
if err := tw.WriteHeader(&tar.Header{Name: "etc/", Mode: 0o755, Typeflag: tar.TypeDir}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
content := []byte("hello from the rootfs\n")
|
|
if err := tw.WriteHeader(&tar.Header{Name: "etc/hello", Mode: 0o644, Size: int64(len(content))}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write(content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, c := range []interface{ Close() error }{tw, gz, f} {
|
|
if err := c.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadUnpacksArchiveIntoTheStore(t *testing.T) {
|
|
if _, err := exec.LookPath("tar"); err != nil {
|
|
t.Skip("tar not installed")
|
|
}
|
|
st := Store{Root: t.TempDir()}
|
|
archive := filepath.Join(t.TempDir(), "mini-rootfs.tar.gz")
|
|
writeTestArchive(t, archive)
|
|
if err := st.Load(archive, "mini"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootfs, err := st.RootFS("mini")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
content, err := os.ReadFile(filepath.Join(rootfs, "etc", "hello"))
|
|
if err != nil || string(content) != "hello from the rootfs\n" {
|
|
t.Errorf("unpacked file: %q, %v", content, err)
|
|
}
|
|
metaRaw, err := os.ReadFile(filepath.Join(st.Root, "images", "mini", "image.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var meta ImageMeta
|
|
if err := json.Unmarshal(metaRaw, &meta); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if meta.Name != "mini" || meta.Source == "" || meta.CreatedAt.IsZero() {
|
|
t.Errorf("image.json incomplete: %+v", meta)
|
|
}
|
|
}
|
|
|
|
func TestLoadRefusesAnExistingImageName(t *testing.T) {
|
|
if _, err := exec.LookPath("tar"); err != nil {
|
|
t.Skip("tar not installed")
|
|
}
|
|
st := Store{Root: t.TempDir()}
|
|
archive := filepath.Join(t.TempDir(), "mini.tar.gz")
|
|
writeTestArchive(t, archive)
|
|
if err := st.Load(archive, "mini"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err := st.Load(archive, "mini")
|
|
if err == nil || !strings.Contains(err.Error(), "already exists") {
|
|
t.Errorf("got %v, want an already-exists refusal", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadLeavesNoHalfImageOnFailure(t *testing.T) {
|
|
if _, err := exec.LookPath("tar"); err != nil {
|
|
t.Skip("tar not installed")
|
|
}
|
|
st := Store{Root: t.TempDir()}
|
|
broken := filepath.Join(t.TempDir(), "broken.tar.gz")
|
|
if err := os.WriteFile(broken, []byte("this is not a tar archive"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.Load(broken, "broken"); err == nil {
|
|
t.Fatal("expected the load to fail")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(st.Root, "images", "broken")); !os.IsNotExist(err) {
|
|
t.Errorf("expected no image directory, got %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(st.Root, "images", "broken.tmp")); !os.IsNotExist(err) {
|
|
t.Errorf("expected no leftover tmp directory, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRootFSValidation(t *testing.T) {
|
|
st := Store{Root: t.TempDir()}
|
|
if _, err := st.RootFS("no-such-image"); err == nil || !strings.Contains(err.Error(), "no such image") {
|
|
t.Errorf("got %v, want a no-such-image error", err)
|
|
}
|
|
if _, err := st.RootFS("../escape"); err == nil || !strings.Contains(err.Error(), "invalid image name") {
|
|
t.Errorf("got %v, want an invalid-name error", err)
|
|
}
|
|
}
|
|
|
|
func TestListNamesLoadedImagesAndIgnoresTmpLeftovers(t *testing.T) {
|
|
st := Store{Root: t.TempDir()}
|
|
if names, err := st.List(); err != nil || names != nil {
|
|
t.Fatalf("empty store: got %v, %v", names, err)
|
|
}
|
|
for _, dir := range []string{"beta", "alpha", "broken.tmp"} {
|
|
if err := os.MkdirAll(filepath.Join(st.Root, "images", dir), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
names, err := st.List()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(names) != 2 || names[0] != "alpha" || names[1] != "beta" {
|
|
t.Errorf("got %v, want [alpha beta]", names)
|
|
}
|
|
}
|
|
|
|
func TestImageNameFromArchive(t *testing.T) {
|
|
tests := []struct{ in, want string }{
|
|
{"werkator-buildenv-trixie.tar.zst", "werkator-buildenv-trixie"},
|
|
{"/path/to/Base.TAR.GZ", "base"},
|
|
{"rootfs.tgz", "rootfs"},
|
|
{"plain", "plain"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := ImageNameFromArchive(tt.in); got != tt.want {
|
|
t.Errorf("ImageNameFromArchive(%q) = %q, want %q", tt.in, got, tt.want)
|
|
}
|
|
}
|
|
}
|