Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## 2.0.6 (2026-03-28)

### Fixes

- Fixed eager rendering of `statecheck` queries that caused hard failures when `this.*` variables were not yet available (e.g. post-create exists re-run fails due to eventual consistency). `statecheck` now uses JIT rendering like `exports`, deferring gracefully when template variables are unresolved.
- When a deferred `statecheck` cannot be rendered post-deploy, the build falls through to `exports`-as-proxy validation or accepts the create/update based on successful execution.
- Applied the same fix to `teardown`, where `statecheck` used as an exists fallback would crash on unresolved variables instead of skipping the resource.
- Fixed `--dry-run` failures for resources that depend on exports from upstream resources. `create` and `update` query rendering now defers gracefully in dry-run mode when upstream exports are unavailable, and placeholder (`<evaluated>`) values are injected for unresolved exports so downstream resources can still render.
- When a post-create exists re-run fails to find a newly created resource (eventual consistency), the exists query is automatically retried using the `statecheck` retry settings if available, giving async providers time to make the resource discoverable.

### Features

- New optional `troubleshoot` IQL anchor for post-failure diagnostics. When a `build` post-deploy check fails or a `teardown` delete cannot be confirmed, a user-defined diagnostic query is automatically rendered and executed, with results logged as pretty-printed JSON. Supports operation-specific variants (`troubleshoot:create`, `troubleshoot:update`, `troubleshoot:delete`) with fallback to a generic `troubleshoot` anchor. Typically used with `return_vals` to capture an async operation handle (e.g. `RequestToken`) from `RETURNING *` and query its status via `{{ this.<field> }}`. See [resource query files documentation](https://stackql-deploy.io/docs/resource-query-files#troubleshoot) for details.
- The `RETURNING *` log message (`storing RETURNING * result...`) is now logged at `debug` level instead of `info`.

## 2.0.5 (2026-03-24)

### Fixes

- Network and authentication errors (DNS failures, 401/403 responses) are now detected early and surfaced as fatal errors instead of being silently retried.
- Unresolved template variables are caught at render time with a clear error message identifying the missing variable and source template.
- `command` type resources now log query output when using `RETURNING` clauses, matching the behavior of `resource` types.
- Stack level exports (`stack_name`, `stack_env`) are now set as scoped environment variables on the host system for use by external tooling.

## 2.0.4 (2026-03-18)

### Identifier capture from `exists` queries
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stackql-deploy"
version = "2.0.5"
version = "2.0.6"
edition = "2021"
rust-version = "1.75"
description = "Infrastructure-as-code framework for declarative cloud resource management using StackQL"
Expand Down
9 changes: 9 additions & 0 deletions examples/aws/aws-vpc-webserver/stackql_manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ resources:
exports:
- vpc_id
- vpc_cidr_block

- name: example_subnet
props:
- name: subnet_cidr_block
Expand All @@ -53,6 +54,7 @@ resources:
exports:
- subnet_id
- availability_zone

- name: example_inet_gateway
props:
- name: inet_gateway_tags
Expand All @@ -62,8 +64,10 @@ resources:
merge: ['global_tags']
exports:
- internet_gateway_id

- name: example_inet_gw_attachment
props: []

- name: example_route_table
props:
- name: route_table_tags
Expand All @@ -73,12 +77,15 @@ resources:
merge: ['global_tags']
exports:
- route_table_id

- name: example_subnet_rt_assn
props: []
exports:
- subnet_route_table_assn_id

- name: example_inet_route
props: []

- name: example_security_group
props:
- name: group_description
Expand Down Expand Up @@ -111,6 +118,7 @@ resources:
IpProtocol: "-1"
exports:
- security_group_id

- name: example_web_server
props:
- name: ami_id
Expand Down Expand Up @@ -141,6 +149,7 @@ resources:
- Identifier: instance_id
exports:
- instance_id

- name: get_web_server_url
type: query
props: []
Expand Down
62 changes: 62 additions & 0 deletions examples/aws/sqlserver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# `stackql-deploy` starter project for `aws`

> for starter projects using other providers, try `stackql-deploy init infrastructure/sqlserver/ --provider=azure` or `stackql-deploy init infrastructure/sqlserver/ --provider=google`

see the following links for more information on `stackql`, `stackql-deploy` and the `awscc` provider:

- [`awscc` provider docs](https://awscc.stackql.io/providers/awscc/)
- [`stackql`](https://github.com/stackql/stackql)
- [`stackql-deploy` GitHub repo](https://github.com/stackql/stackql-deploy-rs)

## Overview

__`stackql-deploy`__ is a stateless, declarative, SQL driven Infrastructure-as-Code (IaC) framework. There is no state file required as the current state is assessed for each resource at runtime. __`stackql-deploy`__ is capable of provisioning, deprovisioning and testing a stack which can include resources across different providers, like a stack spanning `aws` and `azure` for example.

## Prerequisites

This example requires `stackql-deploy` to be installed. The host used to run `stackql-deploy` needs the necessary environment variables set to authenticate to your specific provider, in the case of `aws`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and optionally `AWS_SESSION_TOKEN` must be set, for more information on authentication to `aws` see the [`awscc` provider documentation](https://awscc.stackql.io/providers/awscc/).

## Usage

Adjust the values in the [__`stackql_manifest.yml`__](stackql_manifest.yml) file if desired. The [__`stackql_manifest.yml`__](stackql_manifest.yml) file contains resource configuration variables to support multiple deployment environments, these will be used for `stackql` queries in the `resources` folder.

The syntax for the `stackql-deploy` command is as follows:

```bash
stackql-deploy { build | test | teardown } { stack-directory } { deployment environment} [ optional flags ]
```

### Deploying a stack

For example, to deploy the stack named infrastructure/sqlserver/ to an environment labeled `sit`, run the following:

```bash
target/release/stackql-deploy build examples/aws/sqlserver dev \
-e DB_MASTER_PASSWORD=${DB_MASTER_PASSWORD}
```

Use the `--dry-run` flag to view the queries to be run without actually running them, for example:

```bash
stackql-deploy build infrastructure/sqlserver/ sit \
-e AWS_REGION=us-east-1 \
--dry-run
```

### Testing a stack

To test a stack to ensure that all resources are present and in the desired state, run the following (in our `sit` deployment example):

```bash
stackql-deploy test infrastructure/sqlserver/ sit \
-e AWS_REGION=us-east-1
```

### Tearing down a stack

To destroy or deprovision all resources in a stack for our `sit` deployment example, run the following:

```bash
stackql-deploy teardown infrastructure/sqlserver/ sit \
-e AWS_REGION=us-east-1
```
82 changes: 82 additions & 0 deletions examples/aws/sqlserver/resources/db_instance.iql
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*+ exists */
SELECT count(*) as count
FROM awscc.rds.db_instances
WHERE region = '{{ region }}'
AND Identifier = '{{ db_instance_identifier }}'

/*+ create */
INSERT INTO awscc.rds.db_instances (
AllocatedStorage,
DBInstanceClass,
DBInstanceIdentifier,
DBSubnetGroupName,
Engine,
EngineVersion,
LicenseModel,
MasterUsername,
MasterUserPassword,
MultiAZ,
BackupRetentionPeriod,
PubliclyAccessible,
StorageType,
Tags,
VPCSecurityGroups,
region
)
SELECT
'{{ allocated_storage }}',
'{{ db_instance_class }}',
'{{ db_instance_identifier }}',
'{{ db_subnet_group_name }}',
'{{ engine }}',
'{{ engine_version }}',
'{{ license_model }}',
'{{ master_username }}',
'{{ master_user_password }}',
{{ multi_az }},
{{ backup_retention_period }},
{{ publicly_accessible }},
'{{ storage_type }}',
'{{ tags }}',
'{{ vpc_security_groups }}',
'{{ region }}'
RETURNING *

/*+ update */
UPDATE awscc.rds.db_instances
SET PatchDocument = string('{{ {
"AllocatedStorage": allocated_storage,
"DBInstanceClass": db_instance_class,
"BackupRetentionPeriod": backup_retention_period,
"MasterUserPassword": master_user_password,
"MultiAZ": multi_az,
"PubliclyAccessible": publicly_accessible,
"StorageType": storage_type,
"Tags": tags,
"VPCSecurityGroups": vpc_security_groups
} | generate_patch_document }}')
WHERE region = '{{ region }}'
AND Identifier = '{{ db_instance_identifier }}'

/*+ statecheck, retries=20, retry_delay=30 */
SELECT count(*) as count
FROM awscc.rds.db_instances
WHERE region = '{{ region }}'
AND Identifier = '{{ db_instance_identifier }}'

/*+ exports */
SELECT endpoint as db_endpoint
FROM awscc.rds.db_instances
WHERE region = '{{ region }}'
AND Identifier = '{{ db_instance_identifier }}'

/*+ troubleshoot:create */
SELECT OperationStatus, StatusMessage, ErrorCode
FROM awscc.cloud_control.resource_request
WHERE RequestToken = '{{ this.RequestToken }}'
AND region = '{{ region }}';

/*+ delete */
DELETE FROM awscc.rds.db_instances
WHERE region = '{{ region }}'
AND Identifier = '{{ db_instance_identifier }}'
62 changes: 62 additions & 0 deletions examples/aws/sqlserver/resources/db_subnet_group.iql
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*+ exists */
SELECT count(*) as count
FROM awscc.rds.db_subnet_groups
WHERE region = '{{ region }}' AND
Identifier = '{{ db_subnet_group_name }}'
;

/*+ create */
INSERT INTO awscc.rds.db_subnet_groups (
DBSubnetGroupDescription,
DBSubnetGroupName,
SubnetIds,
Tags,
region
)
SELECT
'{{ db_subnet_group_description }}',
'{{ db_subnet_group_name }}',
'{{ subnet_ids }}',
'{{ tags }}',
'{{ region }}'
RETURNING *;

/*+ update */
UPDATE awscc.rds.db_subnet_groups
SET PatchDocument = string('{{ {
"DBSubnetGroupDescription": db_subnet_group_description,
"SubnetIds": subnet_ids,
"Tags": tags
} | generate_patch_document }}')
WHERE
region = '{{ region }}' AND
Identifier = '{{ db_subnet_group_name }}';

/*+ statecheck, retries=5, retry_delay=10 */
SELECT count(*) as count FROM
(
SELECT
AWS_POLICY_EQUAL(subnet_ids, '{{ subnet_ids }}') as test_subnet_ids,
AWS_POLICY_EQUAL(tags, '{{ tags }}') as test_tags
FROM awscc.rds.db_subnet_groups
WHERE
region = '{{ region }}' AND
Identifier = '{{ db_subnet_group_name }}' AND
db_subnet_group_description = '{{ db_subnet_group_description }}'
) t
WHERE test_subnet_ids = 1 AND test_tags = 1;

/*+ troubleshoot:create */
SELECT OperationStatus, StatusMessage, ErrorCode
FROM awscc.cloud_control.resource_request
WHERE RequestToken = '{{ this.RequestToken }}'
AND region = '{{ region }}';

/*+ exports */
SELECT '{{ db_subnet_group_name }}' as db_subnet_group_name;

/*+ delete */
DELETE FROM awscc.rds.db_subnet_groups
WHERE
Identifier = '{{ db_subnet_group_name }}' AND
region = '{{ region }}';
48 changes: 48 additions & 0 deletions examples/aws/sqlserver/resources/inet_gateway.iql
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*+ exists */
WITH tagged_resources AS
(
SELECT split_part(ResourceARN, '/', 2) as internet_gateway_id
FROM awscc.tagging.tagged_resources
WHERE region = '{{ region }}'
AND TagFilters = '{{ global_tags | to_aws_tag_filters }}'
AND ResourceTypeFilters = '["ec2:internet-gateway"]'
),
internet_gateways AS
(
SELECT internet_gateway_id
FROM awscc.ec2.internet_gateways_list_only
WHERE region = '{{ region }}'
)
SELECT r.internet_gateway_id
FROM internet_gateways r
INNER JOIN tagged_resources tr
ON r.internet_gateway_id = tr.internet_gateway_id;

/*+ statecheck, retries=5, retry_delay=5 */
SELECT COUNT(*) as count FROM
(
SELECT
AWS_POLICY_EQUAL(tags, '{{ inet_gateway_tags }}') as test_tags
FROM awscc.ec2.internet_gateways
WHERE Identifier = '{{ this.internet_gateway_id }}'
AND region = '{{ region }}'
AND test_tags = 1
) t;

/*+ create */
INSERT INTO awscc.ec2.internet_gateways (
Tags,
region
)
SELECT
'{{ inet_gateway_tags }}',
'{{ region }}'
RETURNING *;

/*+ exports */
SELECT '{{ this.internet_gateway_id }}' as internet_gateway_id;

/*+ delete */
DELETE FROM awscc.ec2.internet_gateways
WHERE Identifier = '{{ internet_gateway_id }}'
AND region = '{{ region }}';
28 changes: 28 additions & 0 deletions examples/aws/sqlserver/resources/inet_gw_attachment.iql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*+ exists */
SELECT count(*) as count
FROM awscc.ec2.vpc_gateway_attachments
WHERE Identifier = 'IGW|{{ vpc_id }}'
AND region = '{{ region }}';

/*+ create */
INSERT INTO awscc.ec2.vpc_gateway_attachments (
InternetGatewayId,
VpcId,
region
)
SELECT
'{{ internet_gateway_id }}',
'{{ vpc_id }}',
'{{ region }}'
RETURNING *;

/*+ statecheck, retries=3, retry_delay=5 */
SELECT count(*) as count
FROM awscc.ec2.vpc_gateway_attachments
WHERE Identifier = 'IGW|{{ vpc_id }}'
AND region = '{{ region }}';

/*+ delete */
DELETE FROM awscc.ec2.vpc_gateway_attachments
WHERE Identifier = 'IGW|{{ vpc_id }}'
AND region = '{{ region }}';
Loading
Loading