package plans import ( "context" "encoding/json" "errors" "fmt" "os" "time" "github.com/carlosframework/platform/internal/release" ) // MarkerSchema is the deployment marker's wire version. const MarkerSchema = 1 // Marker says whether plans are on for the deployment (plans 2). The console // writes it once at start; boxes read it on every pass and read no limits // record while it is absent or disabled, so a deployment without a catalog // costs a box one small read per pass and changes nothing. type Marker struct { Schema int `json:"schema"` Enabled bool `json:"enabled"` CatalogID string `json:"catalog_id,omitempty"` UpdatedAt time.Time `json:"updated_at"` } // MarkerKey is where the deployment marker lives. func MarkerKey() string { return "control/plans.json" } // GetMarker reads the marker and its etag. Absent is nil, "", nil. An unknown // schema is an error, so a reader keeps whatever it last knew. func GetMarker(ctx context.Context, s release.Store) (*Marker, string, error) { raw, etag, err := s.Get(ctx, MarkerKey()) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil, "", nil } return nil, "", err } var m Marker if err := json.Unmarshal(raw, &m); err != nil { return nil, "", fmt.Errorf("plans: %s: %w", MarkerKey(), err) } if m.Schema != MarkerSchema { return nil, "", fmt.Errorf("plans: %s: schema %d, want %d", MarkerKey(), m.Schema, MarkerSchema) } return &m, etag, nil } // PutMarker CAS-writes the marker: ifMatch "" is create-only, otherwise // replace-only. Schema is stamped here. func PutMarker(ctx context.Context, s release.Store, m *Marker, ifMatch string) (string, error) { m.Schema = MarkerSchema body, err := json.MarshalIndent(m, "", " ") if err != nil { return "", err } return s.Put(ctx, MarkerKey(), append(body, '\n'), ifMatch) }