repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/publish/publish_test.go
Rawpackage 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")
}
}