Part one: Introduction and Setup
Intro
Approximately 4 years ago I faced one very common but sometimes too overused pattern in terraform configuration - with a count.
You can see it in any community terraform module, like:
1variable "s3_buckets" {
2 type = list(string)s
3 default = ["1bucket", "2bucket", "3bucket"]
4}
5resource "aws_s3_bucket" "s3-module" {
6 count = length(var.s3_buckets)
7}
8
It could produce some undesired changes when we just changed elements order or deleted some of them.
If we had had for_each
or cdktf
that time... but 3 years ago we didn't have another way to do it. However, since Terraform version >= 12, we can use `fore_each in our configuration. It has made our life much better.
1variable "s3_buckets" {
2 type = map(object({
3 acl = string
4 }))
5 default = {
6 1bucket = {
7 acl = "private"
8 }
9 }
10}
11resource "aws_s3_bucket" "s3-module" {
12 for_each = var.s3_buckets
13 acl = each.value.acl
14}
15
As a result, it will not break anything even you delete something or change the order.
The count is still not so bad if you just practice, or you don't have other variants (you have only arrays in some data sources, and conversion to an object is useless).
Better way
But what if I tell you that we have a better approach?
Pulumi
It was my first attempt to reimagine my IaC vision. I was so inspired to use it, made some test configurations but... Pulumi is not a true open-source project, it doesn't have any collaboration alternatives except their cloud. In case if my company can afford to buy an enterprise plan, some very small companies cannot. So it can be hard to recommend it to other people. I still think that Pulumi is a great product, but maybe not for everyone.
Cdktf
Approximately 1 year ago I was pleasantly surprised, that Terraform developed their own CDK (In collaboration with Aws if I am not mistaken). And you can simplify your terraform code (or make it worse :) ) without using terraform-specific functions.
Cdkf setup
Create project
Terraform guys recommend installing cdktf
globally (I don't know why, because I want to keep my OS clean as much as possible) and don't use npx.
That's why I just copy-paste this package.json file across my projects:
1{
2 "name": "terraform",
3 "version": "1.0.0",
4 "main": "main.js",
5 "types": "main.ts",
6 "license": "MIT",
7 "private": true,
8 "scripts": {
9 "get": "cdktf get",
10 "plan": "cdktf diff",
11 "build": "cdktf get && tsc",
12 "synth": "cdktf synth",
13 "apply": "cdktf deploy",
14 "terraform:apply": "terraform -chdir=cdktf.out apply",
15 "terraform:destroy": "terraform -chdir=cdktf.out destroy",
16 "terraform:plan": "terraform -chdir=cdktf.out plan",
17 "destroy": "cdktf destroy",
18 "compile": "tsc --pretty",
19 "watch": "tsc -w",
20 "upgrade": "npm i cdktf@latest cdktf-cli@latest",
21 "upgrade:next": "npm i cdktf@next cdktf-cli@next"
22 },
23 "engines": {
24 "node": ">=10.12"
25 },
26 "dependencies": {
27 "cdktf": "^0.2.0",
28 "constructs": "^3.3.68"
29 },
30 "devDependencies": {
31 "@types/node": "^14.14.10",
32 "cdktf-cli": "^0.2.0",
33 "typescript": "^4.2.3"
34 }
35}
36
But if you prefer building it from scratch you can safely follow this link:
https://learn.hashicorp.com/tutorials/terraform/cdktf-build?in=terraform/cdktf
Then just copy additional commands from my package.json file.
Modify tsconfig.json
Also, I'd like to have absolute imports and separated directories for my code, that's why I will change tsconfig.json file (of you can copy-paste it from here).
1{
2 "compilerOptions": {
3 "baseUrl": "src", // added for source code dir
4 "alwaysStrict": true,
5 "charset": "utf8",
6 "declaration": true,
7 "experimentalDecorators": true,
8 "inlineSourceMap": true,
9 "inlineSources": true,
10 "lib": ["es2018"],
11 "paths": {
12 "*": ["*"]
13 },
14 "outDir": "./dist", // here we will have or js code
15 "allowSyntheticDefaultImports": true,
16 "esModuleInterop": true,
17 "module": "commonjs",
18 "moduleResolution": "node",
19 "noEmitOnError": true,
20 "noFallthroughCasesInSwitch": true,
21 "noImplicitAny": true,
22 "noImplicitReturns": true,
23 "noImplicitThis": true,
24 "noUnusedLocals": true,
25 "noUnusedParameters": true,
26 "resolveJsonModule": true,
27 "strict": true,
28 "strictNullChecks": true,
29 "strictPropertyInitialization": true,
30 "stripInternal": true,
31 "target": "es6"
32 },
33 "include": ["src"],
34 "exclude": ["node_modules"],
35 "moduleDirectories": ["node_modules", "src"]
36}
37
Compare it with:
https://github.com/hashicorp/terraform-cdk/blob/main/examples/typescript/aws/tsconfig.json
Final touches
In case if you prefer using src
folder you also have to correspondingly change your cdktf.json
file.
1{
2 "language": "typescript",
3 "app": "npm run --silent compile && NODE_PATH=./dist node dist/main.js",
4 "terraformProviders": [
5 "aws@~> 2.0"
6 ],
7 "context": {
8 "excludeStackIdFromLogicalIds": "true",
9 "allowSepCharsInLogicalIds": "true"
10 }
11}
12
Also, you can notice that I use main.js
instead of index.js
Install required packages
After it we can run npm install and create src firectory.
In the next chapter, we will setup additional packages and make a simple configuration for AWS.