Files
at-container-registry/deploy/upcloud/backup_test.go
T
Evan JarrettandClaude Fable 5.1 dcad0f8626 deploy: snapshot binaries, configs, units and the appview database before every update
The update command deleted the remote binary before uploading the new one and
took no backup, so a deploy that failed half way left nothing to roll back to.
Previous production deploys made a predeploy-<stamp> directory by hand.

Each target now gets that snapshot automatically before anything is replaced:
the service binary and its companion (scanner with hold, labeler with
appview), their configs and systemd units, the vcs.revision of each old
binary, and for the appview a sqlite3 .backup of ui.db because migrations run
on the next boot. A backup that does not complete aborts the deploy. The
newest five per server are kept; --backup-keep changes that and --skip-backup
turns it off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvFJr4Dwz8p2NDAeXmgmBt
2026-09-11 19:19:40 -05:00

71 lines
2.2 KiB
Go

package main
import (
"strings"
"testing"
"time"
)
func TestBackupSpecForAppviewIncludesDatabaseAndLabeler(t *testing.T) {
naming := Naming{ClientName: "seamark"}
state := &InfraState{LabelerEnabled: true, ScannerEnabled: true}
now := time.Date(2026, 9, 11, 23, 0, 5, 0, time.UTC)
s := backupSpecFor("appview", naming, state, now, 5)
if s.Dir() != "/var/lib/seamark/predeploy-20260911-230005" {
t.Fatalf("dir = %q", s.Dir())
}
if s.SQLiteDB != "/var/lib/seamark/ui.db" {
t.Fatalf("SQLiteDB = %q", s.SQLiteDB)
}
want := []string{"/opt/seamark/bin/seamark-appview", "/opt/seamark/bin/seamark-labeler"}
if strings.Join(s.Binaries, ",") != strings.Join(want, ",") {
t.Fatalf("Binaries = %v", s.Binaries)
}
if strings.Join(s.Units, ",") != "seamark-appview,seamark-labeler" {
t.Fatalf("Units = %v", s.Units)
}
h := backupSpecFor("hold", naming, state, now, 5)
if h.SQLiteDB != "" {
t.Fatalf("hold backup must not snapshot a database, got %q", h.SQLiteDB)
}
if strings.Join(h.Binaries, ",") != "/opt/seamark/bin/seamark-hold,/opt/seamark/bin/seamark-scanner" {
t.Fatalf("hold Binaries = %v", h.Binaries)
}
}
func TestRenderBackupScript(t *testing.T) {
s := backupSpec{
BaseDir: "/var/lib/seamark",
Stamp: "20260911-230005",
Binaries: []string{"/opt/seamark/bin/seamark-appview"},
Configs: []string{"/etc/seamark/appview.yaml"},
Units: []string{"seamark-appview"},
SQLiteDB: "/var/lib/seamark/ui.db",
Keep: 5,
}
script := renderBackupScript(s)
for _, want := range []string{
`DIR="/var/lib/seamark/predeploy-20260911-230005"`,
`cp -p "/opt/seamark/bin/seamark-appview" "$DIR/bin/"`,
`vcs.revision=`,
`cp -p "/etc/seamark/appview.yaml" "$DIR/etc/"`,
`cp -p "/etc/systemd/system/seamark-appview.service" "$DIR/systemd/"`,
`sqlite3 "/var/lib/seamark/ui.db" ".backup`,
`head -n -5 | xargs -r rm -rf`,
`echo "BACKUP_OK $DIR"`,
} {
if !strings.Contains(script, want) {
t.Errorf("script missing %q\n%s", want, script)
}
}
s.SQLiteDB = ""
s.Keep = 0
script = renderBackupScript(s)
if strings.Contains(script, "sqlite3") || strings.Contains(script, "xargs -r rm -rf") {
t.Errorf("no database and no pruning expected without SQLiteDB/Keep:\n%s", script)
}
}