Implement self-update recovery (T-403)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type transactionPhase string
|
||||
|
||||
const (
|
||||
phasePrepared transactionPhase = "prepared"
|
||||
phaseTargetBackedUp transactionPhase = "target_backed_up"
|
||||
phaseStagingActivated transactionPhase = "staging_activated"
|
||||
phaseLaunched transactionPhase = "launched"
|
||||
phaseCommitted transactionPhase = "committed"
|
||||
)
|
||||
|
||||
type transaction struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
RequestID string `json:"request_id"`
|
||||
Phase transactionPhase `json:"phase"`
|
||||
}
|
||||
|
||||
func (record transaction) validate() error {
|
||||
if record.SchemaVersion != transactionSchemaVersion || !validRequestID(record.RequestID) {
|
||||
return ErrTransactionCorrupt
|
||||
}
|
||||
switch record.Phase {
|
||||
case phasePrepared, phaseTargetBackedUp, phaseStagingActivated, phaseLaunched, phaseCommitted:
|
||||
return nil
|
||||
default:
|
||||
return ErrTransactionCorrupt
|
||||
}
|
||||
}
|
||||
|
||||
func writeTransaction(layout updateLayout, phase transactionPhase, syncer DirectorySyncer) error {
|
||||
record := transaction{SchemaVersion: transactionSchemaVersion, RequestID: layout.requestID, Phase: phase}
|
||||
if err := record.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode self-update transaction: %w", err)
|
||||
}
|
||||
data = append(data, '\n')
|
||||
if err := replaceRegularFile(layout.root, layout.transaction, ".self-update-transaction-*.tmp", data, syncer); err != nil {
|
||||
return fmt.Errorf("write self-update transaction: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadTransaction(layout updateLayout) (transaction, bool, error) {
|
||||
data, err := readRegularFile(layout.transaction)
|
||||
if os.IsNotExist(err) {
|
||||
return transaction{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return transaction{}, false, fmt.Errorf("read self-update transaction: %w", err)
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
var record transaction
|
||||
if err := decoder.Decode(&record); err != nil {
|
||||
return transaction{}, false, fmt.Errorf("%w: %v", ErrTransactionCorrupt, err)
|
||||
}
|
||||
var extra interface{}
|
||||
if err := decoder.Decode(&extra); err != io.EOF {
|
||||
if err == nil {
|
||||
return transaction{}, false, fmt.Errorf("%w: trailing JSON value", ErrTransactionCorrupt)
|
||||
}
|
||||
return transaction{}, false, fmt.Errorf("%w: trailing data: %v", ErrTransactionCorrupt, err)
|
||||
}
|
||||
if err := record.validate(); err != nil {
|
||||
return transaction{}, false, err
|
||||
}
|
||||
return record, true, nil
|
||||
}
|
||||
|
||||
func removeTransaction(layout updateLayout, syncer DirectorySyncer) error {
|
||||
if err := removeRegularFile(layout.transaction, layout.root, syncer); err != nil {
|
||||
return fmt.Errorf("remove self-update transaction: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user