103 lines
3.2 KiB
Go
103 lines
3.2 KiB
Go
package updater
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"path/filepath"
|
||
|
|
)
|
||
|
|
|
||
|
|
func recoverLayout(layout updateLayout, syncer DirectorySyncer) error {
|
||
|
|
record, exists, err := loadTransaction(layout)
|
||
|
|
if err != nil || !exists {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
layout.requestID = record.RequestID
|
||
|
|
layout.staging = filepath.Join(layout.root, stagingDirectoryName, record.RequestID)
|
||
|
|
layout.backup = filepath.Join(layout.root, backupsDirectoryName, record.RequestID)
|
||
|
|
switch record.Phase {
|
||
|
|
case phasePrepared:
|
||
|
|
return removeTransaction(layout, syncer)
|
||
|
|
case phaseTargetBackedUp:
|
||
|
|
if err := restoreBackup(layout, syncer); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return removeTransaction(layout, syncer)
|
||
|
|
case phaseStagingActivated, phaseLaunched:
|
||
|
|
if healthMatches(layout, record.RequestID) {
|
||
|
|
if err := removeManagedTree(layout.backup, layout, syncer); err != nil {
|
||
|
|
return fmt.Errorf("finalize recovered self-update: %w", err)
|
||
|
|
}
|
||
|
|
return removeTransaction(layout, syncer)
|
||
|
|
}
|
||
|
|
return rollbackLayout(layout, syncer)
|
||
|
|
case phaseCommitted:
|
||
|
|
if err := removeManagedTree(layout.backup, layout, syncer); err != nil {
|
||
|
|
return fmt.Errorf("finalize committed self-update: %w", err)
|
||
|
|
}
|
||
|
|
return removeTransaction(layout, syncer)
|
||
|
|
default:
|
||
|
|
return ErrTransactionCorrupt
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func rollbackLayout(layout updateLayout, syncer DirectorySyncer) error {
|
||
|
|
backupExists, err := realDirectoryState(layout.backup, "backup")
|
||
|
|
if err != nil || !backupExists {
|
||
|
|
return fmt.Errorf("%w: old app backup is unavailable", ErrRecoveryRequired)
|
||
|
|
}
|
||
|
|
targetExists, err := realDirectoryState(layout.target, "target")
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if targetExists {
|
||
|
|
stagingExists, err := realDirectoryState(layout.staging, "staging")
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if stagingExists {
|
||
|
|
return fmt.Errorf("%w: staged and active app both exist", ErrRecoveryRequired)
|
||
|
|
}
|
||
|
|
if err := renameDirectory(layout.target, layout.staging, layout, syncer, "preserve failed staged app"); err != nil {
|
||
|
|
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if err := renameDirectory(layout.backup, layout.target, layout, syncer, "restore previous app"); err != nil {
|
||
|
|
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||
|
|
}
|
||
|
|
if err := removeTransaction(layout, syncer); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := removeHealth(layout, syncer); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func restoreBackup(layout updateLayout, syncer DirectorySyncer) error {
|
||
|
|
targetExists, err := realDirectoryState(layout.target, "target")
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if targetExists {
|
||
|
|
return fmt.Errorf("%w: target exists before backup restoration", ErrRecoveryRequired)
|
||
|
|
}
|
||
|
|
if exists, err := realDirectoryState(layout.backup, "backup"); err != nil || !exists {
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return fmt.Errorf("%w: backup is unavailable", ErrRecoveryRequired)
|
||
|
|
}
|
||
|
|
if err := renameDirectory(layout.backup, layout.target, layout, syncer, "restore previous app"); err != nil {
|
||
|
|
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func healthMatches(layout updateLayout, requestID string) bool {
|
||
|
|
record, err := readHealth(layout.health)
|
||
|
|
return err == nil && record.RequestID == requestID
|
||
|
|
}
|
||
|
|
|
||
|
|
func removeHealth(layout updateLayout, syncer DirectorySyncer) error {
|
||
|
|
return removeRegularFile(layout.health, layout.root, syncer)
|
||
|
|
}
|