fix: harden pi maintenance lifecycle
This commit is contained in:
@@ -3,6 +3,7 @@ package pi
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -103,10 +104,9 @@ func TestUpdateDoesNotRecreateWhenPreflightOrBuildFails(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("Update() error = nil, want failure")
|
||||
}
|
||||
if result.Phase == PhaseRolledBack {
|
||||
t.Fatalf("pre-recreate failure unexpectedly rolled back: %+v", result)
|
||||
}
|
||||
assertNotCalled(t, fake.calls, "force-recreate")
|
||||
if failure == "preflight" && result.Phase == PhaseRolledBack { t.Fatalf("preflight failure unexpectedly rolled back: %+v", result) }
|
||||
if failure == "build" && result.Phase != PhaseRolledBack { t.Fatalf("candidate build failure must compensate: %+v", result) }
|
||||
if failure == "preflight" { assertNotCalled(t, fake.calls, "force-recreate") }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ func TestRollbackRestoresInterruptedOrPreviouslyRecordedState(t *testing.T) {
|
||||
func TestUpdateRefusesToOverwriteInterruptedRecoveryState(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
statePath := filepath.Join(t.TempDir(), "state.json")
|
||||
writeStateForTest(t, statePath, State{Phase: PhaseRecreated, Previous: Image{ID: "sha256:old", Reference: "thothii-core:local", Volumes: []string{"settings"}}})
|
||||
writeStateForTest(t, statePath, State{Phase: PhaseRecreated, Previous: Image{ID: "sha256:old", Reference: "thothii-core:local", Volumes: []string{"settings"}, MountFingerprint: "recorded"}})
|
||||
_, err := Update(context.Background(), fake, Request{StatePath: statePath, Version: "0.81.0", Source: BuildSource, Confirm: true})
|
||||
if !errors.Is(err, ErrInterruptedUpdate) {
|
||||
t.Fatalf("Update() error = %v, want interrupted update error", err)
|
||||
@@ -180,6 +180,17 @@ func TestRunningImageCapturesServerBindAndNamedMountIdentity(t *testing.T) {
|
||||
if len(image.Mounts) != 3 || image.Mounts[0].Type != "bind" || image.Mounts[0].Destination != "/data" {
|
||||
t.Fatalf("mounts = %#v", image.Mounts)
|
||||
}
|
||||
if strings.Contains(fmt.Sprint(image), "/srv/thothii") || image.Mounts[0].SourceSHA256 == "" || image.MountFingerprint == "" {
|
||||
t.Fatalf("mount contract leaked a server source or lacks a safe fingerprint: %#v", image)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalDigestReferenceRejectsCredentialsAndURLForms(t *testing.T) {
|
||||
valid := "registry.example.invalid/thothii-core@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
if got, err := canonicalDigestReference(valid); err != nil || got != valid { t.Fatalf("canonicalDigestReference() = %q, %v", got, err) }
|
||||
for _, invalid := range []string{"https://registry.example.invalid/a@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "user:pass@registry.example/a@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "registry.example/a@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?token=x"} {
|
||||
if _, err := canonicalDigestReference(invalid); err == nil { t.Fatalf("accepted unsafe reference %q", invalid) }
|
||||
}
|
||||
}
|
||||
|
||||
type fakeRunner struct {
|
||||
@@ -188,12 +199,13 @@ type fakeRunner struct {
|
||||
version string
|
||||
activeSessions bool
|
||||
built bool
|
||||
currentImage string
|
||||
volumes []string
|
||||
mountsJSON string
|
||||
}
|
||||
|
||||
func newFakeRunner() *fakeRunner {
|
||||
return &fakeRunner{version: "0.80.3", volumes: []string{"settings", "pi-state", "sessions", "workspace-registry"}}
|
||||
return &fakeRunner{version: "0.80.3", currentImage: "sha256:old", volumes: []string{"settings", "pi-state", "sessions", "workspace-registry"}}
|
||||
}
|
||||
|
||||
func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) {
|
||||
@@ -201,6 +213,7 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
|
||||
f.calls = append(f.calls, call)
|
||||
if strings.Contains(call, "image tag sha256:old") {
|
||||
f.fail = ""
|
||||
f.currentImage = "sha256:old"
|
||||
}
|
||||
if f.fail == "preflight" && strings.Contains(call, "config --format json") {
|
||||
return compose.Result{ExitCode: 1}, errors.New("provider token=secret")
|
||||
@@ -214,7 +227,7 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
|
||||
if f.fail == "version" && f.built && strings.Contains(call, "pi --version") && strings.Contains(call, "exec") {
|
||||
return compose.Result{ExitCode: 1}, errors.New("version token=secret")
|
||||
}
|
||||
if f.fail == "smoke" && strings.Contains(call, "curl -fsS http://127.0.0.1:8787/models") {
|
||||
if f.fail == "smoke" && f.built && strings.Contains(call, "127.0.0.1:8787/models") {
|
||||
return compose.Result{ExitCode: 1}, errors.New("smoke token=secret")
|
||||
}
|
||||
switch {
|
||||
@@ -223,7 +236,7 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
|
||||
case strings.Contains(call, "ps -q core"):
|
||||
return compose.Result{Stdout: "core-container\n"}, nil
|
||||
case strings.Contains(call, "inspect --format {{.Image}}"):
|
||||
return compose.Result{Stdout: "sha256:old\n"}, nil
|
||||
return compose.Result{Stdout: f.currentImage + "\n"}, nil
|
||||
case strings.Contains(call, "inspect --format {{json .Mounts}}"):
|
||||
if f.mountsJSON != "" {
|
||||
return compose.Result{Stdout: f.mountsJSON}, nil
|
||||
@@ -238,6 +251,7 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
|
||||
case strings.Contains(call, "compose build"):
|
||||
f.built = true
|
||||
f.version = "0.81.0"
|
||||
f.currentImage = "sha256:candidate"
|
||||
return compose.Result{}, nil
|
||||
case strings.Contains(call, "pi --version"):
|
||||
return compose.Result{Stdout: f.version + "\n"}, nil
|
||||
|
||||
Reference in New Issue
Block a user