| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "log" |
| 5 | "sync" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/release" |
| 8 | "github.com/carlosframework/platform/internal/rollout" |
| 9 | ) |
| 10 | |
| 11 | // updatesPassOption arms the instance update settings on an adoption pass |
| 12 | // (settings spec §4). root is set on carlos-adopt's pass only: root resolves |
| 13 | // install requests and writes the consumed file; the agent's passes apply |
| 14 | // the same decisions and write nothing. |
| 15 | // |
| 16 | // The failure book is the zero-downtime plans' (§4.3: failure records |
| 17 | // outrank every rule), read from dd's swap-failures.json at the start of |
| 18 | // every pass, never once per process: the fast loop's options are built once |
| 19 | // per agent run. A build is failed for a host when the book blocks it |
| 20 | // (Blocks: a record the host has not served or been retried past) or holds |
| 21 | // the host down on it (HeldDown: a hold attempt onto it failed there). An |
| 22 | // unreadable book holds every route with settings and every managed route |
| 23 | // (rollout.Managed), as refuseManaged does; a default unmanaged route keeps |
| 24 | // today's adoption, which never read the book. |
| 25 | // |
| 26 | // The pass prints the unreadable-book line on root's pass only. The agent's |
| 27 | // opener logs it here once per distinct error, since the fast loop runs a |
| 28 | // pass every few seconds with options built once. |
| 29 | func updatesPassOption(dd string, root bool, providers ...release.RootUpdateOpener) release.PassOption { |
| 30 | var ( |
| 31 | mu sync.Mutex |
| 32 | lastErr string |
| 33 | ) |
| 34 | open := func() (release.FailedBuild, error) { |
| 35 | book, err := rollout.OpenFailureBook(dd) |
| 36 | mu.Lock() |
| 37 | said := "" |
| 38 | if err != nil { |
| 39 | said = err.Error() |
| 40 | } |
| 41 | if !root && err != nil && said != lastErr { |
| 42 | log.Printf("agent: failure book unreadable (%v): routes with update settings and managed routes install nothing", err) |
| 43 | } |
| 44 | lastErr = said |
| 45 | mu.Unlock() |
| 46 | if err != nil { |
| 47 | return nil, err |
| 48 | } |
| 49 | return func(acct, app, build, host string, served bool) bool { |
| 50 | return book.Blocks(acct, app, build, host, served) || book.HeldDown(acct, app, build, host) |
| 51 | }, nil |
| 52 | } |
| 53 | legacy := release.WithUpdateSettings(open, rollout.Managed, root) |
| 54 | if !root { |
| 55 | return legacy |
| 56 | } |
| 57 | provider := productionRootUpdates(dd) |
| 58 | if len(providers) == 1 { |
| 59 | provider = providers[0] |
| 60 | } |
| 61 | return release.CombinePassOptions(legacy, release.WithRootUpdateInputs(provider)) |
| 62 | } |
| 63 | |