f61ef2aa9e
internal/backup/backup.go: Backup (tar.gz + HMAC-SHA256 signature, excludes /run/orca + sockets + WAL/SHM), VerifySignature, Restore (signature verify + extract + Force flag). internal/cli/backup.go: orca backup --out + orca restore --in --force. Tests: round-trip, signature mismatch, exclusion, force-refuse, force-overwrite. ---ci--- project: orca phase: 04 milestone: v0.11 status: execute ---/ci---
178 lines
4.3 KiB
Go
178 lines
4.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/paths"
|
|
"git.cloudinit.dev/coreci/orca/internal/secrets"
|
|
)
|
|
|
|
func setupBackupTestEnv(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
t.Setenv("ORCA_HOME", dir)
|
|
mk, err := secrets.GenerateMasterKey()
|
|
if err != nil {
|
|
t.Fatalf("GenerateMasterKey: %v", err)
|
|
}
|
|
if err := os.MkdirAll(paths.ClusterDir(), 0o755); err != nil {
|
|
t.Fatalf("mkdir cluster dir: %v", err)
|
|
}
|
|
if err := secrets.SaveMasterKey(paths.MasterKeyPath(), mk); err != nil {
|
|
t.Fatalf("SaveMasterKey: %v", err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
func TestBackupCmdRegistered(t *testing.T) {
|
|
found := false
|
|
for _, cmd := range rootCmd.Commands() {
|
|
if cmd.Name() == "backup" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("backup command not registered on root")
|
|
}
|
|
}
|
|
|
|
func TestRestoreCmdRegistered(t *testing.T) {
|
|
found := false
|
|
for _, cmd := range rootCmd.Commands() {
|
|
if cmd.Name() == "restore" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("restore command not registered on root")
|
|
}
|
|
}
|
|
|
|
func TestBackupRestoreCmdRoundTrip(t *testing.T) {
|
|
home := setupBackupTestEnv(t)
|
|
|
|
if err := os.WriteFile(filepath.Join(home, "keep.txt"), []byte("payload"), 0o644); err != nil {
|
|
t.Fatalf("write keep.txt: %v", err)
|
|
}
|
|
|
|
outDir := t.TempDir()
|
|
out := filepath.Join(outDir, "orca-backup.tar.gz")
|
|
|
|
var buf bytes.Buffer
|
|
resetRootFlags(t)
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"backup", "--out", out})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("orca backup: %v", err)
|
|
}
|
|
if _, err := os.Stat(out + ".sig"); err != nil {
|
|
t.Fatalf("sig missing: %v", err)
|
|
}
|
|
|
|
target := filepath.Join(t.TempDir(), "restored")
|
|
var buf2 bytes.Buffer
|
|
resetRootFlags(t)
|
|
rootCmd.SetOut(&buf2)
|
|
rootCmd.SetErr(&buf2)
|
|
rootCmd.SetArgs([]string{"restore", "--in", out, "--target", target})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("orca restore: %v", err)
|
|
}
|
|
|
|
got, err := os.ReadFile(filepath.Join(target, "keep.txt"))
|
|
if err != nil {
|
|
t.Fatalf("restored keep.txt missing: %v", err)
|
|
}
|
|
if string(got) != "payload" {
|
|
t.Errorf("restored keep.txt = %q, want %q", string(got), "payload")
|
|
}
|
|
}
|
|
|
|
func TestRestoreCmdBadSignature(t *testing.T) {
|
|
setupBackupTestEnv(t)
|
|
|
|
outDir := t.TempDir()
|
|
out := filepath.Join(outDir, "orca-backup.tar.gz")
|
|
body := []byte("not a real tarball")
|
|
if err := os.WriteFile(out, body, 0o644); err != nil {
|
|
t.Fatalf("write fake tarball: %v", err)
|
|
}
|
|
if err := os.WriteFile(out+".sig", []byte("deadbeef"), 0o644); err != nil {
|
|
t.Fatalf("write fake sig: %v", err)
|
|
}
|
|
|
|
target := filepath.Join(t.TempDir(), "restored")
|
|
var buf bytes.Buffer
|
|
resetRootFlags(t)
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"restore", "--in", out, "--target", target})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("restore with bad signature should fail")
|
|
}
|
|
}
|
|
|
|
func TestRestoreCmdRequiresInFlag(t *testing.T) {
|
|
setupBackupTestEnv(t)
|
|
var buf bytes.Buffer
|
|
resetRootFlags(t)
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"restore"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("restore without --in should fail")
|
|
}
|
|
}
|
|
|
|
func TestBackupCmdDefaultOut(t *testing.T) {
|
|
home := setupBackupTestEnv(t)
|
|
if err := os.WriteFile(filepath.Join(home, "f.txt"), []byte("x"), 0o644); err != nil {
|
|
t.Fatalf("write f.txt: %v", err)
|
|
}
|
|
|
|
work := t.TempDir()
|
|
orig, _ := os.Getwd()
|
|
if err := os.Chdir(work); err != nil {
|
|
t.Fatalf("chdir: %v", err)
|
|
}
|
|
defer os.Chdir(orig)
|
|
|
|
var buf bytes.Buffer
|
|
resetRootFlags(t)
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"backup"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("orca backup default out: %v", err)
|
|
}
|
|
|
|
entries, err := os.ReadDir(work)
|
|
if err != nil {
|
|
t.Fatalf("read work: %v", err)
|
|
}
|
|
var foundTar, foundSig bool
|
|
for _, e := range entries {
|
|
if e.Name() == "orca-backup" || strings.HasPrefix(e.Name(), "orca-backup-") && strings.HasSuffix(e.Name(), ".tar.gz") {
|
|
foundTar = true
|
|
}
|
|
if strings.HasSuffix(e.Name(), ".tar.gz.sig") {
|
|
foundSig = true
|
|
}
|
|
}
|
|
if !foundTar {
|
|
t.Errorf("default backup tarball not created in CWD (entries: %d)", len(entries))
|
|
}
|
|
if !foundSig {
|
|
t.Errorf("default backup sig not created in CWD (entries: %d)", len(entries))
|
|
}
|
|
}
|