Implement controlled app launch (T-401)
This commit is contained in:
@@ -48,6 +48,7 @@ type ExtractResult struct {
|
||||
Files int
|
||||
Bytes int64
|
||||
EntrypointPath string
|
||||
WorkingDir string
|
||||
PayloadFiles []ExtractedFile
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ var (
|
||||
|
||||
type HealthCheck func(currentPath string) error
|
||||
|
||||
// PreSwitchCheck runs immediately before replacing an existing current
|
||||
// directory. It is intentionally core-only so callers can inject process
|
||||
// state without bringing platform APIs into the transaction.
|
||||
type PreSwitchCheck func() error
|
||||
|
||||
type switchStep string
|
||||
|
||||
const (
|
||||
@@ -42,13 +47,27 @@ func (err *RollbackError) Unwrap() []error {
|
||||
|
||||
// Switcher activates a verified staging directory and runs an injected check.
|
||||
type Switcher struct {
|
||||
health HealthCheck
|
||||
afterStep func(switchStep) error
|
||||
durability durabilityFence
|
||||
health HealthCheck
|
||||
preSwitchCheck PreSwitchCheck
|
||||
afterStep func(switchStep) error
|
||||
durability durabilityFence
|
||||
}
|
||||
|
||||
func NewSwitcher(health HealthCheck) *Switcher {
|
||||
return &Switcher{health: health, durability: defaultDurability()}
|
||||
return NewSwitcherWithPreSwitchCheck(health, nil)
|
||||
}
|
||||
|
||||
// NewSwitcherWithPreSwitchCheck creates a switcher that performs the optional
|
||||
// check only for updates with an existing current directory.
|
||||
func NewSwitcherWithPreSwitchCheck(
|
||||
health HealthCheck,
|
||||
preSwitchCheck PreSwitchCheck,
|
||||
) *Switcher {
|
||||
return &Switcher{
|
||||
health: health,
|
||||
preSwitchCheck: preSwitchCheck,
|
||||
durability: defaultDurability(),
|
||||
}
|
||||
}
|
||||
|
||||
func (switcher *Switcher) Switch(root string) error {
|
||||
@@ -75,6 +94,14 @@ func (switcher *Switcher) Switch(root string) error {
|
||||
if state.backup {
|
||||
return ErrBackupExists
|
||||
}
|
||||
if state.current && switcher.preSwitchCheck != nil {
|
||||
if err := switcher.preSwitchCheck(); err != nil {
|
||||
if cleanupErr := removeManagedDirectoryWithFence(layout, layout.staging, fence); cleanupErr != nil {
|
||||
return errors.Join(err, cleanupErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
record := newTransaction(phasePrepared, state.current)
|
||||
if err := writeTransactionWithFence(layout, record, fence); err != nil {
|
||||
|
||||
@@ -57,6 +57,41 @@ func TestSwitcherRemovesFailedInitialInstall(t *testing.T) {
|
||||
assertMissing(t, filepath.Join(root, transactionFileName))
|
||||
}
|
||||
|
||||
func TestSwitcherPreSwitchCheckPreventsUpdateAndCleansStaging(t *testing.T) {
|
||||
root := makeInstallRoot(t, "old", "new")
|
||||
checkErr := errors.New("target is running")
|
||||
switcher := NewSwitcherWithPreSwitchCheck(
|
||||
func(string) error { return nil },
|
||||
func() error {
|
||||
assertVersion(t, filepath.Join(root, "current"), "old")
|
||||
assertVersion(t, filepath.Join(root, "staging"), "new")
|
||||
return checkErr
|
||||
},
|
||||
)
|
||||
|
||||
err := switcher.Switch(root)
|
||||
if !errors.Is(err, checkErr) {
|
||||
t.Fatalf("Switch() error = %v, want %v", err, checkErr)
|
||||
}
|
||||
assertVersion(t, filepath.Join(root, "current"), "old")
|
||||
assertMissing(t, filepath.Join(root, "staging"))
|
||||
assertMissing(t, filepath.Join(root, "backup"))
|
||||
assertMissing(t, filepath.Join(root, transactionFileName))
|
||||
}
|
||||
|
||||
func TestSwitcherSkipsPreSwitchCheckForInitialInstall(t *testing.T) {
|
||||
root := makeInstallRoot(t, "", "new")
|
||||
switcher := NewSwitcherWithPreSwitchCheck(
|
||||
func(string) error { return nil },
|
||||
func() error { return errors.New("must not run") },
|
||||
)
|
||||
|
||||
if err := switcher.Switch(root); err != nil {
|
||||
t.Fatalf("Switch() error = %v", err)
|
||||
}
|
||||
assertVersion(t, filepath.Join(root, "current"), "new")
|
||||
}
|
||||
|
||||
func TestRecoverInterruptedSwitch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -197,6 +197,7 @@ func (extractor Extractor) ExtractVerifiedFileWithCheck(
|
||||
if err != nil {
|
||||
return ExtractResult{}, packageError(PackageStageExtract, err)
|
||||
}
|
||||
result.WorkingDir = manifest.WorkingDir
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user