carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package appconfig
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7
8 "github.com/carlosframework/platform/internal/release"
9)
10
11// SwapPolicy is an app's swap policy (zero-downtime spec §4.1) with its
12// defaults filled in. It lives in the DEFAULT bundle beside exec_delivery,
13// which is where plan 5's boxes read it; a named environment never carries it.
14type SwapPolicy struct {
15 Swap string // SwapOverlap or SwapHold
16 Concurrency int // 1 or more
17}
18
19// PolicyOf reads m's swap fields with the defaults filled in, the way the
20// box's rollout.PolicyFromManifest does. nil is an app never configured. A
21// stored mode this binary does not know reads as hold: missing or
22// unverifiable policy never permits an overlap (spec §4.2).
23func PolicyOf(m *Manifest) SwapPolicy {
24 p := SwapPolicy{Swap: SwapOverlap, Concurrency: 1}
25 if m == nil {
26 return p
27 }
28 if m.Swap != "" && m.Swap != SwapOverlap {
29 p.Swap = SwapHold
30 }
31 if m.SwapConcurrency > 1 {
32 p.Concurrency = m.SwapConcurrency
33 }
34 return p
35}
36
37// GetSwapPolicy reads the app's policy. No config.json is the defaults, not
38// an error (Get's own convention).
39func GetSwapPolicy(ctx context.Context, s release.Store, acct, app string) (SwapPolicy, error) {
40 m, err := Get(ctx, s, acct, app)
41 if err != nil {
42 return SwapPolicy{}, err
43 }
44 return PolicyOf(m), nil
45}
46
47// SetSwapPolicy writes the policy into the default bundle by the CAS
48// read-modify-write SetExecDeliveryAt uses, and returns what is stored. swap
49// "" and concurrency 0 leave their half alone.
50//
51// Defaults are stored as ABSENT keys, so an app set back to overlap/1 has the
52// bytes it had before. A call that changes nothing writes nothing and touches
53// nothing: setting the defaults on an app with no config.json never creates
54// one. A first non-default policy does create one, holding no vars, and
55// config sync leaves an instance whose config file is absent as it is for
56// such a bundle (renderExecConfig's keepAbsent), so that first write
57// restarts nothing (Astra plan 6 R1 #1).
58func SetSwapPolicy(ctx context.Context, s release.Store, acct, app, swap string, concurrency int) (claimResult0 SwapPolicy, claimResult1 error) {
59 claimResult1 = release.WithAppMutation(ctx, s, acct, app, "appconfig.SetSwapPolicy", func(claimContext context.Context) error {
60 claimResult0, claimResult1 = claimRawSetSwapPolicy(claimContext, s, acct, app, swap, concurrency)
61 return claimResult1
62 })
63 return
64}
65
66// claimRawSetSwapPolicy is the unwrapped implementation of SetSwapPolicy,
67// called from within the WithAppMutation wrapper.
68func claimRawSetSwapPolicy(ctx context.Context, s release.Store, acct, app, swap string, concurrency int) (SwapPolicy, error) {
69 if !release.ValidAccount(acct) {
70 return SwapPolicy{}, fmt.Errorf("config: invalid account %q", acct)
71 }
72 if !release.ValidApp(app) {
73 return SwapPolicy{}, fmt.Errorf("config: invalid app %q", app)
74 }
75 if !ValidSwap(swap) {
76 return SwapPolicy{}, fmt.Errorf("config: invalid swap %q, want %q or %q", swap, SwapOverlap, SwapHold)
77 }
78 if !ValidSwapConcurrency(concurrency) {
79 return SwapPolicy{}, fmt.Errorf("config: invalid swap_concurrency %d, want 1 or more", concurrency)
80 }
81 key := Key(acct, app)
82 m, etag, _, err := getForWriteAt(ctx, s, key, app)
83 if err != nil {
84 return SwapPolicy{}, err
85 }
86 next := m
87 switch swap {
88 case SwapOverlap:
89 next.Swap = ""
90 case SwapHold:
91 next.Swap = SwapHold
92 }
93 switch {
94 case concurrency == 1:
95 next.SwapConcurrency = 0
96 case concurrency > 1:
97 next.SwapConcurrency = concurrency
98 }
99 if next.Swap == m.Swap && next.SwapConcurrency == m.SwapConcurrency {
100 return PolicyOf(&m), nil
101 }
102 next.Schema, next.App = 1, app
103 raw, err := json.MarshalIndent(next, "", " ")
104 if err != nil {
105 return SwapPolicy{}, err
106 }
107 if _, err := s.Put(ctx, key, raw, etag); err != nil {
108 return SwapPolicy{}, err
109 }
110 // A policy change is a config change for the fast loop (apply's doc
111 // comment): best-effort, since the write above has landed.
112 touchLogged(ctx, s, acct, app)
113 return PolicyOf(&next), nil
114}
115