Prototype atomic install recovery (T-103)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsafeInstallLayout = errors.New("unsafe install directory layout")
|
||||
ErrStagingMissing = errors.New("staging directory is missing")
|
||||
ErrBackupExists = errors.New("backup directory already exists")
|
||||
)
|
||||
|
||||
type appLayout struct {
|
||||
root string
|
||||
current string
|
||||
staging string
|
||||
backup string
|
||||
transaction string
|
||||
transactionBackup string
|
||||
}
|
||||
|
||||
type directoryState struct {
|
||||
current bool
|
||||
staging bool
|
||||
backup bool
|
||||
}
|
||||
|
||||
func inspectAppLayout(root string) (appLayout, error) {
|
||||
if root == "" {
|
||||
return appLayout{}, fmt.Errorf("%w: empty root", ErrUnsafeInstallLayout)
|
||||
}
|
||||
absolute, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return appLayout{}, fmt.Errorf("%w: %v", ErrUnsafeInstallLayout, err)
|
||||
}
|
||||
info, err := os.Lstat(absolute)
|
||||
if err != nil {
|
||||
return appLayout{}, fmt.Errorf("%w: root: %v", ErrUnsafeInstallLayout, err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return appLayout{}, fmt.Errorf("%w: root is not a real directory", ErrUnsafeInstallLayout)
|
||||
}
|
||||
transaction, transactionBackup := transactionPaths(absolute)
|
||||
return appLayout{
|
||||
root: absolute,
|
||||
current: filepath.Join(absolute, "current"),
|
||||
staging: filepath.Join(absolute, "staging"),
|
||||
backup: filepath.Join(absolute, "backup"),
|
||||
transaction: transaction,
|
||||
transactionBackup: transactionBackup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func inspectDirectories(layout appLayout) (directoryState, error) {
|
||||
current, err := inspectManagedDirectory(layout.current)
|
||||
if err != nil {
|
||||
return directoryState{}, err
|
||||
}
|
||||
staging, err := inspectManagedDirectory(layout.staging)
|
||||
if err != nil {
|
||||
return directoryState{}, err
|
||||
}
|
||||
backup, err := inspectManagedDirectory(layout.backup)
|
||||
if err != nil {
|
||||
return directoryState{}, err
|
||||
}
|
||||
return directoryState{
|
||||
current: current,
|
||||
staging: staging,
|
||||
backup: backup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func inspectManagedDirectory(path string) (bool, error) {
|
||||
info, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: inspect %s: %v", ErrUnsafeInstallLayout, path, err)
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return false, fmt.Errorf("%w: %s is not a real directory", ErrUnsafeInstallLayout, path)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func removeManagedDirectory(layout appLayout, target string) error {
|
||||
if filepath.Dir(target) != layout.root {
|
||||
return fmt.Errorf("%w: refuse removal outside app root", ErrUnsafeInstallLayout)
|
||||
}
|
||||
base := filepath.Base(target)
|
||||
if base != "current" && base != "staging" && base != "backup" {
|
||||
return fmt.Errorf("%w: refuse removal of %s", ErrUnsafeInstallLayout, base)
|
||||
}
|
||||
exists, err := inspectManagedDirectory(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
if err := os.RemoveAll(target); err != nil {
|
||||
return fmt.Errorf("remove managed directory %s: %w", base, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user