Skip to content

Commit a380581

Browse files
intel352claude
andcommitted
Add wfctl infra bootstrap design doc
New wfctl capabilities: infra bootstrap command, cross-resource output wiring ({{ outputs.resource.key }}), pluggable secrets provider (GitHub, Vault, AWS, env), sensitive value masking in state/logs/plan output, auto-bootstrap with config toggle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d92457 commit a380581

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
# wfctl Infra Bootstrap, Output Wiring, and Secrets Provider — Design
2+
3+
**Date:** 2026-04-08
4+
**Status:** Approved
5+
6+
## Problem Statement
7+
8+
Infrastructure bootstrapping logic (creating state backends, generating credentials, discovering database URLs, wiring outputs between resources) currently lives in CI shell scripts (GitHub Actions). This locks the deployment process to a specific CI provider. All infrastructure intelligence should live in `wfctl` so the same commands work on any CI system or locally.
9+
10+
## Solution
11+
12+
Three new capabilities in the workflow engine:
13+
14+
1. **`wfctl infra bootstrap`** — creates state backend, generates credentials and secrets
15+
2. **Output wiring**`{{ outputs.resource.key }}` template syntax for cross-resource references
16+
3. **Secrets provider integration** — pluggable secrets storage (GitHub, Vault, AWS, env) with sensitive value protection
17+
18+
## 1. `wfctl infra bootstrap`
19+
20+
New command that prepares the infrastructure state backend and generates required credentials.
21+
22+
```bash
23+
wfctl infra bootstrap --config infra/staging.yaml
24+
```
25+
26+
**Behavior:**
27+
1. Reads `iac.state` module config
28+
2. Creates state backend (e.g., DO Spaces bucket) if missing — uses provider API
29+
3. Generates state backend credentials (e.g., Spaces access keys) if not available
30+
4. Generates application secrets from `secrets.generate` config
31+
5. Stores credentials/secrets via configured `secrets.provider`
32+
6. Idempotent — skips creation/generation if already exists
33+
34+
**Auto-bootstrap in apply:**
35+
36+
```yaml
37+
infra:
38+
auto_bootstrap: true # default: true
39+
```
40+
41+
When `true`, `wfctl infra apply` runs bootstrap logic before applying. When `false`, errors if state backend missing. Users can explicitly set `false` and run `wfctl infra bootstrap` manually.
42+
43+
## 2. Output Wiring — `{{ outputs.resource.key }}`
44+
45+
Cross-resource output references in infra YAML:
46+
47+
```yaml
48+
- name: staging-app
49+
type: infra.container_service
50+
config:
51+
env_vars:
52+
DATABASE_URL: "{{ outputs.staging-db.uri }}"
53+
JWT_SECRET: "{{ secrets.jwt_secret }}"
54+
```
55+
56+
**Resolution flow:**
57+
1. Parse YAML, detect `{{ outputs.* }}` and `{{ secrets.* }}` references
58+
2. Auto-infer dependencies (staging-app depends on staging-db)
59+
3. Topological sort: apply resources in dependency order
60+
4. After each resource applies, store outputs in state
61+
5. Before applying next resource, resolve template expressions from accumulated outputs
62+
6. Template resolution happens in-memory only — never written to YAML files
63+
64+
**Implementation:** In `parseInfraResourceSpecs()` (`cmd/wfctl/infra.go`), scan config values for template expressions, build dependency graph, resolve after each apply step.
65+
66+
## 3. Secrets Provider Integration
67+
68+
Uses existing `secrets.Provider` interface:
69+
70+
```go
71+
type Provider interface {
72+
Name() string
73+
Get(ctx context.Context, key string) (string, error)
74+
Set(ctx context.Context, key, value string) error
75+
Delete(ctx context.Context, key string) error
76+
List(ctx context.Context) ([]string, error)
77+
}
78+
```
79+
80+
**Existing providers:** EnvProvider, AWSSecretsManagerProvider, VaultProvider
81+
82+
**New provider:** GitHubSecretsProvider — stores/retrieves secrets via GitHub repository secrets API.
83+
84+
**Config:**
85+
86+
```yaml
87+
secrets:
88+
provider: github
89+
config:
90+
repo: GoCodeAlone/workflow-dnd
91+
token_env: GH_MANAGEMENT_TOKEN
92+
generate:
93+
- key: jwt_secret
94+
type: random_hex
95+
length: 32
96+
- key: spaces_access_key
97+
type: provider_credential
98+
source: digitalocean.spaces
99+
```
100+
101+
**Secret generators:**
102+
- `random_hex` — `crypto/rand` hex string of specified length
103+
- `random_base64` — base64-encoded random bytes
104+
- `provider_credential` — calls provider API to generate credentials (e.g., DO Spaces keys)
105+
106+
## 4. Sensitive Value Protection
107+
108+
Following Terraform/Pulumi best practices:
109+
110+
### State Files
111+
- Sensitive outputs marked with `sensitive: true` in `ResourceOutput`
112+
- Stored in state as encrypted references, not plaintext
113+
- State file format: `{"uri": "secret://state/staging-db/uri"}` — the actual value is stored separately in the secrets provider or encrypted in-place
114+
115+
### Logs and Plan Output
116+
- All sensitive values automatically masked with `(sensitive)` in:
117+
- `wfctl infra plan` output (table and markdown formats)
118+
- `wfctl infra apply` progress logs
119+
- `wfctl infra status` output
120+
- Masking applied by checking `ResourceOutput.Sensitive` flag and `secrets.*` config values
121+
122+
### Template Resolution
123+
- `{{ outputs.staging-db.uri }}` resolves in-memory at apply time
124+
- The resolved value is passed to the provider's Create/Update API but never logged
125+
- Pipeline step outputs containing sensitive values are marked and masked in step output logs
126+
127+
### Implementation
128+
- Add `Sensitive bool` field to `interfaces.ResourceOutput` (and `platform.ResourceOutput`)
129+
- Add `SensitiveKeys []string` to `ResourceDriver` interface (drivers declare which output keys are sensitive — e.g., database driver declares `uri`, `password`)
130+
- Add masking middleware in `cmd/wfctl/infra.go` plan/apply formatters
131+
- Add `--show-sensitive` flag for debugging (off by default, requires explicit opt-in)
132+
133+
## 5. Updated Infra YAML Example
134+
135+
```yaml
136+
infra:
137+
auto_bootstrap: true
138+
139+
secrets:
140+
provider: github
141+
config:
142+
repo: GoCodeAlone/workflow-dnd
143+
token_env: GH_MANAGEMENT_TOKEN
144+
generate:
145+
- key: jwt_secret
146+
type: random_hex
147+
length: 32
148+
149+
modules:
150+
- name: do-provider
151+
type: iac.provider
152+
config:
153+
provider: digitalocean
154+
credentials: env
155+
region: nyc3
156+
157+
- name: iac-state
158+
type: iac.state
159+
config:
160+
backend: spaces
161+
region: nyc3
162+
bucket: dnd-iac-state
163+
prefix: staging/
164+
165+
- name: staging-vpc
166+
type: infra.vpc
167+
config:
168+
name: dnd-staging-vpc
169+
cidr: "10.10.0.0/16"
170+
provider: do-provider
171+
172+
- name: staging-firewall
173+
type: infra.firewall
174+
config:
175+
name: dnd-staging-firewall
176+
provider: do-provider
177+
inbound_rules:
178+
- protocol: tcp
179+
ports: "8180"
180+
sources: ["75.61.150.38"]
181+
182+
- name: staging-registry
183+
type: infra.registry
184+
config:
185+
name: dnd-registry
186+
tier: basic
187+
provider: do-provider
188+
189+
- name: staging-db
190+
type: infra.database
191+
config:
192+
name: dnd-staging-db
193+
engine: pg
194+
version: "16"
195+
size: db-s-1vcpu-1gb
196+
provider: do-provider
197+
198+
- name: staging-app
199+
type: infra.container_service
200+
config:
201+
name: dnd-staging
202+
image: registry.digitalocean.com/dnd-registry/dnd-server:latest
203+
http_port: 8180
204+
instance_count: 1
205+
provider: do-provider
206+
env_vars:
207+
DATABASE_URL: "{{ outputs.staging-db.uri }}"
208+
SESSION_STORE: "pg"
209+
JWT_SECRET: "{{ secrets.jwt_secret }}"
210+
SINGLE_PORT: "true"
211+
GRPC_PORT: "8180"
212+
213+
pipelines:
214+
apply:
215+
steps:
216+
- name: apply_vpc
217+
type: step.iac_apply
218+
config:
219+
platform: staging-vpc
220+
state_store: iac-state
221+
- name: apply_firewall
222+
type: step.iac_apply
223+
config:
224+
platform: staging-firewall
225+
state_store: iac-state
226+
- name: apply_registry
227+
type: step.iac_apply
228+
config:
229+
platform: staging-registry
230+
state_store: iac-state
231+
- name: apply_db
232+
type: step.iac_apply
233+
config:
234+
platform: staging-db
235+
state_store: iac-state
236+
- name: apply_app
237+
type: step.iac_apply
238+
config:
239+
platform: staging-app
240+
state_store: iac-state
241+
```
242+
243+
## 6. Simplified CI
244+
245+
After implementation, any CI system needs only:
246+
247+
```yaml
248+
# GitHub Actions
249+
- uses: GoCodeAlone/setup-wfctl@v1
250+
- run: wfctl infra bootstrap -c infra/staging.yaml
251+
env:
252+
DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
253+
GH_MANAGEMENT_TOKEN: ${{ secrets.GH_MANAGEMENT_TOKEN }}
254+
- run: wfctl infra apply -c infra/staging.yaml -y
255+
env:
256+
DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
257+
258+
# GitLab CI (identical commands)
259+
script:
260+
- wfctl infra bootstrap -c infra/staging.yaml
261+
- wfctl infra apply -c infra/staging.yaml -y
262+
```
263+
264+
Required secrets: **2** (DIGITALOCEAN_TOKEN + GH_MANAGEMENT_TOKEN). Everything else auto-generated.
265+
266+
## 7. Implementation Scope
267+
268+
| Component | Repo | New/Modify |
269+
|---|---|---|
270+
| `wfctl infra bootstrap` command | workflow | New in `cmd/wfctl/infra.go` |
271+
| Auto-bootstrap in apply | workflow | Modify `cmd/wfctl/infra.go` |
272+
| Output template resolution | workflow | New in `cmd/wfctl/infra.go` |
273+
| Auto-dependency inference | workflow | Modify `cmd/wfctl/infra.go` |
274+
| Secrets config parsing | workflow | New in `cmd/wfctl/infra.go` |
275+
| `{{ secrets.* }}` resolution | workflow | New in `cmd/wfctl/infra.go` |
276+
| Secret generators | workflow | New `secrets/generators.go` |
277+
| GitHubSecretsProvider | workflow | New `secrets/github_provider.go` |
278+
| Sensitive output masking | workflow | Modify `cmd/wfctl/infra.go`, `interfaces/iac_provider.go` |
279+
| `Sensitive` field on ResourceOutput | workflow | Modify `interfaces/iac_provider.go` |
280+
| `SensitiveKeys` on ResourceDriver | workflow | Modify `interfaces/iac_resource_driver.go` |
281+
| DO database driver sensitive keys | workflow-plugin-digitalocean | Modify `drivers/database.go` |
282+
| Update deploy.yml to use wfctl | workflow-dnd | Modify `.github/workflows/deploy.yml` |
283+
| Update infra/staging.yaml | workflow-dnd | Modify `infra/staging.yaml` |
284+
| Update docs/DEPLOYMENT.md | workflow-dnd | Modify `docs/DEPLOYMENT.md` |

0 commit comments

Comments
 (0)