Skip to main content

AWS VPC and EC2 Instance

In this example, we'll demonstrate how to set up a complete VPC networking stack with an EC2 web server instance in AWS using stackql-deploy and the awscc (Cloud Control) provider. Resources are identified using the awscc.tagging.tagged_resources service with a standard tag taxonomy.

Simple AWS VPC EC2 Stack

The EC2 instance is bootstrapped with a web server that serves a simple page using the EC2 instance UserData property.

Deploying the Stack

Install stackql-deploy (see Installing stackql-deploy), set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables, that's it!

Once you have setup your project directory (your "stack"), you can use the stackql-deploy cli application to deploy, test or teardown the stack in any given environment. To deploy the stack to an environment labeled dev, run the following:

stackql-deploy build examples/aws/aws-vpc-webserver dev

Use the --dry-run flag to view the queries to be run without actually running them:

stackql-deploy build examples/aws/aws-vpc-webserver dev --dry-run --show-queries

stackql_manifest.yml

The stackql_manifest.yml defines the resources in your stack and their property values (for one or more environments). This stack uses the awscc provider with a standard tag taxonomy (stackql:stack-name, stackql:stack-env, stackql:resource-name) for resource identification.

Click to expand the stackql_manifest.yml file
version: 1
name: "aws-vpc-webserver"
description: Provisions a complete AWS networking stack (VPC, subnet, internet gateway, route table, security group) with an Apache web server EC2 instance.
providers:
- awscc
globals:
- name: region
description: aws region
value: "{{ AWS_REGION }}"
- name: global_tags
value:
- Key: 'stackql:stack-name'
Value: "{{ stack_name }}"
- Key: 'stackql:stack-env'
Value: "{{ stack_env }}"
- Key: 'stackql:resource-name'
Value: "{{ resource_name }}"
resources:
- name: example_vpc
props:
- name: vpc_cidr_block
values:
prd:
value: "10.0.0.0/16"
sit:
value: "10.1.0.0/16"
dev:
value: "10.2.0.0/16"
- name: vpc_tags
value:
- Key: Name
Value: "{{ stack_name }}-{{ stack_env }}-vpc"
merge:
- global_tags
exports:
- vpc_id
- vpc_cidr_block
- name: example_subnet
props:
- name: subnet_cidr_block
values:
prd:
value: "10.0.1.0/24"
sit:
value: "10.1.1.0/24"
dev:
value: "10.2.1.0/24"
- name: subnet_tags
value:
- Key: Name
Value: "{{ stack_name }}-{{ stack_env }}-subnet"
merge: ['global_tags']
exports:
- subnet_id
- availability_zone
- name: example_inet_gateway
props:
- name: inet_gateway_tags
value:
- Key: Name
Value: "{{ stack_name }}-{{ stack_env }}-inet-gateway"
merge: ['global_tags']
exports:
- internet_gateway_id
- name: example_inet_gw_attachment
props: []
- name: example_route_table
props:
- name: route_table_tags
value:
- Key: Name
Value: "{{ stack_name }}-{{ stack_env }}-route-table"
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
value: "web security group for {{ stack_name }} ({{ stack_env }} environment)"
- name: group_name
value: "{{ stack_name }}-{{ stack_env }}-web-sg"
- name: sg_tags
value:
- Key: Name
Value: "{{ stack_name }}-{{ stack_env }}-web-sg"
merge: ['global_tags']
- name: security_group_ingress
value:
- IpProtocol: "tcp"
CidrIp: "0.0.0.0/0"
Description: Allow HTTP traffic
FromPort: 80
ToPort: 80
- IpProtocol: "tcp"
CidrIp: "{{ vpc_cidr_block }}"
Description: Allow SSH traffic from the internal network
FromPort: 22
ToPort: 22
- name: security_group_egress
value:
- CidrIp: "0.0.0.0/0"
Description: "Allow all outbound traffic"
FromPort: -1
ToPort: -1
IpProtocol: "-1"
exports:
- security_group_id
- name: example_web_server
props:
- name: ami_id
value: ami-05024c2628f651b80
- name: instance_type
value: t2.micro
- name: instance_subnet_id
value: "{{ subnet_id }}"
- name: sg_ids
value:
- "{{ security_group_id }}"
- name: user_data
value: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo '<!DOCTYPE html>...' > /var/www/html/index.html
- name: instance_tags
value:
- Key: Name
Value: "{{ stack_name }}-{{ stack_env }}-instance"
merge: ['global_tags']
exports:
- instance_id
- name: get_web_server_url
type: query
props: []
exports:
- public_dns_name

Resource Query Files

Resource query files are templates which are used to create, update, test and delete resources in your stack. This stack uses the identifier capture pattern — the exists query discovers the resource via tags and the captured field is used in statecheck and exports queries via {{ this.<field> }}.

/*+ exists */
WITH tagged_resources AS
(
SELECT split_part(ResourceARN, '/', 2) as vpc_id
FROM awscc.tagging.tagged_resources
WHERE region = '{{ region }}'
AND TagFilters = '{{ global_tags | to_aws_tag_filters }}'
AND ResourceTypeFilters = '["ec2:vpc"]'
),
vpcs AS
(
SELECT vpc_id
FROM awscc.ec2.vpcs_list_only
WHERE region = '{{ region }}'
)
SELECT r.vpc_id
FROM vpcs r
INNER JOIN tagged_resources tr
ON r.vpc_id = tr.vpc_id;

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

/*+ create */
INSERT INTO awscc.ec2.vpcs (
CidrBlock, Tags, EnableDnsSupport, EnableDnsHostnames, region
)
SELECT
'{{ vpc_cidr_block }}', '{{ vpc_tags }}', true, true, '{{ region }}'
RETURNING *;

/*+ exports */
SELECT '{{ this.vpc_id }}' as vpc_id,
'{{ vpc_cidr_block }}' as vpc_cidr_block;

/*+ delete */
DELETE FROM awscc.ec2.vpcs
WHERE Identifier = '{{ vpc_id }}'
AND region = '{{ region }}';

Key Patterns

Tag-Based Resource Discovery

Resources are identified using awscc.tagging.tagged_resources cross-referenced with the provider's *_list_only resource via a CTE + INNER JOIN. This ensures the resource both has the expected tags and currently exists in the provider (eliminating stale tag records).

to_aws_tag_filters Filter

The global_tags variable is converted to AWS TagFilters format using the to_aws_tag_filters custom filter, keeping queries clean:

AND TagFilters = '{{ global_tags | to_aws_tag_filters }}'

Property Verification with AWS_POLICY_EQUAL

Statechecks use AWS_POLICY_EQUAL for order-independent comparison of tags and security group rules.

More Information

The complete code for this example stack is available here. For more information on how to use StackQL and StackQL Deploy, visit: