Skip to content

Commit e6b5bec

Browse files
committed
Initial commit
0 parents  commit e6b5bec

File tree

53 files changed

+3541
-0
lines changed

Some content is hidden

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

53 files changed

+3541
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.venv
2+
/dist
3+
/src/terraform

README.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# CloudFormation Custom Resource - Terraform Integration
2+
3+
## Rationale
4+
5+
The integration of Terraform within a CloudFormation Custom Resource addresses the need for greater flexibility in infrastructure management. While AWS CloudFormation is great for managing AWS resources, it's confined to the AWS ecosystem. Terraform, however, offers a wide range of providers, enabling seamless management of resources from third-party services. These include on-premises servers, external databases, monitoring tools, and CI/CD systems. This approach unifies resource management by leveraging CloudFormation's native AWS integration while extending its capabilities through Terraform's extensive provider ecosystem. The result is a more comprehensive and automated infrastructure provisioning process.
6+
7+
## Solution Overview
8+
9+
This solution leverages Lambda-backed CloudFormation Custom Resources to seamlessly integrate Terraform configurations into CloudFormation templates. It offers a Lambda function—designated as the ServiceToken in custom resources—that executes Terraform's provisioning logic.
10+
11+
See [CloudFormation Custom Resources Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) for more details.
12+
13+
## Setup Instructions
14+
15+
1. Clone the repository:
16+
17+
```bash
18+
git clone https://github.com/humanonymous/cloudformation-custom-resource-terraform.git
19+
cd cloudformation-custom-resource-terraform
20+
```
21+
22+
2. Install **go-task** from [https://taskfile.dev](https://taskfile.dev/). It's a standalone binary with no dependencies that elegantly wraps shell scripts. For more details, refer to [Taskfile.yaml](Taskfile.yaml). You can also copy scripts from the file and run them manually—the code is pretty straightforward.
23+
3. Configure [Taskfile.env](Taskfile.env) to set up your environment variables. These variables specify function name, S3 bucket used for CloudFormation stack deployment, and other essential parameters. Refer to the file for more details.
24+
4. Execute the following commands to build and deploy the CloudFormation Custom Resource:
25+
26+
```bash
27+
task clean build deploy --silent
28+
```
29+
30+
## General Usage
31+
32+
Create CloudFormation Custom Resources in your infrastructure code. Ensure you use the Lambda ARN in the custom resource configuration's `ServiceToken` property.
33+
34+
```yaml
35+
Resources:
36+
CustomTerraformConfigurationExample:
37+
Type: Custom::TerraformConfiguration
38+
Properties:
39+
ServiceToken: <ARN of Lambda>
40+
Configuration: |
41+
terraform {
42+
backend "s3" {
43+
bucket = "mybucket"
44+
key = "path/to/my/key"
45+
region = "us-east-1"
46+
dynamodb_table = "TableName"
47+
}
48+
}
49+
resource "aws_s3_bucket" "example" {
50+
bucket = "example-bucket"
51+
}
52+
...
53+
```
54+
55+
## Property Reference
56+
57+
### ServiceToken (Required)
58+
59+
The ARN of the Lambda function handling the custom resource logic. The ARN is generated during setup and must be always provided in the custom resource definitions.
60+
61+
```yaml
62+
Resources:
63+
CustomTerraformConfigurationExample:
64+
Type: Custom::TerraformConfiguration
65+
Properties:
66+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
67+
# Other properties...
68+
```
69+
70+
**Note:** The `ServiceToken` property can't be changed after resource creation. Check the "Potential Drawbacks or Limitations" section for more details.
71+
72+
### Backend (Optional)
73+
74+
Specifies the Terraform backend. Supported options:
75+
76+
- `Auto`: We currently support automatic configuration of an S3 backend with a DynamoDB table for state locking. This is the recommended method for implementing consistent Terraform state isolation per CloudFormation resource. To use the `Auto` configured backend, you'll need to set two additional environment variables in [Taskfile.env](Taskfile.env):
77+
- `STACK_PARAM_TERRAFORM_BACKEND_AUTO_S3_BUCKET` - The bucket to store Terraform states. This bucket should be accessible by CloudFormation during stack deployment.
78+
- `STACK_PARAM_TERRAFORM_BACKEND_AUTO_S3_DYNAMODB_TABLE` - The DynamoDB table for state locking and consistency checking.
79+
- **Partial Configuration**: Provide a partial Terraform backend configuration as defined in the [Backend Partial Configuration](https://developer.hashicorp.com/terraform/language/backend#partial-configuration) guide.
80+
- **Inline Configuration:** You can also specify backend configuration directly in the `Configuration` property (see the corresponding section below).
81+
82+
**Note:** If not specified, Terraform will use a `local` backend. This prevents the custom resource from preserving state between **Create**, **Update**, and **Delete** actions, leading to CloudFormation lifecycle failures. Always provide a backend configuration to ensure proper state management.
83+
84+
- Automatically configured backend. The `terraform { backend “s3” {}}` block is required.
85+
86+
```yaml
87+
Resources:
88+
CustomTerraformConfigurationExample:
89+
Type: Custom::TerraformConfiguration
90+
Properties:
91+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
92+
Backend: "Auto"
93+
Configuration: |
94+
terraform {
95+
backend "s3" {}
96+
}
97+
resource "aws_s3_bucket" "example" {
98+
bucket = "example-bucket"
99+
}
100+
```
101+
102+
- Partial backend configuration
103+
104+
```yaml
105+
Resources:
106+
CustomTerraformConfigurationExample:
107+
Type: Custom::TerraformConfiguration
108+
Properties:
109+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
110+
Backend: |
111+
address = "demo.consul.io"
112+
scheme = "https"
113+
path = "path/to/terraform/state"
114+
Configuration: |
115+
terraform {
116+
backend "consul" {}
117+
}
118+
resource "aws_s3_bucket" "example" {
119+
bucket = "example-bucket"
120+
}
121+
```
122+
123+
- Inline backend configuration
124+
125+
```yaml
126+
Resources:
127+
CustomTerraformConfigurationExample:
128+
Type: Custom::TerraformConfiguration
129+
Properties:
130+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
131+
Configuration: |
132+
terraform {
133+
backend "consul" {
134+
address = "demo.consul.io"
135+
scheme = "https"
136+
path = "path/to/terraform/state"
137+
}
138+
}
139+
resource "aws_s3_bucket" "example" {
140+
bucket = "example-bucket"
141+
}
142+
```
143+
144+
### Environment (Optional)
145+
146+
A map of environment variables to be set during Terraform execution. For more details, refer to [Terraform's Environment Variables Guide](https://developer.hashicorp.com/terraform/cli/config/environment-variables).
147+
148+
```yaml
149+
Resources:
150+
CustomTerraformConfigurationExample:
151+
Type: Custom::TerraformConfiguration
152+
Properties:
153+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
154+
Backend: "Auto"
155+
Environment:
156+
TF_LOG: "TRACE"
157+
TF_VAR_bucket_name: "example-bucket"
158+
Configuration: |
159+
terraform {
160+
backend "s3" {}
161+
}
162+
variable "bucket_name" {
163+
type = string
164+
}
165+
resource "aws_s3_bucket" "example" {
166+
bucket = var.bucket_name
167+
}
168+
```
169+
170+
**Note:** The `TF_IN_AUTOMATION` and `TF_INPUT` environment variables are automatically set and cannot be overridden for Custom CloudFormation Resources.
171+
172+
### Configuration (Required)
173+
174+
Specifies the Terraform configuration in one of the following formats:
175+
176+
- Inline Terraform configuration:
177+
178+
```yaml
179+
Resources:
180+
CustomTerraformConfigurationExample:
181+
Type: Custom::TerraformConfiguration
182+
Properties:
183+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
184+
Backend: "Auto"
185+
Configuration: |
186+
terraform {
187+
backend "s3" {}
188+
}
189+
resource "aws_s3_bucket" "example" {
190+
bucket = "example-bucket"
191+
}
192+
```
193+
194+
- HTTP(S) URL pointing to a Terraform configuration file:
195+
196+
```yaml
197+
Resources:
198+
CustomTerraformConfigurationExample:
199+
Type: Custom::TerraformConfiguration
200+
Properties:
201+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
202+
Backend: "Auto"
203+
Configuration: "https://example.com/path/to/terraform.tf"
204+
```
205+
206+
- S3 URL pointing to a Terraform configuration file:
207+
208+
```yaml
209+
Resources:
210+
CustomTerraformConfigurationExample:
211+
Type: Custom::TerraformConfiguration
212+
Properties:
213+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
214+
Backend: "Auto"
215+
Configuration: "s3://example/path/to/terraform.tf"
216+
```
217+
218+
### Variables (Optional)
219+
220+
A map of Terraform variables to pass to the configuration:
221+
222+
```yaml
223+
Resources:
224+
CustomTerraformConfigurationExample:
225+
Type: Custom::TerraformConfiguration
226+
Properties:
227+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
228+
Backend: "Auto"
229+
Configuration: |
230+
terraform {
231+
backend "s3" {}
232+
}
233+
variable "String" {
234+
type = string
235+
}
236+
variable "List" {
237+
type = list
238+
}
239+
variable "Number" {
240+
type = number
241+
}
242+
resource "aws_s3_bucket" "example" {
243+
bucket = var.String
244+
}
245+
Variables:
246+
String: "example-bucket"
247+
List:
248+
- "item1"
249+
- "item2"
250+
Number: 5
251+
```
252+
253+
### ExecutionLogsTargetArn (Optional)
254+
255+
The ARN of a CloudWatch Log Group where Terraform execution logs will be sent.
256+
257+
## Outputs
258+
259+
The Custom Resource automatically maps Terraform outputs to CloudFormation outputs, allowing seamless integration between Terraform-managed resources and the rest of your CloudFormation stack.
260+
261+
```yaml
262+
Resources:
263+
CustomTerraformConfigurationExample:
264+
Type: Custom::TerraformConfiguration
265+
Properties:
266+
ServiceToken: "arn:aws:lambda:us-east-1:123456789012:function:cloudformation-custom-resource-terraform"
267+
Backend: "Auto"
268+
Configuration: |
269+
terraform {
270+
backend "s3" {}
271+
}
272+
resource "aws_s3_bucket" "example" {
273+
bucket = "example-bucket"
274+
}
275+
output "S3BucketName" {
276+
value = var.aws_s3_bucket.id
277+
}
278+
279+
Outputs:
280+
S3BucketName: !GetAtt TerraformManagedResource.S3BucketName
281+
```
282+
283+
## Potential Drawbacks or Limitations
284+
285+
- Modifying the `ServiceToken` value in custom resources requires replacing the entire custom resource. CloudFormation doesn't allow changes to this property and will fail with the error "Modifying service token is not allowed." This limitation means you'll need to recreate the resource if you want to change the underlying Lambda function.
286+
- Limited access to Terraform state can make troubleshooting difficult. Developers may struggle to diagnose issues or track changes without direct access to the state managed by the CloudFormation Custom Resource.
287+
288+
## Testing
289+
290+
1. Install **go-task** from [https://taskfile.dev](https://taskfile.dev/).
291+
2. Configure the relevant section of [Taskfile.env](Taskfile.env) to set up your environment variables.
292+
3. Execute the following commands to build and deploy the CloudFormation Custom Resource:
293+
294+
```bash
295+
task test --silent
296+
```

Taskfile.env

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# S3 bucket used to store CloudFormation template and lambda code for deployment.
2+
CLOUDFORMATION_DEPLOYMENT_S3_BUCKET="your-bucket-for-cloudformation-deployment-artifacts"
3+
4+
# S3 prefix to be added to artifact names during CloudFormation deployment.
5+
CLOUDFORMATION_DEPLOYMENT_S3_PREFIX="cloudformation/templates"
6+
7+
# Name of the Lambda Function.
8+
STACK_PARAM_FUNCTION_NAME="cloudformation-custom-resource-terraform"
9+
10+
# VPC ID to use if the Lambda Function needs to access resources in your VPC
11+
STACK_PARAM_VPC_ID=""
12+
13+
# VPC Subnet IDs if the Lambda Function needs to access resources in your VPC
14+
STACK_PARAM_SUBNET_IDS=""
15+
16+
# Security Group IDs for the Lambda Function.
17+
# If not specified, a new Security Group will be created for the Lambda Function.
18+
# This default security group will allow all egress traffic.
19+
STACK_PARAM_SECURITY_GROUP_IDS=""
20+
21+
# Execution role ARN for the Lambda Function.
22+
# If not specified, a new IAM Role will be created for the Lambda Function.
23+
# This default role will allow all permissions except for route53 domain management.
24+
STACK_PARAM_EXECUTION_ROLE_ARN=""
25+
26+
# Name of bucket to store Terraform states if using S3 backend with Auto backend configuration.
27+
# The bucket should be accessible by CloudFormation during stack deployment.
28+
STACK_PARAM_TERRAFORM_BACKEND_AUTO_S3_BUCKET=""
29+
30+
# DynamoDB table for state locking and consistency checking.
31+
# Relevant if using S3 backend with Auto backend configuration.
32+
STACK_PARAM_TERRAFORM_BACKEND_AUTO_S3_DYNAMODB_TABLE=""
33+
34+
# ------------------------------------------------------------------------------
35+
36+
# Name of the software, mainly used to identify relevant test CloudFormation stacks.
37+
TESTS_SOFTWARE_NAME="cloudformation-custom-resource-terraform"
38+
39+
# CloudFormation Custom Resource Service Token to test. This should be an ARN of the lambda function.
40+
TESTS_SERVICE_TOKEN="arn:aws:lambda:us-east-1:123456789000:function:cloudformation-custom-resource-terraform"
41+
42+
# Private S3 bucket used to store test artifacts, such as remote terraform configuration.
43+
TESTS_S3_PRIVATE_BUCKET="your-private-bucket-for-test-artifacts"
44+
45+
# S3 prefix to be added to names of test artifacts in the private S3 bucket.
46+
TESTS_S3_PRIVATE_PREFIX="cloudformation-custom-resource-terraform/tests"
47+
48+
# Public S3 bucket used to store test artifacts, such as remote terraform configuration.
49+
# It should be set up as website with public http(s) access.
50+
TESTS_S3_PUBLIC_BUCKET="your-public-bucket-for-test-artifacts"
51+
52+
# S3 prefix to be added to names of test artifacts in the public S3 bucket.
53+
TESTS_S3_PUBLIC_PREFIX="cloudformation-custom-resource-terraform/tests"

0 commit comments

Comments
 (0)