| 1 | // Package flowspec turns a legatus Flow into a single declarative table instead |
| 2 | // of imperative builder code. A Spec is a list of Rows; each Row carries the |
| 3 | // conditionality that consumers otherwise hand-roll as control flow: |
| 4 | // |
| 5 | // - Gate — a static feature gate (the `if cfg.X.Enabled` around a block). |
| 6 | // - Over — a fan-out list (the `for _, e := range cfg.Exits` loop). |
| 7 | // - When — a per-host apply-time gate (steps.When), evaluated against the host. |
| 8 | // - Make — the step factory for one item. |
| 9 | // |
| 10 | // Spec implements legatus.FlowSource, so a Spec is handed to Runner.Run exactly |
| 11 | // where a static Flow would be. Resolve lowers the table to a Flow once, before |
| 12 | // any host fan-out, which is why Gate/Over are host-independent and only When |
| 13 | // sees the host. |
| 14 | package flowspec |
| 15 | |
| 16 | import ( |
| 17 | "go.bigb.es/auxilia/legatus" |
| 18 | "go.bigb.es/auxilia/legatus/steps" |
| 19 | ) |
| 20 | |
| 21 | // Row is one entry in a Spec. Every column is optional except Make: |
| 22 | // |
| 23 | // - Gate nil — the row is always included; false drops it (Over/Make unrun). |
| 24 | // - Over nil — a single pass with item == ""; otherwise one pass per element. |
| 25 | // - When nil — the emitted steps are unconditional; otherwise each is wrapped |
| 26 | // in steps.When{Cond: When}. |
| 27 | type Row struct { |
| 28 | Gate func() bool |
| 29 | Over func() []string |
| 30 | When func(*legatus.FlowCtx) bool |
| 31 | Make func(item string) ([]legatus.Step, error) |
| 32 | } |
| 33 | |
| 34 | // Spec is a named, ordered table of Rows that resolves to a legatus.Flow. |
| 35 | type Spec struct { |
| 36 | Name string |
| 37 | Rows []Row |
| 38 | } |
| 39 | |
| 40 | // Resolve lowers the table to a Flow, walking rows in declaration order: a row |
| 41 | // whose Gate is false contributes nothing; otherwise Make runs once per Over item |
| 42 | // (or once with ""), and each produced step is wrapped in steps.When when the row |
| 43 | // has a When gate. The first Make error aborts, returning Flow{Name} (no steps) |
| 44 | // plus the error so the Runner can name the failed flow. |
| 45 | func (s Spec) Resolve() (legatus.Flow, error) { |
| 46 | var out []legatus.Step |
| 47 | for _, row := range s.Rows { |
| 48 | if row.Gate != nil && !row.Gate() { |
| 49 | continue |
| 50 | } |
| 51 | items := []string{""} |
| 52 | if row.Over != nil { |
| 53 | items = row.Over() |
| 54 | } |
| 55 | for _, item := range items { |
| 56 | made, err := row.Make(item) |
| 57 | if err != nil { |
| 58 | return legatus.Flow{Name: s.Name}, err |
| 59 | } |
| 60 | for _, st := range made { |
| 61 | if row.When != nil { |
| 62 | st = steps.When{Cond: row.When, Step: st} |
| 63 | } |
| 64 | out = append(out, st) |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | return legatus.Flow{Name: s.Name, Steps: out}, nil |
| 69 | } |
| 70 | |