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 }