Skip to content

Commit 188f50c

Browse files
committed
2.0.4 wip
1 parent 764c8a7 commit 188f50c

54 files changed

Lines changed: 2510 additions & 1197 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ jobs:
257257
258258
If you are currently using the Python package on PyPI, please migrate to this
259259
release. The Python package is now deprecated and will no longer receive updates:
260-
https://pypi.org/project/stackql-deploy/
260+
https://crates.io/crates/stackql-deploy
261261
262262
The CLI interface is fully compatible — existing `stackql_manifest.yml` files and
263263
project layouts work without modification.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ stackql*.zip
66
stackql*.pkg
77
stackql_history.txt
88
stackql.log
9+
stackql-zip
910
.env
1011
nohup.out
1112
contributors.csv
12-
.claude/
13+
.claude/
14+
nohup.out

CHANGELOG.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,74 @@
11
# Changelog
22

3+
## 2.0.4 (2026-03-18)
4+
5+
### Identifier capture from `exists` queries
6+
7+
The `exists` query can now return a named field (e.g. `vpc_id`) instead of `count`. The returned value is automatically captured as a resource-scoped variable (`{{ this.<field> }}`) and made available to all subsequent queries (`statecheck`, `exports`, `delete`) for that resource. This enables a two-step workflow where `exists` discovers the resource identifier and `statecheck` verifies its properties.
8+
9+
- When `exists` returns `null` or empty for the captured field, the resource is treated as non-existent
10+
- Multiple rows from an `exists` (identifier pattern) or `exports` query is now a fatal error
11+
- After a `create`, the `exists` query is automatically re-run to capture the identifier for use in post-deploy `statecheck` and `exports` queries
12+
13+
### `RETURNING *` identifier capture
14+
15+
When a `create` statement includes `RETURNING *` and the response contains an `Identifier` field, it is automatically injected as `this.identifier` — skipping the post-create `exists` re-run and saving an API call per resource.
16+
17+
### `return_vals` manifest field
18+
19+
New optional `return_vals` field on resources to explicitly map fields from `RETURNING *` responses to resource-scoped variables:
20+
21+
```yaml
22+
return_vals:
23+
create:
24+
- Identifier: identifier # rename pattern
25+
- ErrorCode # direct capture
26+
```
27+
28+
If `return_vals` is specified but the field is missing from the response, the build fails.
29+
30+
### `to_aws_tag_filters` template filter
31+
32+
New AWS-specific Tera filter that converts `global_tags` (list of `Key`/`Value` pairs) to the AWS Resource Groups Tagging API `TagFilters` format:
33+
34+
```sql
35+
AND TagFilters = '{{ global_tags | to_aws_tag_filters }}'
36+
```
37+
38+
### YAML type preservation fix
39+
40+
Fixed an issue where YAML string values that look like numbers (e.g. `IpProtocol: "-1"`) were being coerced to integers during JSON serialization. String types declared in YAML are now preserved through to the rendered query.
41+
42+
### Teardown improvements
43+
44+
- Teardown no longer retries exports queries that return empty results — missing exports are set to `<unknown>` and teardown continues best-effort
45+
- Post-delete existence checks accept the first empty response instead of retrying, reducing teardown time significantly
46+
47+
### AWS starter template updated
48+
49+
The `stackql-deploy init --provider aws` starter template now uses:
50+
- `awscc` (Cloud Control) provider instead of `aws`
51+
- CTE + INNER JOIN exists pattern with `to_aws_tag_filters`
52+
- `AWS_POLICY_EQUAL` for statecheck tag comparison
53+
- `this.<field>` identifier capture pattern
54+
- `RETURNING *` on create statements
55+
- `stackql:stack-name` / `stackql:stack-env` / `stackql:resource-name` tag taxonomy
56+
57+
### AWS VPC Web Server example
58+
59+
Complete rewrite of the `examples/aws/aws-vpc-webserver` stack (renamed from `aws-stack`) using the `awscc` provider exclusively. Includes 10 resources demonstrating all query patterns: tag-based discovery, identifier capture, property-level statechecks, PatchDocument updates, and the `to_aws_tag_filters` filter.
60+
61+
### Patch Document Test example
62+
63+
New `examples/aws/patch-doc-test` example demonstrating the Cloud Control API `UPDATE` workflow with `PatchDocument` — deploy an S3 bucket, modify its versioning config in the manifest, and re-deploy to apply the update.
64+
65+
### Other changes
66+
67+
- Fixed `init` command missing `--env` argument (defaulting to `dev`)
68+
- Added `debug` log import to build command
69+
- Debug logging now shows full `RETURNING *` payloads
70+
- Documentation updates: `resource-query-files.md`, `template-filters.md`, `manifest-file.md`, and AWS template library
71+
372
## 2.0.0 (2026-03-14)
473

