Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

services/luci/internal/publish/publish_test.go

Raw
package publish

import (
	"bytes"
	"context"
	"errors"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

func TestPkgAndSitePublishReplaceDestination(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(filepath.Join(workspace, "dist"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "dist", "index.html"), []byte("new"), 0o644); err != nil {
		t.Fatal(err)
	}
	for _, adapter := range []string{"pkg", "site"} {
		destinationRoot := filepath.Join(root, adapter)
		destination := filepath.Join(destinationRoot, "project")
		if err := os.MkdirAll(destination, 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(filepath.Join(destination, "old"), []byte("old"), 0o644); err != nil {
			t.Fatal(err)
		}
		service := Service{PkgRoot: destinationRoot, SiteRoot: destinationRoot}
		err := service.Publish(context.Background(), Request{Adapter: adapter, From: "dist", To: "project", Workspace: workspace}, &bytes.Buffer{})
		if err != nil {
			t.Fatal(err)
		}
		data, err := os.ReadFile(filepath.Join(destination, "index.html"))
		if err != nil || string(data) != "new" {
			t.Fatalf("%s data=%q err=%v", adapter, data, err)
		}
		if _, err := os.Stat(filepath.Join(destination, "old")); !os.IsNotExist(err) {
			t.Fatalf("%s old file remains: %v", adapter, err)
		}
	}
}

func TestPublishReplacementFailureLeavesPreviousTargetVisible(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(filepath.Join(workspace, "dist"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "dist", "new"), []byte("new"), 0o644); err != nil {
		t.Fatal(err)
	}
	targetRoot := filepath.Join(root, "site")
	target := filepath.Join(targetRoot, "project")
	if err := os.MkdirAll(target, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(target, "old"), []byte("old"), 0o644); err != nil {
		t.Fatal(err)
	}
	boom := errors.New("exchange unavailable")
	oldExchange := renameExchange
	renameExchange = func(string, string) error { return boom }
	defer func() { renameExchange = oldExchange }()
	err := (Service{SiteRoot: targetRoot}).Publish(context.Background(), Request{Adapter: "site", From: "dist", To: "project", Workspace: workspace}, &bytes.Buffer{})
	if !errors.Is(err, boom) {
		t.Fatalf("err=%v", err)
	}
	data, err := os.ReadFile(filepath.Join(target, "old"))
	if err != nil || string(data) != "old" {
		t.Fatalf("previous target missing or changed: %q %v", data, err)
	}
	if _, err := os.Stat(filepath.Join(target, "new")); !os.IsNotExist(err) {
		t.Fatalf("partial new target visible: %v", err)
	}
}

func TestPublishFirstCreateRenameFailureLeavesNoTarget(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(filepath.Join(workspace, "dist"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "dist", "new"), []byte("new"), 0o644); err != nil {
		t.Fatal(err)
	}
	boom := errors.New("rename failed")
	oldRename := renamePublish
	renamePublish = func(string, string) error { return boom }
	defer func() { renamePublish = oldRename }()
	targetRoot := filepath.Join(root, "site")
	err := (Service{SiteRoot: targetRoot}).Publish(context.Background(), Request{Adapter: "site", From: "dist", To: "project", Workspace: workspace}, &bytes.Buffer{})
	if !errors.Is(err, boom) {
		t.Fatalf("err=%v", err)
	}
	if _, err := os.Stat(filepath.Join(targetRoot, "project")); !os.IsNotExist(err) {
		t.Fatalf("partial first target visible: %v", err)
	}
}

func TestPublishContextCancellationLeavesDestinationUntouched(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(filepath.Join(workspace, "dist"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "dist", "new"), []byte("new"), 0o644); err != nil {
		t.Fatal(err)
	}
	destinationRoot := filepath.Join(root, "site")
	destination := filepath.Join(destinationRoot, "project")
	if err := os.MkdirAll(destination, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(destination, "old"), []byte("old"), 0o644); err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	err := (Service{SiteRoot: destinationRoot}).Publish(ctx, Request{Adapter: "site", From: "dist", To: "project", Workspace: workspace}, &bytes.Buffer{})
	if err != context.Canceled {
		t.Fatalf("err=%v", err)
	}
	data, err := os.ReadFile(filepath.Join(destination, "old"))
	if err != nil || string(data) != "old" {
		t.Fatalf("destination=%q err=%v", data, err)
	}
}

func TestRegistryPublishStagesArchiveAndUsesSkopeo(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(workspace, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "image.oci"), []byte("archive"), 0o600); err != nil {
		t.Fatal(err)
	}
	var gotArgs, gotEnv []string
	var staged []byte
	var output bytes.Buffer
	service := Service{RegistryAuthFile: "/auth.json", Run: func(_ context.Context, args, env []string, _ io.Writer) error {
		gotArgs, gotEnv = args, env
		var err error
		staged, err = os.ReadFile(args[5][len("oci-archive:"):])
		if err != nil {
			return err
		}
		return os.WriteFile(args[4], []byte("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n"), 0o600)
	}}
	err := service.Publish(context.Background(), Request{Adapter: "registry", From: "image.oci", To: "registry.invalid/app:v1", Workspace: workspace}, &output)
	if err != nil {
		t.Fatal(err)
	}
	if len(gotArgs) != 7 || gotArgs[0] != "copy" || gotArgs[1] != "--authfile" || gotArgs[2] != "/auth.json" || gotArgs[3] != "--digestfile" || !strings.HasPrefix(gotArgs[5], "oci-archive:") || gotArgs[6] != "docker://registry.invalid/app:v1" {
		t.Fatalf("args=%v", gotArgs)
	}
	if string(staged) != "archive" {
		t.Fatalf("staged=%q", staged)
	}
	if !reflect.DeepEqual(gotEnv, os.Environ()) {
		t.Fatalf("env=%v", gotEnv)
	}
	if !strings.Contains(output.String(), "[luci] registry-published destination=registry.invalid/app:v1 digest=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef reference=registry.invalid/app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") {
		t.Fatalf("output=%q", output.String())
	}
	if _, err := os.Stat(gotArgs[5][len("oci-archive:"):]); !os.IsNotExist(err) {
		t.Fatalf("staged archive remains: %v", err)
	}
}

func TestCanonicalRegistryReference(t *testing.T) {
	actual := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	for _, test := range []struct {
		destination string
		want        string
		wantErr     bool
	}{
		{"registry.invalid/app", "registry.invalid/app@" + actual, false},
		{"localhost:5000/app:v1", "localhost:5000/app@" + actual, false},
		{"registry.invalid/team/app:v1", "registry.invalid/team/app@" + actual, false},
		{"registry.invalid/app@" + actual, "registry.invalid/app@" + actual, false},
		{"registry.invalid/app:v1@" + actual, "registry.invalid/app@" + actual, false},
		{"registry.invalid/app@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "", true},
	} {
		got, err := canonicalRegistryReference(test.destination, actual)
		if (err != nil) != test.wantErr || got != test.want {
			t.Fatalf("canonicalRegistryReference(%q) = %q, %v; want %q, error=%t", test.destination, got, err, test.want, test.wantErr)
		}
	}
}

func TestRegistryPublishRejectsMissingMalformedOrFailedDigestWithoutEvidence(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(workspace, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(workspace, "image.oci"), []byte("archive"), 0o600); err != nil {
		t.Fatal(err)
	}
	actual := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	for name, test := range map[string]struct {
		to  string
		run func([]string) error
	}{
		"failure":                   {"registry.invalid/app:v1", func([]string) error { return errors.New("publish failed") }},
		"missing":                   {"registry.invalid/app:v1", func([]string) error { return nil }},
		"malformed":                 {"registry.invalid/app:v1", func(args []string) error { return os.WriteFile(args[4], []byte("sha256:nope"), 0o600) }},
		"conflicting pinned target": {"registry.invalid/app@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", func(args []string) error { return os.WriteFile(args[4], []byte(actual), 0o600) }},
	} {
		test := test
		t.Run(name, func(t *testing.T) {
			var output bytes.Buffer
			service := Service{RegistryAuthFile: "/auth.json", Run: func(_ context.Context, args, _ []string, _ io.Writer) error { return test.run(args) }}
			if err := service.Publish(context.Background(), Request{Adapter: "registry", From: "image.oci", To: test.to, Workspace: workspace}, &output); err == nil {
				t.Fatal("publish succeeded")
			}
			if strings.Contains(output.String(), "registry-published") {
				t.Fatalf("false evidence: %q", output.String())
			}
		})
	}
}

func TestRegistryRejectsUnsafeArchiveAndTarget(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(workspace, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.Symlink(root, filepath.Join(workspace, "image.oci")); err != nil {
		t.Fatal(err)
	}
	service := Service{RegistryAuthFile: "/auth.json"}
	for _, request := range []Request{
		{Adapter: "registry", From: "missing.oci", To: "registry.invalid/app:latest", Workspace: workspace},
		{Adapter: "registry", From: "image.oci", To: "registry.invalid/app:latest", Workspace: workspace},
		{Adapter: "registry", From: "missing.oci", To: "--tls-verify=false", Workspace: workspace},
		{Adapter: "registry", From: "missing.oci", To: "docker://registry.invalid/app", Workspace: workspace},
	} {
		if err := service.Publish(context.Background(), request, &bytes.Buffer{}); err == nil {
			t.Fatalf("accepted %#v", request)
		}
	}
}

func TestPublishRejectsMissingConfigBadShapeUnsafePathsAndSymlinks(t *testing.T) {
	root := t.TempDir()
	workspace := filepath.Join(root, "work")
	if err := os.MkdirAll(filepath.Join(workspace, "dist"), 0o755); err != nil {
		t.Fatal(err)
	}
	service := Service{}
	cases := []Request{
		{Adapter: "registry", Image: "app", To: "target"},
		{Adapter: "registry", From: "dist", To: "file:///tmp/target", Workspace: workspace},
		{Adapter: "pkg", From: "dist", To: "target", Workspace: workspace},
		{Adapter: "site", From: "dist", To: "target", Workspace: workspace},
		{Adapter: "unknown", From: "dist", To: "target", Workspace: workspace},
	}
	for _, request := range cases {
		if err := service.Publish(context.Background(), request, &bytes.Buffer{}); err == nil {
			t.Fatalf("accepted %#v", request)
		}
	}
	service.SiteRoot = filepath.Join(root, "site")
	if err := service.Publish(context.Background(), Request{Adapter: "site", From: "../outside", To: "target", Workspace: workspace}, &bytes.Buffer{}); err == nil {
		t.Fatal("unsafe source accepted")
	}
	if err := os.Symlink(root, filepath.Join(workspace, "link")); err != nil {
		t.Fatal(err)
	}
	if err := service.Publish(context.Background(), Request{Adapter: "site", From: "link", To: "target", Workspace: workspace}, &bytes.Buffer{}); err == nil {
		t.Fatal("source symlink accepted")
	}
	if err := os.MkdirAll(service.SiteRoot, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.Symlink(root, filepath.Join(service.SiteRoot, "escape")); err != nil {
		t.Fatal(err)
	}
	if err := service.Publish(context.Background(), Request{Adapter: "site", From: "dist", To: "escape/target", Workspace: workspace}, &bytes.Buffer{}); err == nil {
		t.Fatal("target parent symlink accepted")
	}
	if err := os.Symlink(root, filepath.Join(service.SiteRoot, "target")); err != nil {
		t.Fatal(err)
	}
	if err := service.Publish(context.Background(), Request{Adapter: "site", From: "dist", To: "target", Workspace: workspace}, &bytes.Buffer{}); err == nil {
		t.Fatal("target leaf symlink accepted")
	}
}