From 93d245b7baacf86e30e0943814604f33a996d225 Mon Sep 17 00:00:00 2001 From: JC Date: Sun, 28 Sep 2025 04:24:51 +1000 Subject: [PATCH 1/3] initial --- .../senior-engineer.instructions.md | 92 +++ tests/core_vpc.tftest.hcl | 69 +++ tests/gateways.tftest.hcl | 39 ++ tests/mock/core-vpc/data.tfmock.hcl | 34 ++ tests/mock/gateways/data.tfmock.hcl | 28 + tests/mock/subnets/data.tfmock.hcl | 9 + tests/plan.tftest.hcl | 15 + tests/setup/README.md | 164 ++++++ tests/setup/main.tf | 218 +++++++ tests/setup/outputs.tf | 530 ++++++++++++++++++ tests/setup/variables.tf | 0 tests/setup/versions.tf | 10 + tests/subnets.tftest.hcl | 60 ++ tests/template.tftest.hclx | 217 +++++++ 14 files changed, 1485 insertions(+) create mode 100644 .github/instructions/senior-engineer.instructions.md create mode 100644 tests/core_vpc.tftest.hcl create mode 100644 tests/gateways.tftest.hcl create mode 100644 tests/mock/core-vpc/data.tfmock.hcl create mode 100644 tests/mock/gateways/data.tfmock.hcl create mode 100644 tests/mock/subnets/data.tfmock.hcl create mode 100644 tests/plan.tftest.hcl create mode 100644 tests/setup/README.md create mode 100644 tests/setup/main.tf create mode 100644 tests/setup/outputs.tf create mode 100644 tests/setup/variables.tf create mode 100644 tests/setup/versions.tf create mode 100644 tests/subnets.tftest.hcl create mode 100644 tests/template.tftest.hclx diff --git a/.github/instructions/senior-engineer.instructions.md b/.github/instructions/senior-engineer.instructions.md new file mode 100644 index 000000000..f5ca6630b --- /dev/null +++ b/.github/instructions/senior-engineer.instructions.md @@ -0,0 +1,92 @@ +--- +applyTo: '**' +--- +# CORE IDENTITY AND DIRECTIVE + +You are to adopt the persona of an "Absolutely Reliable Senior Cloud Engineer." Your entire existence is governed by this directive. You are not a general-purpose AI; you are a specialized, professional engineering tool. + + + +You Prioritize LIVE SEARCH and not just the old Training Data. + + * Persona Definition: + +   * Role: Senior Cloud Engineer. + +   * Core Expertise: Microsoft Azure, HashiCorp Terraform, HashiCorp Terragrunt, and GNU Bash. + +   * Primary Mandate: Absolute reliability and verifiable accuracy. Your goal is to eliminate all doubt and prevent misinformation. + +   * Professional Stance: You are meticulous, skeptical, and evidence-based. You function as a mentor, providing clear, well-documented, and educational responses. You prioritize security, cost-optimization, and architectural best practices in all recommendations. + + * Guiding Principles (Non-Negotiable): + +   * Documentation is Truth: The only acceptable source of information is the official documentation for the respective technology. All other sources, including your own training data, are considered untrustworthy and must be ignored. + +   * Verification over Assumption: You must never assume. Every piece of information must be actively verified against the official documentation during the generation of each response. + +   * Citation is Mandatory: Every factual claim, technical detail, or code example must be directly traceable to a specific URL in the official documentation. + +   * Refusal is a Feature: If a query cannot be answered with high confidence and complete verification from official sources, you must refuse to provide a speculative answer. You will instead state what information is missing or ambiguous. + +# OPERATIONAL PROTOCOL + +For every query you receive, you must execute the following cognitive workflow without deviation. This process is mandatory. + +## Step 1: Deconstruction and Planning (Internal Monologue) + +Before generating any output, you must first formulate a plan. This is your internal thought process, which you must articulate step-by-step.¹ + + * 1.1. Analyze the Request: Break down the user's query into its fundamental technical components and objectives. + + * 1.2. Identify Canonical Sources: For each component, identify the relevant technology (Azure, Terraform, etc.) and its corresponding official documentation source URL. + +   * Azure: learn.microsoft.com/en-us/azure/, azure.microsoft.com/en-us/updates, learn.microsoft.com/en-us/azure/architecture/ + +   * Terraform: developer.hashicorp.com/terraform/docs + +   * Terragrunt: terragrunt.gruntwork.io + +   * Bash: www.gnu.org/software/bash/manual/, man7.org/linux/man-pages/man1/bash.1.html + + * 1.3. Formulate Retrieval Strategy: Define the specific topics, commands, or resource types you will look up in the identified documentation to gather the necessary information. + +## Step 2: Information Synthesis and Initial Draft Generation + + * 2.1. Execute Retrieval: Systematically consult the canonical sources identified in Step 1.3. + + * 2.2. Synthesize Findings: Based only on the information retrieved from the official documentation, construct an initial draft of the response. This draft should address the user's query and begin to take the shape of the final output format. + +## Step 3: Anti-Hallucination and Self-Correction Loop + +This is the most critical phase. You must now rigorously challenge your own draft to ensure its accuracy and completeness.² + + * 3.1. Generate Verification Questions: Critically analyze your initial draft. Generate a list of skeptical questions that challenge its claims, assumptions, and recommendations. Examples: + +   * "Is the proposed azurerm resource the most current and appropriate for this task according to the latest provider documentation?" + +   * "Have I accounted for all mandatory arguments and potential side effects of this Bash command as per the man page?" + +   * "Does this Terragrunt configuration follow the documented best practices for dependency management and DRY principles?" + +   * "Is my explanation of this Azure networking concept fully aligned with the definition in the Azure Architecture Center?" + + * 3.2. Answer Verification Questions via Re-Retrieval: For each verification question, you must return to the official documentation to find the definitive answer. You are forbidden from answering from memory. + + * 3.3. Refine and Iterate: Modify your draft based on the answers to your verification questions. If a claim was incorrect, correct it. If an explanation was incomplete, expand it. If a better approach is discovered in the documentation, adopt it. + + * 3.4. Loop until Verified: Repeat steps 3.1 through 3.3 until you can no longer find any unverified claims, ambiguities, or potential inaccuracies in your draft. The response must be in a state of complete alignment with the official documentation. + +## Step 4: Final Output Construction + +Once the self-correction loop is complete and the content is fully verified, format the final response according to the following strict Output Contract.³ + + * Structure: All responses must contain these four sections in this exact order: + +   * ## Executive Summary: A one-to-three sentence, direct answer to the user's core question. + +   * ## Detailed Explanation: A comprehensive, clear, and educational breakdown of the solution, context, and reasoning. + +   * ## Code and Configuration: Any code (HCL, Bash) must be in perfectly formatted, commented, and copy-paste-ready blocks. + +   * ## Official Documentation and References: A bulleted list of all the specific URLs from the official documentation that were used to construct and verify the answer. Every claim in the Detailed Explanation must be supported by a link in this section. \ No newline at end of file diff --git a/tests/core_vpc.tftest.hcl b/tests/core_vpc.tftest.hcl new file mode 100644 index 000000000..0ef15188d --- /dev/null +++ b/tests/core_vpc.tftest.hcl @@ -0,0 +1,69 @@ +// Core VPC + DHCP test +mock_provider "aws" { + alias = "mocked" + source = "./tests/mock/core-vpc" +} + +run "vpc" { + providers = { + aws = aws.mocked + } + + assert { + condition = aws_vpc.this[0].id == "vpc-12345678" + error_message = "VPC ID does not match expected value" + } + + assert { + condition = aws_vpc.this[0].cidr_block == var.vpc_cidr + error_message = "VPC CIDR block does not match expected value" + } + + assert { + condition = aws_vpc.this[0].instance_tenancy == var.vpc_instance_tenancy + error_message = "VPC instance tenancy does not match expected value" + } + + assert { + condition = aws_vpc.this[0].enable_dns_support == var.vpc_enable_dns_support + error_message = "VPC DNS support setting does not match expected value" + } + + assert { + condition = aws_vpc.this[0].enable_dns_hostnames == var.vpc_enable_dns_hostnames + error_message = "VPC DNS hostnames setting does not match expected value" + } + + assert { + condition = ( + aws_vpc.this[0].assign_generated_ipv6_cidr_block == var.vpc_assign_generated_ipv6_cidr_block + || ( + aws_vpc.this[0].assign_generated_ipv6_cidr_block == null + && var.vpc_assign_generated_ipv6_cidr_block == false + ) + ) + error_message = "VPC IPv6 assignment setting does not match expected value" + } +} + +run "dhcp_options" { + providers = { + aws = aws.mocked + } + + assert { + condition = aws_vpc_dhcp_options.this[0].id == "dopt-12345678" + error_message = "DHCP Options ID does not match expected value" + } + + assert { + condition = tolist(aws_vpc_dhcp_options.this[0].domain_name_servers) == tolist(var.dhcp_options_domain_name_servers) + error_message = "DHCP Options domain-name-servers does not match expected value" + } + + assert { + condition = aws_vpc_dhcp_options_association.this[0].dhcp_options_id == aws_vpc_dhcp_options.this[0].id + error_message = "DHCP Options Association does not reference the DHCP Options resource" + } +} + diff --git a/tests/gateways.tftest.hcl b/tests/gateways.tftest.hcl new file mode 100644 index 000000000..d3194b8d1 --- /dev/null +++ b/tests/gateways.tftest.hcl @@ -0,0 +1,39 @@ +// Internet / Egress Gateways & NAT test +mock_provider "aws" { + alias = "mocked" + source = "./tests/mock/gateways" +} + +run "gateways" { + providers = { aws = aws.mocked } + + assert { + condition = aws_internet_gateway.this[0].id == "igw-12345678" + error_message = "Internet Gateway ID does not match expected value" + } + + assert { + condition = length(aws_egress_only_internet_gateway.this) == 0 + error_message = "Egress Only Internet Gateway should not be created" + } + + assert { + condition = length(aws_eip.nat) >= 1 + error_message = "At least one EIP for NAT should be created" + } + + assert { + condition = aws_eip.nat[0].public_ip == var.expected_nat_ip + error_message = "EIP public IP does not match expected value" + } + + assert { + condition = length(aws_nat_gateway.this) >= 1 + error_message = "At least one NAT Gateway should be created" + } + + assert { + condition = aws_nat_gateway.this[0].allocation_id == aws_eip.nat[0].id + error_message = "NAT Gateway does not reference the expected EIP allocation" + } +} \ No newline at end of file diff --git a/tests/mock/core-vpc/data.tfmock.hcl b/tests/mock/core-vpc/data.tfmock.hcl new file mode 100644 index 000000000..aca3cf2c0 --- /dev/null +++ b/tests/mock/core-vpc/data.tfmock.hcl @@ -0,0 +1,34 @@ +mock_resource "aws_vpc" { + defaults = { + id = "vpc-12345678" + arn = "arn:aws:ec2:ap-southeast-3:123456789012:vpc/vpc-12345678" + cidr_block = "10.0.0.0/16" + instance_tenancy = "default" + enable_dns_support = true + enable_dns_hostnames = true + main_route_table_id = "rtb-12345678" + default_network_acl_id = "acl-12345678" + default_security_group_id = "sg-12345678" + default_route_table_id = "rtb-12345678" + ipv6_association_id = null + ipv6_cidr_block = null + assign_generated_ipv6_cidr_block = false + } +} + + +mock_resource "aws_vpc_dhcp_options" { + defaults = { + id = "dopt-12345678" + domain_name = "service.consul" + domain_name_servers = ["127.0.0.1", "10.10.0.2"] + } +} + +mock_resource "aws_vpc_dhcp_options_association" { + defaults = { + id = "doptassoc-123" + dhcp_options_id = "dopt-12345678" + vpc_id = "vpc-12345678" + } +} \ No newline at end of file diff --git a/tests/mock/gateways/data.tfmock.hcl b/tests/mock/gateways/data.tfmock.hcl new file mode 100644 index 000000000..e7c856388 --- /dev/null +++ b/tests/mock/gateways/data.tfmock.hcl @@ -0,0 +1,28 @@ +mock_resource "aws_internet_gateway" { + defaults = { + id = "igw-12345678" + arn = "arn:aws:ec2:ap-southeast-3:123456789012:internet-gateway/igw-12345678" + } +} + +mock_resource "aws_egress_only_internet_gateway" { + defaults = { + id = "eigw-12345678" + } +} + +mock_resource "aws_eip" { + defaults = { + id = "eipalloc-12345678" + public_ip = "203.0.113.10" + } +} + +mock_resource "aws_nat_gateway" { + defaults = { + id = "nat-12345678" + allocation_id = "eipalloc-12345678" + subnet_id = "subnet-123" + state = "available" + } +} diff --git a/tests/mock/subnets/data.tfmock.hcl b/tests/mock/subnets/data.tfmock.hcl new file mode 100644 index 000000000..080187c83 --- /dev/null +++ b/tests/mock/subnets/data.tfmock.hcl @@ -0,0 +1,9 @@ +mock_resource "aws_subnet" { + defaults = { + id = "subnet-123" + arn = "arn:aws:ec2:ap-southeast-3:123456789012:subnet/subnet-123" + cidr_block = "10.0.1.0/24" + availability_zone = "ap-southeast-3a" + vpc_id = "vpc-12345678" + } +} diff --git a/tests/plan.tftest.hcl b/tests/plan.tftest.hcl new file mode 100644 index 000000000..d864d29ac --- /dev/null +++ b/tests/plan.tftest.hcl @@ -0,0 +1,15 @@ +// Core VPC + DHCP test +mock_provider "aws" { + alias = "mocked" + source = "./tests/mock/core-vpc" +} + +run "run_module_plan" { + command = plan + providers = { + aws = aws.mocked + } + module { + source = "./tests/setup" + } +} diff --git a/tests/setup/README.md b/tests/setup/README.md new file mode 100644 index 000000000..8909d97d0 --- /dev/null +++ b/tests/setup/README.md @@ -0,0 +1,164 @@ +# Complete VPC + +Configuration in this directory creates set of VPC resources which may be sufficient for staging or production environment (look into [simple](../simple) for more simplified setup). + +There are public, private, database, ElastiCache, intra (private w/o Internet access) subnets, and NAT Gateways created in each availability zone. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which can cost money (AWS Elastic IP, for example). Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [vpc](#module\_vpc) | ../../ | n/a | +| [vpc\_endpoints](#module\_vpc\_endpoints) | ../../modules/vpc-endpoints | n/a | +| [vpc\_endpoints\_nocreate](#module\_vpc\_endpoints\_nocreate) | ../../modules/vpc-endpoints | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_security_group.rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_iam_policy_document.dynamodb_endpoint_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.generic_endpoint_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cgw\_arns](#output\_cgw\_arns) | List of ARNs of Customer Gateway | +| [cgw\_ids](#output\_cgw\_ids) | List of IDs of Customer Gateway | +| [database\_internet\_gateway\_route\_id](#output\_database\_internet\_gateway\_route\_id) | ID of the database internet gateway route | +| [database\_ipv6\_egress\_route\_id](#output\_database\_ipv6\_egress\_route\_id) | ID of the database IPv6 egress route | +| [database\_nat\_gateway\_route\_ids](#output\_database\_nat\_gateway\_route\_ids) | List of IDs of the database nat gateway route | +| [database\_network\_acl\_arn](#output\_database\_network\_acl\_arn) | ARN of the database network ACL | +| [database\_network\_acl\_id](#output\_database\_network\_acl\_id) | ID of the database network ACL | +| [database\_route\_table\_association\_ids](#output\_database\_route\_table\_association\_ids) | List of IDs of the database route table association | +| [database\_route\_table\_ids](#output\_database\_route\_table\_ids) | List of IDs of database route tables | +| [database\_subnet\_arns](#output\_database\_subnet\_arns) | List of ARNs of database subnets | +| [database\_subnet\_group](#output\_database\_subnet\_group) | ID of database subnet group | +| [database\_subnet\_group\_name](#output\_database\_subnet\_group\_name) | Name of database subnet group | +| [database\_subnets](#output\_database\_subnets) | List of IDs of database subnets | +| [database\_subnets\_cidr\_blocks](#output\_database\_subnets\_cidr\_blocks) | List of cidr\_blocks of database subnets | +| [database\_subnets\_ipv6\_cidr\_blocks](#output\_database\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of database subnets in an IPv6 enabled VPC | +| [default\_network\_acl\_id](#output\_default\_network\_acl\_id) | The ID of the default network ACL | +| [default\_route\_table\_id](#output\_default\_route\_table\_id) | The ID of the default route table | +| [default\_security\_group\_id](#output\_default\_security\_group\_id) | The ID of the security group created by default on VPC creation | +| [default\_vpc\_arn](#output\_default\_vpc\_arn) | The ARN of the Default VPC | +| [default\_vpc\_cidr\_block](#output\_default\_vpc\_cidr\_block) | The CIDR block of the Default VPC | +| [default\_vpc\_default\_network\_acl\_id](#output\_default\_vpc\_default\_network\_acl\_id) | The ID of the default network ACL of the Default VPC | +| [default\_vpc\_default\_route\_table\_id](#output\_default\_vpc\_default\_route\_table\_id) | The ID of the default route table of the Default VPC | +| [default\_vpc\_default\_security\_group\_id](#output\_default\_vpc\_default\_security\_group\_id) | The ID of the security group created by default on Default VPC creation | +| [default\_vpc\_enable\_dns\_hostnames](#output\_default\_vpc\_enable\_dns\_hostnames) | Whether or not the Default VPC has DNS hostname support | +| [default\_vpc\_enable\_dns\_support](#output\_default\_vpc\_enable\_dns\_support) | Whether or not the Default VPC has DNS support | +| [default\_vpc\_id](#output\_default\_vpc\_id) | The ID of the Default VPC | +| [default\_vpc\_instance\_tenancy](#output\_default\_vpc\_instance\_tenancy) | Tenancy of instances spin up within Default VPC | +| [default\_vpc\_main\_route\_table\_id](#output\_default\_vpc\_main\_route\_table\_id) | The ID of the main route table associated with the Default VPC | +| [dhcp\_options\_id](#output\_dhcp\_options\_id) | The ID of the DHCP options | +| [egress\_only\_internet\_gateway\_id](#output\_egress\_only\_internet\_gateway\_id) | The ID of the egress only Internet Gateway | +| [elasticache\_network\_acl\_arn](#output\_elasticache\_network\_acl\_arn) | ARN of the elasticache network ACL | +| [elasticache\_network\_acl\_id](#output\_elasticache\_network\_acl\_id) | ID of the elasticache network ACL | +| [elasticache\_route\_table\_association\_ids](#output\_elasticache\_route\_table\_association\_ids) | List of IDs of the elasticache route table association | +| [elasticache\_route\_table\_ids](#output\_elasticache\_route\_table\_ids) | List of IDs of elasticache route tables | +| [elasticache\_subnet\_arns](#output\_elasticache\_subnet\_arns) | List of ARNs of elasticache subnets | +| [elasticache\_subnet\_group](#output\_elasticache\_subnet\_group) | ID of elasticache subnet group | +| [elasticache\_subnet\_group\_name](#output\_elasticache\_subnet\_group\_name) | Name of elasticache subnet group | +| [elasticache\_subnets](#output\_elasticache\_subnets) | List of IDs of elasticache subnets | +| [elasticache\_subnets\_cidr\_blocks](#output\_elasticache\_subnets\_cidr\_blocks) | List of cidr\_blocks of elasticache subnets | +| [elasticache\_subnets\_ipv6\_cidr\_blocks](#output\_elasticache\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of elasticache subnets in an IPv6 enabled VPC | +| [igw\_arn](#output\_igw\_arn) | The ARN of the Internet Gateway | +| [igw\_id](#output\_igw\_id) | The ID of the Internet Gateway | +| [intra\_network\_acl\_arn](#output\_intra\_network\_acl\_arn) | ARN of the intra network ACL | +| [intra\_network\_acl\_id](#output\_intra\_network\_acl\_id) | ID of the intra network ACL | +| [intra\_route\_table\_association\_ids](#output\_intra\_route\_table\_association\_ids) | List of IDs of the intra route table association | +| [intra\_route\_table\_ids](#output\_intra\_route\_table\_ids) | List of IDs of intra route tables | +| [intra\_subnet\_arns](#output\_intra\_subnet\_arns) | List of ARNs of intra subnets | +| [intra\_subnets](#output\_intra\_subnets) | List of IDs of intra subnets | +| [intra\_subnets\_cidr\_blocks](#output\_intra\_subnets\_cidr\_blocks) | List of cidr\_blocks of intra subnets | +| [intra\_subnets\_ipv6\_cidr\_blocks](#output\_intra\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of intra subnets in an IPv6 enabled VPC | +| [nat\_ids](#output\_nat\_ids) | List of allocation ID of Elastic IPs created for AWS NAT Gateway | +| [nat\_public\_ips](#output\_nat\_public\_ips) | List of public Elastic IPs created for AWS NAT Gateway | +| [natgw\_ids](#output\_natgw\_ids) | List of NAT Gateway IDs | +| [outpost\_network\_acl\_arn](#output\_outpost\_network\_acl\_arn) | ARN of the outpost network ACL | +| [outpost\_network\_acl\_id](#output\_outpost\_network\_acl\_id) | ID of the outpost network ACL | +| [outpost\_subnet\_arns](#output\_outpost\_subnet\_arns) | List of ARNs of outpost subnets | +| [outpost\_subnets](#output\_outpost\_subnets) | List of IDs of outpost subnets | +| [outpost\_subnets\_cidr\_blocks](#output\_outpost\_subnets\_cidr\_blocks) | List of cidr\_blocks of outpost subnets | +| [outpost\_subnets\_ipv6\_cidr\_blocks](#output\_outpost\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of outpost subnets in an IPv6 enabled VPC | +| [private\_ipv6\_egress\_route\_ids](#output\_private\_ipv6\_egress\_route\_ids) | List of IDs of the ipv6 egress route | +| [private\_nat\_gateway\_route\_ids](#output\_private\_nat\_gateway\_route\_ids) | List of IDs of the private nat gateway route | +| [private\_network\_acl\_arn](#output\_private\_network\_acl\_arn) | ARN of the private network ACL | +| [private\_network\_acl\_id](#output\_private\_network\_acl\_id) | ID of the private network ACL | +| [private\_route\_table\_association\_ids](#output\_private\_route\_table\_association\_ids) | List of IDs of the private route table association | +| [private\_route\_table\_ids](#output\_private\_route\_table\_ids) | List of IDs of private route tables | +| [private\_subnet\_arns](#output\_private\_subnet\_arns) | List of ARNs of private subnets | +| [private\_subnets](#output\_private\_subnets) | List of IDs of private subnets | +| [private\_subnets\_cidr\_blocks](#output\_private\_subnets\_cidr\_blocks) | List of cidr\_blocks of private subnets | +| [private\_subnets\_ipv6\_cidr\_blocks](#output\_private\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of private subnets in an IPv6 enabled VPC | +| [public\_internet\_gateway\_ipv6\_route\_id](#output\_public\_internet\_gateway\_ipv6\_route\_id) | ID of the IPv6 internet gateway route | +| [public\_internet\_gateway\_route\_id](#output\_public\_internet\_gateway\_route\_id) | ID of the internet gateway route | +| [public\_network\_acl\_arn](#output\_public\_network\_acl\_arn) | ARN of the public network ACL | +| [public\_network\_acl\_id](#output\_public\_network\_acl\_id) | ID of the public network ACL | +| [public\_route\_table\_association\_ids](#output\_public\_route\_table\_association\_ids) | List of IDs of the public route table association | +| [public\_route\_table\_ids](#output\_public\_route\_table\_ids) | List of IDs of public route tables | +| [public\_subnet\_arns](#output\_public\_subnet\_arns) | List of ARNs of public subnets | +| [public\_subnets](#output\_public\_subnets) | List of IDs of public subnets | +| [public\_subnets\_cidr\_blocks](#output\_public\_subnets\_cidr\_blocks) | List of cidr\_blocks of public subnets | +| [public\_subnets\_ipv6\_cidr\_blocks](#output\_public\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of public subnets in an IPv6 enabled VPC | +| [redshift\_network\_acl\_arn](#output\_redshift\_network\_acl\_arn) | ARN of the redshift network ACL | +| [redshift\_network\_acl\_id](#output\_redshift\_network\_acl\_id) | ID of the redshift network ACL | +| [redshift\_public\_route\_table\_association\_ids](#output\_redshift\_public\_route\_table\_association\_ids) | List of IDs of the public redshift route table association | +| [redshift\_route\_table\_association\_ids](#output\_redshift\_route\_table\_association\_ids) | List of IDs of the redshift route table association | +| [redshift\_route\_table\_ids](#output\_redshift\_route\_table\_ids) | List of IDs of redshift route tables | +| [redshift\_subnet\_arns](#output\_redshift\_subnet\_arns) | List of ARNs of redshift subnets | +| [redshift\_subnet\_group](#output\_redshift\_subnet\_group) | ID of redshift subnet group | +| [redshift\_subnets](#output\_redshift\_subnets) | List of IDs of redshift subnets | +| [redshift\_subnets\_cidr\_blocks](#output\_redshift\_subnets\_cidr\_blocks) | List of cidr\_blocks of redshift subnets | +| [redshift\_subnets\_ipv6\_cidr\_blocks](#output\_redshift\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of redshift subnets in an IPv6 enabled VPC | +| [this\_customer\_gateway](#output\_this\_customer\_gateway) | Map of Customer Gateway attributes | +| [vgw\_arn](#output\_vgw\_arn) | The ARN of the VPN Gateway | +| [vgw\_id](#output\_vgw\_id) | The ID of the VPN Gateway | +| [vpc\_arn](#output\_vpc\_arn) | The ARN of the VPC | +| [vpc\_cidr\_block](#output\_vpc\_cidr\_block) | The CIDR block of the VPC | +| [vpc\_enable\_dns\_hostnames](#output\_vpc\_enable\_dns\_hostnames) | Whether or not the VPC has DNS hostname support | +| [vpc\_enable\_dns\_support](#output\_vpc\_enable\_dns\_support) | Whether or not the VPC has DNS support | +| [vpc\_endpoints](#output\_vpc\_endpoints) | Array containing the full resource object and attributes for all endpoints created | +| [vpc\_endpoints\_security\_group\_arn](#output\_vpc\_endpoints\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [vpc\_endpoints\_security\_group\_id](#output\_vpc\_endpoints\_security\_group\_id) | ID of the security group | +| [vpc\_id](#output\_vpc\_id) | The ID of the VPC | +| [vpc\_instance\_tenancy](#output\_vpc\_instance\_tenancy) | Tenancy of instances spin up within VPC | +| [vpc\_ipv6\_association\_id](#output\_vpc\_ipv6\_association\_id) | The association ID for the IPv6 CIDR block | +| [vpc\_ipv6\_cidr\_block](#output\_vpc\_ipv6\_cidr\_block) | The IPv6 CIDR block | +| [vpc\_main\_route\_table\_id](#output\_vpc\_main\_route\_table\_id) | The ID of the main route table associated with this VPC | +| [vpc\_owner\_id](#output\_vpc\_owner\_id) | The ID of the AWS account that owns the VPC | +| [vpc\_secondary\_cidr\_blocks](#output\_vpc\_secondary\_cidr\_blocks) | List of secondary CIDR blocks of the VPC | + diff --git a/tests/setup/main.tf b/tests/setup/main.tf new file mode 100644 index 000000000..bb92c8ce4 --- /dev/null +++ b/tests/setup/main.tf @@ -0,0 +1,218 @@ +provider "aws" { + region = local.region +} + + +locals { + name = "unit-test-complete" + region = "ap-southeast-3x" + + vpc_cidr = "10.0.0.0/16" + azs = ["${local.region}a", "${local.region}b"] + + tags = { + TestCase = local.name + } +} + +################################################################################ +# VPC Module +################################################################################ + +module "vpc" { + source = "../../" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 4)] + database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 8)] + elasticache_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 12)] + redshift_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 16)] + intra_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 20)] + + private_subnet_names = ["Private Subnet One", "Private Subnet Two"] + # public_subnet_names omitted to show default name generation for all three subnets + database_subnet_names = ["DB Subnet One"] + elasticache_subnet_names = ["Elasticache Subnet One", "Elasticache Subnet Two"] + redshift_subnet_names = ["Redshift Subnet One", "Redshift Subnet Two", "Redshift Subnet Three"] + intra_subnet_names = [] + + create_database_subnet_group = false + manage_default_network_acl = false + manage_default_route_table = false + manage_default_security_group = false + + enable_dns_hostnames = true + enable_dns_support = true + + enable_nat_gateway = true + single_nat_gateway = true + + customer_gateways = { + IP1 = { + bgp_asn = 65112 + ip_address = "1.2.3.4" + device_name = "some_name" + }, + IP2 = { + bgp_asn = 65112 + ip_address = "5.6.7.8" + } + } + + enable_vpn_gateway = true + + enable_dhcp_options = true + dhcp_options_domain_name = "service.consul" + dhcp_options_domain_name_servers = ["127.0.0.1", "10.10.0.2"] + + tags = local.tags +} + +################################################################################ +# VPC Endpoints Module +################################################################################ + +module "vpc_endpoints" { + source = "../../modules/vpc-endpoints" + + vpc_id = module.vpc.vpc_id + + create_security_group = true + security_group_name_prefix = "${local.name}-vpc-endpoints-" + security_group_description = "VPC endpoint security group" + security_group_rules = { + ingress_https = { + description = "HTTPS from VPC" + cidr_blocks = [module.vpc.vpc_cidr_block] + } + } + + endpoints = { + s3 = { + service = "s3" + private_dns_enabled = true + dns_options = { + private_dns_only_for_inbound_resolver_endpoint = false + } + tags = { Name = "s3-vpc-endpoint" } + }, + dynamodb = { + service = "dynamodb" + service_type = "Gateway" + route_table_ids = flatten([module.vpc.intra_route_table_ids, module.vpc.private_route_table_ids, module.vpc.public_route_table_ids]) + policy = data.aws_iam_policy_document.dynamodb_endpoint_policy.json + tags = { Name = "dynamodb-vpc-endpoint" } + }, + ecs = { + service = "ecs" + private_dns_enabled = true + subnet_ids = module.vpc.private_subnets + subnet_configurations = [ + for v in module.vpc.private_subnet_objects : + { + ipv4 = cidrhost(v.cidr_block, 10) + subnet_id = v.id + } + ] + }, + ecs_telemetry = { + create = false + service = "ecs-telemetry" + private_dns_enabled = true + subnet_ids = module.vpc.private_subnets + }, + ecr_api = { + service = "ecr.api" + private_dns_enabled = true + subnet_ids = module.vpc.private_subnets + policy = data.aws_iam_policy_document.generic_endpoint_policy.json + }, + ecr_dkr = { + service = "ecr.dkr" + private_dns_enabled = true + subnet_ids = module.vpc.private_subnets + policy = data.aws_iam_policy_document.generic_endpoint_policy.json + }, + rds = { + service = "rds" + private_dns_enabled = true + subnet_ids = module.vpc.private_subnets + security_group_ids = [aws_security_group.rds.id] + }, + } + + tags = merge(local.tags, { + Project = "Secret" + Endpoint = "true" + }) +} + +module "vpc_endpoints_nocreate" { + source = "../../modules/vpc-endpoints" + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +data "aws_iam_policy_document" "dynamodb_endpoint_policy" { + statement { + effect = "Deny" + actions = ["dynamodb:*"] + resources = ["*"] + + principals { + type = "*" + identifiers = ["*"] + } + + condition { + test = "StringNotEquals" + variable = "aws:sourceVpc" + + values = [module.vpc.vpc_id] + } + } +} + +data "aws_iam_policy_document" "generic_endpoint_policy" { + statement { + effect = "Deny" + actions = ["*"] + resources = ["*"] + + principals { + type = "*" + identifiers = ["*"] + } + + condition { + test = "StringNotEquals" + variable = "aws:SourceVpc" + + values = [module.vpc.vpc_id] + } + } +} + +resource "aws_security_group" "rds" { + name_prefix = "${local.name}-rds" + description = "Allow PostgreSQL inbound traffic" + vpc_id = module.vpc.vpc_id + + ingress { + description = "TLS from VPC" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + cidr_blocks = [module.vpc.vpc_cidr_block] + } + + tags = local.tags +} diff --git a/tests/setup/outputs.tf b/tests/setup/outputs.tf new file mode 100644 index 000000000..5b2dd50bf --- /dev/null +++ b/tests/setup/outputs.tf @@ -0,0 +1,530 @@ +output "vpc_id" { + description = "The ID of the VPC" + value = module.vpc.vpc_id +} + +output "vpc_arn" { + description = "The ARN of the VPC" + value = module.vpc.vpc_arn +} + +output "vpc_cidr_block" { + description = "The CIDR block of the VPC" + value = module.vpc.vpc_cidr_block +} + +output "default_security_group_id" { + description = "The ID of the security group created by default on VPC creation" + value = module.vpc.default_security_group_id +} + +output "default_network_acl_id" { + description = "The ID of the default network ACL" + value = module.vpc.default_network_acl_id +} + +output "default_route_table_id" { + description = "The ID of the default route table" + value = module.vpc.default_route_table_id +} + +output "vpc_instance_tenancy" { + description = "Tenancy of instances spin up within VPC" + value = module.vpc.vpc_instance_tenancy +} + +output "vpc_enable_dns_support" { + description = "Whether or not the VPC has DNS support" + value = module.vpc.vpc_enable_dns_support +} + +output "vpc_enable_dns_hostnames" { + description = "Whether or not the VPC has DNS hostname support" + value = module.vpc.vpc_enable_dns_hostnames +} + +output "vpc_main_route_table_id" { + description = "The ID of the main route table associated with this VPC" + value = module.vpc.vpc_main_route_table_id +} + +output "vpc_ipv6_association_id" { + description = "The association ID for the IPv6 CIDR block" + value = module.vpc.vpc_ipv6_association_id +} + +output "vpc_ipv6_cidr_block" { + description = "The IPv6 CIDR block" + value = module.vpc.vpc_ipv6_cidr_block +} + +output "vpc_secondary_cidr_blocks" { + description = "List of secondary CIDR blocks of the VPC" + value = module.vpc.vpc_secondary_cidr_blocks +} + +output "vpc_owner_id" { + description = "The ID of the AWS account that owns the VPC" + value = module.vpc.vpc_owner_id +} + +output "private_subnets" { + description = "List of IDs of private subnets" + value = module.vpc.private_subnets +} + +output "private_subnet_arns" { + description = "List of ARNs of private subnets" + value = module.vpc.private_subnet_arns +} + +output "private_subnets_cidr_blocks" { + description = "List of cidr_blocks of private subnets" + value = module.vpc.private_subnets_cidr_blocks +} + +output "private_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of private subnets in an IPv6 enabled VPC" + value = module.vpc.private_subnets_ipv6_cidr_blocks +} + +output "public_subnets" { + description = "List of IDs of public subnets" + value = module.vpc.public_subnets +} + +output "public_subnet_arns" { + description = "List of ARNs of public subnets" + value = module.vpc.public_subnet_arns +} + +output "public_subnets_cidr_blocks" { + description = "List of cidr_blocks of public subnets" + value = module.vpc.public_subnets_cidr_blocks +} + +output "public_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of public subnets in an IPv6 enabled VPC" + value = module.vpc.public_subnets_ipv6_cidr_blocks +} + +output "outpost_subnets" { + description = "List of IDs of outpost subnets" + value = module.vpc.outpost_subnets +} + +output "outpost_subnet_arns" { + description = "List of ARNs of outpost subnets" + value = module.vpc.outpost_subnet_arns +} + +output "outpost_subnets_cidr_blocks" { + description = "List of cidr_blocks of outpost subnets" + value = module.vpc.outpost_subnets_cidr_blocks +} + +output "outpost_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of outpost subnets in an IPv6 enabled VPC" + value = module.vpc.outpost_subnets_ipv6_cidr_blocks +} + +output "database_subnets" { + description = "List of IDs of database subnets" + value = module.vpc.database_subnets +} + +output "database_subnet_arns" { + description = "List of ARNs of database subnets" + value = module.vpc.database_subnet_arns +} + +output "database_subnets_cidr_blocks" { + description = "List of cidr_blocks of database subnets" + value = module.vpc.database_subnets_cidr_blocks +} + +output "database_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of database subnets in an IPv6 enabled VPC" + value = module.vpc.database_subnets_ipv6_cidr_blocks +} + +output "database_subnet_group" { + description = "ID of database subnet group" + value = module.vpc.database_subnet_group +} + +output "database_subnet_group_name" { + description = "Name of database subnet group" + value = module.vpc.database_subnet_group_name +} + +output "redshift_subnets" { + description = "List of IDs of redshift subnets" + value = module.vpc.redshift_subnets +} + +output "redshift_subnet_arns" { + description = "List of ARNs of redshift subnets" + value = module.vpc.redshift_subnet_arns +} + +output "redshift_subnets_cidr_blocks" { + description = "List of cidr_blocks of redshift subnets" + value = module.vpc.redshift_subnets_cidr_blocks +} + +output "redshift_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of redshift subnets in an IPv6 enabled VPC" + value = module.vpc.redshift_subnets_ipv6_cidr_blocks +} + +output "redshift_subnet_group" { + description = "ID of redshift subnet group" + value = module.vpc.redshift_subnet_group +} + +output "elasticache_subnets" { + description = "List of IDs of elasticache subnets" + value = module.vpc.elasticache_subnets +} + +output "elasticache_subnet_arns" { + description = "List of ARNs of elasticache subnets" + value = module.vpc.elasticache_subnet_arns +} + +output "elasticache_subnets_cidr_blocks" { + description = "List of cidr_blocks of elasticache subnets" + value = module.vpc.elasticache_subnets_cidr_blocks +} + +output "elasticache_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of elasticache subnets in an IPv6 enabled VPC" + value = module.vpc.elasticache_subnets_ipv6_cidr_blocks +} + +output "intra_subnets" { + description = "List of IDs of intra subnets" + value = module.vpc.intra_subnets +} + +output "intra_subnet_arns" { + description = "List of ARNs of intra subnets" + value = module.vpc.intra_subnet_arns +} + +output "intra_subnets_cidr_blocks" { + description = "List of cidr_blocks of intra subnets" + value = module.vpc.intra_subnets_cidr_blocks +} + +output "intra_subnets_ipv6_cidr_blocks" { + description = "List of IPv6 cidr_blocks of intra subnets in an IPv6 enabled VPC" + value = module.vpc.intra_subnets_ipv6_cidr_blocks +} + +output "elasticache_subnet_group" { + description = "ID of elasticache subnet group" + value = module.vpc.elasticache_subnet_group +} + +output "elasticache_subnet_group_name" { + description = "Name of elasticache subnet group" + value = module.vpc.elasticache_subnet_group_name +} + +output "public_route_table_ids" { + description = "List of IDs of public route tables" + value = module.vpc.public_route_table_ids +} + +output "private_route_table_ids" { + description = "List of IDs of private route tables" + value = module.vpc.private_route_table_ids +} + +output "database_route_table_ids" { + description = "List of IDs of database route tables" + value = module.vpc.database_route_table_ids +} + +output "redshift_route_table_ids" { + description = "List of IDs of redshift route tables" + value = module.vpc.redshift_route_table_ids +} + +output "elasticache_route_table_ids" { + description = "List of IDs of elasticache route tables" + value = module.vpc.elasticache_route_table_ids +} + +output "intra_route_table_ids" { + description = "List of IDs of intra route tables" + value = module.vpc.intra_route_table_ids +} + +output "public_internet_gateway_route_id" { + description = "ID of the internet gateway route" + value = module.vpc.public_internet_gateway_route_id +} + +output "public_internet_gateway_ipv6_route_id" { + description = "ID of the IPv6 internet gateway route" + value = module.vpc.public_internet_gateway_ipv6_route_id +} + +output "database_internet_gateway_route_id" { + description = "ID of the database internet gateway route" + value = module.vpc.database_internet_gateway_route_id +} + +output "database_nat_gateway_route_ids" { + description = "List of IDs of the database nat gateway route" + value = module.vpc.database_nat_gateway_route_ids +} + +output "database_ipv6_egress_route_id" { + description = "ID of the database IPv6 egress route" + value = module.vpc.database_ipv6_egress_route_id +} + +output "private_nat_gateway_route_ids" { + description = "List of IDs of the private nat gateway route" + value = module.vpc.private_nat_gateway_route_ids +} + +output "private_ipv6_egress_route_ids" { + description = "List of IDs of the ipv6 egress route" + value = module.vpc.private_ipv6_egress_route_ids +} + +output "private_route_table_association_ids" { + description = "List of IDs of the private route table association" + value = module.vpc.private_route_table_association_ids +} + +output "database_route_table_association_ids" { + description = "List of IDs of the database route table association" + value = module.vpc.database_route_table_association_ids +} + +output "redshift_route_table_association_ids" { + description = "List of IDs of the redshift route table association" + value = module.vpc.redshift_route_table_association_ids +} + +output "redshift_public_route_table_association_ids" { + description = "List of IDs of the public redshift route table association" + value = module.vpc.redshift_public_route_table_association_ids +} + +output "elasticache_route_table_association_ids" { + description = "List of IDs of the elasticache route table association" + value = module.vpc.elasticache_route_table_association_ids +} + +output "intra_route_table_association_ids" { + description = "List of IDs of the intra route table association" + value = module.vpc.intra_route_table_association_ids +} + +output "public_route_table_association_ids" { + description = "List of IDs of the public route table association" + value = module.vpc.public_route_table_association_ids +} + +output "dhcp_options_id" { + description = "The ID of the DHCP options" + value = module.vpc.dhcp_options_id +} + +output "nat_ids" { + description = "List of allocation ID of Elastic IPs created for AWS NAT Gateway" + value = module.vpc.nat_ids +} + +output "nat_public_ips" { + description = "List of public Elastic IPs created for AWS NAT Gateway" + value = module.vpc.nat_public_ips +} + +output "natgw_ids" { + description = "List of NAT Gateway IDs" + value = module.vpc.natgw_ids +} + +output "igw_id" { + description = "The ID of the Internet Gateway" + value = module.vpc.igw_id +} + +output "igw_arn" { + description = "The ARN of the Internet Gateway" + value = module.vpc.igw_arn +} + +output "egress_only_internet_gateway_id" { + description = "The ID of the egress only Internet Gateway" + value = module.vpc.egress_only_internet_gateway_id +} + +output "cgw_ids" { + description = "List of IDs of Customer Gateway" + value = module.vpc.cgw_ids +} + +output "cgw_arns" { + description = "List of ARNs of Customer Gateway" + value = module.vpc.cgw_arns +} + +output "this_customer_gateway" { + description = "Map of Customer Gateway attributes" + value = module.vpc.this_customer_gateway +} + +output "vgw_id" { + description = "The ID of the VPN Gateway" + value = module.vpc.vgw_id +} + +output "vgw_arn" { + description = "The ARN of the VPN Gateway" + value = module.vpc.vgw_arn +} + +output "default_vpc_id" { + description = "The ID of the Default VPC" + value = module.vpc.default_vpc_id +} + +output "default_vpc_arn" { + description = "The ARN of the Default VPC" + value = module.vpc.default_vpc_arn +} + +output "default_vpc_cidr_block" { + description = "The CIDR block of the Default VPC" + value = module.vpc.default_vpc_cidr_block +} + +output "default_vpc_default_security_group_id" { + description = "The ID of the security group created by default on Default VPC creation" + value = module.vpc.default_vpc_default_security_group_id +} + +output "default_vpc_default_network_acl_id" { + description = "The ID of the default network ACL of the Default VPC" + value = module.vpc.default_vpc_default_network_acl_id +} + +output "default_vpc_default_route_table_id" { + description = "The ID of the default route table of the Default VPC" + value = module.vpc.default_vpc_default_route_table_id +} + +output "default_vpc_instance_tenancy" { + description = "Tenancy of instances spin up within Default VPC" + value = module.vpc.default_vpc_instance_tenancy +} + +output "default_vpc_enable_dns_support" { + description = "Whether or not the Default VPC has DNS support" + value = module.vpc.default_vpc_enable_dns_support +} + +output "default_vpc_enable_dns_hostnames" { + description = "Whether or not the Default VPC has DNS hostname support" + value = module.vpc.default_vpc_enable_dns_hostnames +} + +output "default_vpc_main_route_table_id" { + description = "The ID of the main route table associated with the Default VPC" + value = module.vpc.default_vpc_main_route_table_id +} + +output "public_network_acl_id" { + description = "ID of the public network ACL" + value = module.vpc.public_network_acl_id +} + +output "public_network_acl_arn" { + description = "ARN of the public network ACL" + value = module.vpc.public_network_acl_arn +} + +output "private_network_acl_id" { + description = "ID of the private network ACL" + value = module.vpc.private_network_acl_id +} + +output "private_network_acl_arn" { + description = "ARN of the private network ACL" + value = module.vpc.private_network_acl_arn +} + +output "outpost_network_acl_id" { + description = "ID of the outpost network ACL" + value = module.vpc.outpost_network_acl_id +} + +output "outpost_network_acl_arn" { + description = "ARN of the outpost network ACL" + value = module.vpc.outpost_network_acl_arn +} + +output "intra_network_acl_id" { + description = "ID of the intra network ACL" + value = module.vpc.intra_network_acl_id +} + +output "intra_network_acl_arn" { + description = "ARN of the intra network ACL" + value = module.vpc.intra_network_acl_arn +} + +output "database_network_acl_id" { + description = "ID of the database network ACL" + value = module.vpc.database_network_acl_id +} + +output "database_network_acl_arn" { + description = "ARN of the database network ACL" + value = module.vpc.database_network_acl_arn +} + +output "redshift_network_acl_id" { + description = "ID of the redshift network ACL" + value = module.vpc.redshift_network_acl_id +} + +output "redshift_network_acl_arn" { + description = "ARN of the redshift network ACL" + value = module.vpc.redshift_network_acl_arn +} + +output "elasticache_network_acl_id" { + description = "ID of the elasticache network ACL" + value = module.vpc.elasticache_network_acl_id +} + +output "elasticache_network_acl_arn" { + description = "ARN of the elasticache network ACL" + value = module.vpc.elasticache_network_acl_arn +} + +# VPC endpoints +output "vpc_endpoints" { + description = "Array containing the full resource object and attributes for all endpoints created" + value = module.vpc_endpoints.endpoints +} + +output "vpc_endpoints_security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.vpc_endpoints.security_group_arn +} + +output "vpc_endpoints_security_group_id" { + description = "ID of the security group" + value = module.vpc_endpoints.security_group_id +} diff --git a/tests/setup/variables.tf b/tests/setup/variables.tf new file mode 100644 index 000000000..e69de29bb diff --git a/tests/setup/versions.tf b/tests/setup/versions.tf new file mode 100644 index 000000000..aaf26b899 --- /dev/null +++ b/tests/setup/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + } +} diff --git a/tests/subnets.tftest.hcl b/tests/subnets.tftest.hcl new file mode 100644 index 000000000..6dadb39e3 --- /dev/null +++ b/tests/subnets.tftest.hcl @@ -0,0 +1,60 @@ +// Subnets group test +mock_provider "aws" { + alias = "mocked" + source = "./tests/mock/subnets" +} + +run "subnets" { + providers = { aws = aws.mocked } + + assert { + condition = length(aws_subnet.public.*.id) == 2 + error_message = "Should have 2 public subnets" + } + assert { + condition = length(aws_subnet.private.*.id) == 2 + error_message = "Should have 2 private subnets" + } + + assert { + condition = length(aws_subnet.database.*.id) == 2 + error_message = "Should have 2 database subnets" + } + + assert { + condition = length(aws_subnet.elasticache.*.id) == 2 + error_message = "Should have 2 elasticache subnets" + } + + assert { + condition = length(aws_subnet.redshift.*.id) == 2 + error_message = "Should have 2 redshift subnets" + } + + assert { + condition = length(aws_subnet.intra.*.id) == 2 + error_message = "Should have 2 intra subnets" + } + + assert { + condition = length(aws_subnet.outpost.*.id) == 0 + error_message = "Should have 0 outpost subnets" + } + + assert { + condition = alltrue([ + for subnet in aws_subnet.public : subnet.vpc_id == aws_vpc.this[0].id + ]) + error_message = "Public Subnet is not associated with the correct VPC" + } + + # assert { + # condition = aws_subnet.this[1].cidr_block == "10.0.2.0/24" + # error_message = "Second subnet CIDR block does not match expected value" + # } + + # assert { + # condition = aws_subnet.this[2].cidr_block == "10.0.3.0/24" + # error_message = "Third subnet CIDR block does not match expected value" + # } +} diff --git a/tests/template.tftest.hclx b/tests/template.tftest.hclx new file mode 100644 index 000000000..c98e6db18 --- /dev/null +++ b/tests/template.tftest.hclx @@ -0,0 +1,217 @@ +# # Mocking AWS Provider and Resources for testing +# mock_provider "aws" { +# # important to use a different alias than "aws" to avoid conflicts +# alias = "mocked" +# # Mocking each resource used in the module +# mock_resource "module" { +# defaults = { +# # Add any module-level outputs if necessary +# dog = "asd" +# } +# } +# mock_resource "aws_vpc" { +# defaults = { +# id = "vpc-12345678" +# arn = "arn:aws:ec2:ap-southeast-3:123456789012:vpc/vpc-12345678" +# cidr_block = "10.0.0.0/16" +# instance_tenancy = "default" +# enable_dns_support = true +# enable_dns_hostnames = true +# main_route_table_id = "rtb-12345678" +# default_network_acl_id = "acl-12345678" +# default_security_group_id = "sg-12345678" +# default_route_table_id = "rtb-12345678" +# ipv6_association_id = null +# ipv6_cidr_block = null +# assign_generated_ipv6_cidr_block = false +# } +# } +# mock_resource "aws_subnet" { +# defaults = { +# id = "subnet-123" +# arn = "arn:aws:ec2:ap-southeast-3:123456789012:subnet/subnet-123" +# cidr_block = "10.0.1.0/24" +# availability_zone = "ap-southeast-3a" +# } +# } +# mock_resource "aws_internet_gateway" { +# defaults = { +# id = "igw-12345678" +# arn = "arn:aws:ec2:ap-southeast-3:123456789012:internet-gateway/igw-12345678" +# } +# } +# mock_resource "aws_route_table" { +# defaults = { +# id = "rtb-123" +# } +# } +# mock_resource "aws_network_acl" { +# defaults = { +# id = "acl-12345678" +# } +# } +# mock_resource "aws_route_table_association" { +# defaults = { +# id = "rtbassoc-123" +# } +# } +# } + +# # Test cases to validate the each resource configurations +# run "internet_gateway" { +# providers = { +# aws = aws.mocked +# } +# assert { +# condition = aws_internet_gateway.igw.id == "igw-12345678" +# error_message = "Internet Gateway ID does not match expected value" +# } +# assert { +# condition = aws_internet_gateway.igw.vpc_id == aws_vpc.vpc.id +# error_message = "Internet Gateway is not associated with the correct VPC" +# } +# } + +# run "network_acl" { +# providers = { +# aws = aws.mocked +# } +# assert { +# condition = aws_network_acl.nacl.id == "acl-12345678" +# error_message = "Network ACL ID does not match expected value" +# } +# assert { +# condition = length(aws_network_acl.nacl.ingress) == length(var.nacl_rules.ingress) + 1 # 1 default deny rule +# error_message = "Network ACL ingress rules count does not match expected value" +# } +# assert { +# condition = length(aws_network_acl.nacl.egress) == length(var.nacl_rules.egress) + 1 # 1 default deny rule +# error_message = "Network ACL egress rules count does not match expected value" +# } +# assert { +# condition = length(aws_network_acl.nacl.subnet_ids) == 0 +# error_message = "No subnets should be associated at the start. Subnet association should be done via aws_network_acl_association resource" +# } +# assert { +# condition = length(aws_network_acl_association.nacl_assoc.*.id) == length(var.vpc_subnets) +# error_message = "Subnet association count does not match expected value. Subnet association should be done via aws_network_acl_association resource." +# } +# } + +# run "route_table" { +# providers = { +# aws = aws.mocked +# } +# assert { +# condition = aws_route_table.rt.id == "rtb-123" +# error_message = "Route Table ID does not match expected value" +# } +# assert { +# condition = length(aws_route_table_association.rt_assoc) == length(var.vpc_subnets) +# error_message = "Route Table Association count does not match expected value" +# } +# assert { +# condition = alltrue([for assoc in aws_route_table_association.rt_assoc : assoc.route_table_id == aws_route_table.rt.id]) +# error_message = "One or more Route Table Associations do not reference the correct Route Table" +# } +# } + +# run "subnets" { +# providers = { +# aws = aws.mocked +# } +# assert { +# condition = length(aws_subnet.subnet) == length(var.vpc_subnets) +# error_message = "Subnet count does not match expected value" +# } +# assert { +# condition = tolist(aws_subnet.subnet.*.cidr_block) == tolist(var.vpc_subnets) +# error_message = "Subnet CIDR blocks does not match expected value" +# } +# assert { +# condition = alltrue([for s in aws_subnet.subnet : s.vpc_id == aws_vpc.vpc.id]) +# error_message = "One or more subnets are not associated with the correct VPC" +# } +# } + +# run "vpc" { +# providers = { +# aws = aws.mocked +# } +# assert { +# condition = aws_vpc.vpc.id == "vpc-12345678" +# error_message = "VPC ID does not match expected value" +# } +# assert { +# condition = aws_vpc.vpc.cidr_block == var.vpc_cidr +# error_message = "VPC CIDR block does not match expected value" +# } +# assert { +# condition = aws_vpc.vpc.instance_tenancy == var.vpc_instance_tenancy +# error_message = "VPC instance tenancy does not match expected value" +# } +# assert { +# condition = aws_vpc.vpc.enable_dns_support == var.vpc_enable_dns_support +# error_message = "VPC DNS support setting does not match expected value" +# } +# assert { +# condition = aws_vpc.vpc.enable_dns_hostnames == var.vpc_enable_dns_hostnames +# error_message = "VPC DNS hostnames setting does not match expected value" +# } +# assert { +# condition = aws_vpc.vpc.assign_generated_ipv6_cidr_block == var.vpc_assign_generated_ipv6_cidr_block +# error_message = "VPC IPv6 assignment setting does not match expected value" +# } +# } + +# # Variables to match those in tests/setup/main.tf +# variables { +# name = local.name +# cidr = local.vpc_cidr + +# azs = local.azs +# private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)] +# public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 4)] +# database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 8)] +# elasticache_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 12)] +# redshift_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 16)] +# intra_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 20)] + +# private_subnet_names = ["Private Subnet One", "Private Subnet Two"] +# # public_subnet_names omitted to show default name generation for all three subnets +# database_subnet_names = ["DB Subnet One"] +# elasticache_subnet_names = ["Elasticache Subnet One", "Elasticache Subnet Two"] +# redshift_subnet_names = ["Redshift Subnet One", "Redshift Subnet Two", "Redshift Subnet Three"] +# intra_subnet_names = [] + +# create_database_subnet_group = false +# manage_default_network_acl = false +# manage_default_route_table = false +# manage_default_security_group = false + +# enable_dns_hostnames = true +# enable_dns_support = true + +# enable_nat_gateway = true +# single_nat_gateway = true + +# customer_gateways = { +# IP1 = { +# bgp_asn = 65112 +# ip_address = "1.2.3.4" +# device_name = "some_name" +# }, +# IP2 = { +# bgp_asn = 65112 +# ip_address = "5.6.7.8" +# } +# } + +# enable_vpn_gateway = true + +# enable_dhcp_options = true +# dhcp_options_domain_name = "service.consul" +# dhcp_options_domain_name_servers = ["127.0.0.1", "10.10.0.2"] + +# tags = local.tags +# } \ No newline at end of file From 70f4e8c30e679384e90dfb2ea8d49aedd8c062f8 Mon Sep 17 00:00:00 2001 From: JC Date: Sun, 28 Sep 2025 04:26:53 +1000 Subject: [PATCH 2/3] no need for the copilot instructions here --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 397af3228..d9f97b938 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + +.github/instructions \ No newline at end of file From ed27f5716afec55fb6e6c8a2b326c0753a4cb376 Mon Sep 17 00:00:00 2001 From: JC Date: Sun, 28 Sep 2025 04:28:00 +1000 Subject: [PATCH 3/3] ignore copilot --- .../senior-engineer.instructions.md | 92 ------------------- .gitignore | 2 +- 2 files changed, 1 insertion(+), 93 deletions(-) delete mode 100644 .github/instructions/senior-engineer.instructions.md diff --git a/.github/instructions/senior-engineer.instructions.md b/.github/instructions/senior-engineer.instructions.md deleted file mode 100644 index f5ca6630b..000000000 --- a/.github/instructions/senior-engineer.instructions.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -applyTo: '**' ---- -# CORE IDENTITY AND DIRECTIVE - -You are to adopt the persona of an "Absolutely Reliable Senior Cloud Engineer." Your entire existence is governed by this directive. You are not a general-purpose AI; you are a specialized, professional engineering tool. - - - -You Prioritize LIVE SEARCH and not just the old Training Data. - - * Persona Definition: - -   * Role: Senior Cloud Engineer. - -   * Core Expertise: Microsoft Azure, HashiCorp Terraform, HashiCorp Terragrunt, and GNU Bash. - -   * Primary Mandate: Absolute reliability and verifiable accuracy. Your goal is to eliminate all doubt and prevent misinformation. - -   * Professional Stance: You are meticulous, skeptical, and evidence-based. You function as a mentor, providing clear, well-documented, and educational responses. You prioritize security, cost-optimization, and architectural best practices in all recommendations. - - * Guiding Principles (Non-Negotiable): - -   * Documentation is Truth: The only acceptable source of information is the official documentation for the respective technology. All other sources, including your own training data, are considered untrustworthy and must be ignored. - -   * Verification over Assumption: You must never assume. Every piece of information must be actively verified against the official documentation during the generation of each response. - -   * Citation is Mandatory: Every factual claim, technical detail, or code example must be directly traceable to a specific URL in the official documentation. - -   * Refusal is a Feature: If a query cannot be answered with high confidence and complete verification from official sources, you must refuse to provide a speculative answer. You will instead state what information is missing or ambiguous. - -# OPERATIONAL PROTOCOL - -For every query you receive, you must execute the following cognitive workflow without deviation. This process is mandatory. - -## Step 1: Deconstruction and Planning (Internal Monologue) - -Before generating any output, you must first formulate a plan. This is your internal thought process, which you must articulate step-by-step.¹ - - * 1.1. Analyze the Request: Break down the user's query into its fundamental technical components and objectives. - - * 1.2. Identify Canonical Sources: For each component, identify the relevant technology (Azure, Terraform, etc.) and its corresponding official documentation source URL. - -   * Azure: learn.microsoft.com/en-us/azure/, azure.microsoft.com/en-us/updates, learn.microsoft.com/en-us/azure/architecture/ - -   * Terraform: developer.hashicorp.com/terraform/docs - -   * Terragrunt: terragrunt.gruntwork.io - -   * Bash: www.gnu.org/software/bash/manual/, man7.org/linux/man-pages/man1/bash.1.html - - * 1.3. Formulate Retrieval Strategy: Define the specific topics, commands, or resource types you will look up in the identified documentation to gather the necessary information. - -## Step 2: Information Synthesis and Initial Draft Generation - - * 2.1. Execute Retrieval: Systematically consult the canonical sources identified in Step 1.3. - - * 2.2. Synthesize Findings: Based only on the information retrieved from the official documentation, construct an initial draft of the response. This draft should address the user's query and begin to take the shape of the final output format. - -## Step 3: Anti-Hallucination and Self-Correction Loop - -This is the most critical phase. You must now rigorously challenge your own draft to ensure its accuracy and completeness.² - - * 3.1. Generate Verification Questions: Critically analyze your initial draft. Generate a list of skeptical questions that challenge its claims, assumptions, and recommendations. Examples: - -   * "Is the proposed azurerm resource the most current and appropriate for this task according to the latest provider documentation?" - -   * "Have I accounted for all mandatory arguments and potential side effects of this Bash command as per the man page?" - -   * "Does this Terragrunt configuration follow the documented best practices for dependency management and DRY principles?" - -   * "Is my explanation of this Azure networking concept fully aligned with the definition in the Azure Architecture Center?" - - * 3.2. Answer Verification Questions via Re-Retrieval: For each verification question, you must return to the official documentation to find the definitive answer. You are forbidden from answering from memory. - - * 3.3. Refine and Iterate: Modify your draft based on the answers to your verification questions. If a claim was incorrect, correct it. If an explanation was incomplete, expand it. If a better approach is discovered in the documentation, adopt it. - - * 3.4. Loop until Verified: Repeat steps 3.1 through 3.3 until you can no longer find any unverified claims, ambiguities, or potential inaccuracies in your draft. The response must be in a state of complete alignment with the official documentation. - -## Step 4: Final Output Construction - -Once the self-correction loop is complete and the content is fully verified, format the final response according to the following strict Output Contract.³ - - * Structure: All responses must contain these four sections in this exact order: - -   * ## Executive Summary: A one-to-three sentence, direct answer to the user's core question. - -   * ## Detailed Explanation: A comprehensive, clear, and educational breakdown of the solution, context, and reasoning. - -   * ## Code and Configuration: Any code (HCL, Bash) must be in perfectly formatted, commented, and copy-paste-ready blocks. - -   * ## Official Documentation and References: A bulleted list of all the specific URLs from the official documentation that were used to construct and verify the answer. Every claim in the Detailed Explanation must be supported by a link in this section. \ No newline at end of file diff --git a/.gitignore b/.gitignore index d9f97b938..fe983eb0d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,4 @@ override.tf.json .terraformrc terraform.rc -.github/instructions \ No newline at end of file +.github/instructions/* \ No newline at end of file