574
### Initial Rust Release

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stackql-deploy"
3-
version = "2.0.3"
3+
version = "2.0.4"
44
edition = "2021"
55
rust-version = "1.75"
66
description = "Infrastructure-as-code framework for declarative cloud resource management using StackQL"

docs/flows.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# Resource Processing Flows
2+
3+
This document describes every code path in the `build`, `test`, and `teardown` commands based on which anchors are present in a resource's `.iql` file.
4+
5+
## Anchor Reference
6+
7+
| Anchor | Purpose |
8+
|--------|---------|
9+
| `exists` | Check if resource exists (returns `count` or a named field) |
10+
| `statecheck` | Verify resource properties match desired state |
11+
| `create` | Create the resource |
12+
| `update` | Update the resource (patch document) |
13+
| `createorupdate` | Always execute (skip exists/statecheck) |
14+
| `exports` | Extract values for downstream resources |
15+
| `delete` | Remove the resource |
16+
17+
## Exists Query Variants
18+
19+
The `exists` query has two modes based on the returned column name:
20+
21+
**Count mode** — returns `count`, existence is `count > 0`:
22+
```sql
23+
SELECT count(*) as count FROM awscc.s3.buckets WHERE Identifier = '{{ bucket_name }}' AND region = '{{ region }}';
24+
```
25+
26+
**Field capture mode** — returns a named field (e.g. `vpc_id`), captured as `this.<field>` for downstream queries:
27+
```sql
28+
SELECT split_part(ResourceARN, '/', 2) as vpc_id FROM awscc.tagging.tagged_resources WHERE ...;
29+
```
30+
31+
When `null` or empty is returned in field capture mode, the resource is treated as non-existent.
32+
33+
---
34+
35+
## Build Flows
36+
37+
### Flow A: `createorupdate` + `exports`
38+
39+
Used for resources that should always be applied (e.g. routes, gateway attachments).
40+
41+
```mermaid
42+
graph LR
43+
A[createorupdate] --> B[exports proxy?]
44+
B -->|rows returned| C[done ✓]
45+
B -->|no rows| D[FAIL ✗]
46+
```
47+
48+
**Anchors:** `createorupdate`, optionally `exports`
49+
**Examples:** `example_inet_route`, `example_inet_gw_attachment` (old pattern)
50+
51+
---
52+
53+
### Flow B: `exists`(count) + `statecheck` + `create`/`update` + `exports`
54+
55+
Classic pattern for resources identified by a known name/identifier.
56+
57+
```mermaid
58+
graph LR
59+
A[exists count] -->|0| B[create]
60+
A -->|>0| C[statecheck]
61+
C -->|pass| D[exports]
62+
C -->|fail| E[update]
63+
B --> F[post-create exists]
64+
F --> G[statecheck]
65+
G --> D
66+
E --> H[post-update exists]
67+
H --> I[statecheck]
68+
I --> D
69+
D --> J[done ✓]
70+
```
71+
72+
**Anchors:** `exists`(count), `statecheck`, `create`, optionally `update`, optionally `exports`
73+
**Examples:** `databricks_account_credentials`, `aws_s3_workspace_bucket_policy`
74+
75+
---
76+
77+
### Flow C: `exists`(field) + `statecheck` + `create` + `exports`
78+
79+
Pattern for awscc resources using tagging for identification. The exists query returns a resource-specific field (e.g. `vpc_id`) which is captured as `this.<field>` and used in statecheck and exports.
80+
81+
```mermaid
82+
graph LR
83+
A[exists field] -->|null| B[create]
84+
A -->|value| C["this.field = value"]
85+
C --> D["statecheck(this.field)"]
86+
D -->|pass| E["exports(this.field)"]
87+
D -->|fail| F["update(this.field)"]
88+
B --> G[post-create exists]
89+
G --> H["this.field = value"]
90+
H --> I["statecheck(this.field)"]
91+
I --> E
92+
F --> J[post-update exists]
93+
J --> K["statecheck(this.field)"]
94+
K --> E
95+
E --> L[done ✓]
96+
```
97+
98+
**Anchors:** `exists`(field), `statecheck`, `create`, optionally `update`, `exports`
99+
**Examples:** `example_vpc`, `example_subnet`, `example_inet_gateway`, `example_route_table`, `example_security_group`, `example_web_server`
100+
101+
---
102+
103+
### Flow D: `exists`(count) + `exports`-as-proxy (no statecheck)
104+
105+
The exports query doubles as a statecheck — if it returns rows, the resource is in the desired state.
106+
107+
```mermaid
108+
graph LR
109+
A[exists count] -->|0| B[create]
110+
A -->|>0| C[exports proxy]
111+
C -->|rows returned| D[done ✓]
112+
C -->|no rows| E[update]
113+
B --> F[post-create exists]
114+
F --> G[exports proxy]
115+
G --> D
116+
E --> H[post-update exists]
117+
H --> I[exports proxy]
118+
I --> D
119+
```
120+
121+
**Anchors:** `exists`(count), `create`, optionally `update`, `exports`
122+
**Examples:** `aws_s3_workspace_bucket`, `databricks_storage_configuration`
123+
124+
---
125+
126+
### Flow E: `exists`(field) + `exports` (no statecheck)
127+
128+
Field capture from exists, exports uses `this.<field>`. Exports acts as statecheck proxy.
129+
130+
```mermaid
131+
graph LR
132+
A[exists field] -->|null| B[create]
133+
A -->|value| C["this.field = value"]
134+
C --> D["exports proxy(this.field)"]
135+
D -->|rows returned| E[done ✓]
136+
D -->|no rows| F[update]
137+
B --> G[post-create exists]
138+
G --> H["this.field = value"]
139+
H --> I["exports proxy(this.field)"]
140+
I --> E
141+
```
142+
143+
**Anchors:** `exists`(field), `create`, `exports`
144+
**Examples:** `example_subnet_rt_assn`
145+
146+
---
147+
148+
### Flow F: `exists`(field) only (no statecheck, no exports)
149+
150+
Minimal pattern — exists confirms the resource, the captured field is the only output.
151+
152+
```mermaid
153+
graph LR
154+
A[exists field] -->|null| B[create]
155+
A -->|value| C[done ✓]
156+
B --> D[post-create exists]
157+
D -->|value| C
158+
```
159+
160+
**Anchors:** `exists`(field), `create`
161+
**Examples:** Simple resources where existence is sufficient
162+
163+
---
164+
165+
## Test Flows
166+
167+
Test runs the same exists → statecheck/exports-proxy sequence as build, but never creates or updates. If a resource doesn't exist or fails statecheck, the test fails.
168+
169+
```mermaid
170+
graph LR
171+
A[exists] -->|not found| B[FAIL ✗]
172+
A -->|found| C[statecheck or exports proxy]
173+
C -->|pass| D[exports]
174+
C -->|fail| B
175+
D --> E[done ✓]
176+
```
177+
178+
---
179+
180+
## Teardown Flows
181+
182+
Teardown processes resources in reverse manifest order. First collects all exports (running exists to capture `this.*` fields), then deletes.
183+
184+
```mermaid
185+
graph LR
186+
A[collect exports phase] --> B[exists per resource]
187+
B --> C[exports per resource]
188+
C --> D[reverse order delete phase]
189+
D --> E[exists check]
190+
E -->|found| F[delete]
191+
E -->|not found| G[skip]
192+
F --> H[post-delete exists]
193+
H -->|gone| I[done ✓]
194+
H -->|still there| I
195+
```
196+
197+
During teardown export collection, missing exports are set to `<unknown>` rather than failing — the stack may be partially deployed.
198+
199+
---
200+
201+
## `this.*` Variable Lifecycle
202+
203+
When an exists query returns a named field (not `count`), the value is captured as a resource-scoped variable:
204+
205+
| Phase | Variable | Available to |
206+
|-------|----------|-------------|
207+
| exists returns `vpc_id` | `this.vpc_id``example_vpc.vpc_id` | statecheck, exports, delete for this resource |
208+
| exports returns `vpc_id` | `vpc_id` and `example_vpc.vpc_id` | all subsequent resources |
209+
210+
The `this.*` prefix is syntactic sugar — `{{ this.vpc_id }}` in `example_vpc`'s queries is preprocessed to `{{ example_vpc.vpc_id }}`.
211+
212+
---
213+
214+
## RETURNING * Optimization
215+
216+
When a `create` query includes `RETURNING *`, the Cloud Control API returns the operation metadata immediately. If `return_vals` is configured in the manifest, specified fields are captured as `this.*` variables, eliminating the need for a post-create exists re-run.
217+
218+
```yaml
219+
resources:
220+
- name: example_vpc
221+
return_vals:
222+
create:
223+
- Identifier: vpc_id # rename Identifier → this.vpc_id
224+
- ErrorCode # capture as this.ErrorCode
225+
```

0 commit comments

Comments
 (0)