docs: publish Tend Compose continuity evidence
Export the reviewed allowlisted snapshot from private source commit 07c1655921f21ee5e4fc4d85639d199e8867b17d. This records the Docker Compose activation, schema-compatible rollback, and stateful migration resource findings from Observatory Preview 19 dogfooding. AI-Assisted: OpenAI Codex Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const SchemaVersion = 1
|
||||
|
||||
type Record struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
Strategy string `json:"strategy"`
|
||||
DesiredRelease string `json:"desired_release,omitempty"`
|
||||
CandidateRelease string `json:"candidate_release,omitempty"`
|
||||
ActiveSlot string `json:"active_slot"`
|
||||
ActiveRelease string `json:"active_release"`
|
||||
PreviousSlot string `json:"previous_slot,omitempty"`
|
||||
PreviousRelease string `json:"previous_release,omitempty"`
|
||||
LastAttemptRelease string `json:"last_attempt_release,omitempty"`
|
||||
LastAttemptOutcome string `json:"last_attempt_outcome,omitempty"`
|
||||
LastAttemptAt string `json:"last_attempt_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
func Load(path, root, strategy string) (Record, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(b))
|
||||
dec.DisallowUnknownFields()
|
||||
var record Record
|
||||
if err := dec.Decode(&record); err != nil {
|
||||
return Record{}, fmt.Errorf("decode state: %w", err)
|
||||
}
|
||||
var extra any
|
||||
if err := dec.Decode(&extra); !errors.Is(err, io.EOF) {
|
||||
return Record{}, errors.New("state contains trailing data")
|
||||
}
|
||||
if err := record.Validate(root, strategy); err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (r Record) Validate(root, strategy string) error {
|
||||
if r.SchemaVersion != SchemaVersion {
|
||||
return errors.New("state schema version is unsupported")
|
||||
}
|
||||
if r.Strategy != strategy {
|
||||
return errors.New("state strategy does not match configuration")
|
||||
}
|
||||
if strategy == "blue_green" && (r.ActiveSlot != "blue" && r.ActiveSlot != "green") {
|
||||
return errors.New("active slot is invalid")
|
||||
}
|
||||
if strategy == "singleton_candidate" && r.ActiveSlot != "singleton" {
|
||||
return errors.New("singleton state slot is invalid")
|
||||
}
|
||||
if err := releaseBelow(root, r.ActiveRelease); err != nil {
|
||||
return fmt.Errorf("active release: %w", err)
|
||||
}
|
||||
if r.PreviousRelease != "" {
|
||||
if err := releaseBelow(root, r.PreviousRelease); err != nil {
|
||||
return fmt.Errorf("previous release: %w", err)
|
||||
}
|
||||
}
|
||||
for label, release := range map[string]string{"desired release": r.DesiredRelease, "candidate release": r.CandidateRelease, "last attempt release": r.LastAttemptRelease} {
|
||||
if release != "" {
|
||||
if err := releaseBelow(root, release); err != nil {
|
||||
return fmt.Errorf("%s: %w", label, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.LastAttemptOutcome != "" {
|
||||
switch r.LastAttemptOutcome {
|
||||
case "running", "succeeded", "failed", "rolled_back":
|
||||
default:
|
||||
return errors.New("last attempt outcome is invalid")
|
||||
}
|
||||
if _, err := time.Parse(time.RFC3339, r.LastAttemptAt); err != nil {
|
||||
return errors.New("last attempt timestamp is invalid")
|
||||
}
|
||||
}
|
||||
if r.LastAttemptOutcome == "running" && r.CandidateRelease == "" {
|
||||
return errors.New("running attempt requires a candidate release")
|
||||
}
|
||||
if strategy == "blue_green" && r.PreviousRelease != "" && r.PreviousSlot == r.ActiveSlot {
|
||||
return errors.New("previous slot must differ from active slot")
|
||||
}
|
||||
if _, err := time.Parse(time.RFC3339, r.UpdatedAt); err != nil {
|
||||
return errors.New("state timestamp is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Store(path, root string, record Record) error {
|
||||
if err := record.Validate(root, record.Strategy); err != nil {
|
||||
return err
|
||||
}
|
||||
if info, err := os.Lstat(path); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
||||
return errors.New("state file must not be a symlink")
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
b, err := json.MarshalIndent(record, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b = append(b, '\n')
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, err := os.CreateTemp(dir, ".tend-state-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := tmp.Name()
|
||||
ok := false
|
||||
defer func() {
|
||||
_ = tmp.Close()
|
||||
if !ok {
|
||||
_ = os.Remove(name)
|
||||
}
|
||||
}()
|
||||
if err := tmp.Chmod(0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tmp.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tmp.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(name, path); err != nil {
|
||||
return err
|
||||
}
|
||||
ok = true
|
||||
return syncDir(dir)
|
||||
}
|
||||
|
||||
func releaseBelow(root, path string) error {
|
||||
if !filepath.IsAbs(path) || filepath.Clean(path) != path {
|
||||
return errors.New("must be a clean absolute path")
|
||||
}
|
||||
releases := filepath.Join(root, "releases")
|
||||
rel, err := filepath.Rel(releases, path)
|
||||
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || strings.ContainsRune(rel, filepath.Separator) {
|
||||
return errors.New("must be one direct child of the release directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func syncDir(path string) error {
|
||||
dir, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
return dir.Sync()
|
||||
}
|
||||
Reference in New Issue
Block a user