88 lines
2.6 KiB
Go
88 lines
2.6 KiB
Go
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
|
|
}
|