├── .gitignore
├── 0_remote_state
├── .terraform-docs.yml
├── README.md
├── data.tf
├── dynamodb.tf
├── img
│ └── Remote-state.png
├── main.tf
├── outputs.tf
├── provider.tf
├── s3.tf
└── usage.md
├── 1_pipeline
├── .terraform-docs.yml
├── README.md
├── artifacts_s3.tf
├── buildspecs
│ ├── buildspec.yml
│ ├── checkov.yml
│ ├── infracost.yml
│ ├── opa.yml
│ ├── terrascan.yml
│ ├── terratest.yml
│ └── tflint.yml
├── codebuild.tf
├── codepipeline.tf
├── data.tf
├── img
│ └── CICD-pipeline-architecture.png
├── main.tf
├── provider.tf
├── ssm_parameters.tf
├── usage.md
└── variables.tf
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .terraform/
3 | .terraform.lock.hcl
4 | *.tfstate*
--------------------------------------------------------------------------------
/0_remote_state/.terraform-docs.yml:
--------------------------------------------------------------------------------
1 | formatter: "markdown"
2 |
3 | version: ""
4 |
5 | header-from: main.tf
6 | footer-from: ""
7 |
8 | recursive:
9 | enabled: false
10 | path: modules
11 |
12 | sections:
13 | hide: []
14 | show: []
15 |
16 | content: |-
17 | {{ include "./usage.md" }}
18 | {{ .Providers }}
19 | {{ .Resources }}
20 | {{ .Outputs }}
21 |
22 | output:
23 | file: README.md
24 | mode: inject
25 | template: |-
26 |
27 |
28 | {{ .Content }}
29 |
30 |
31 |
32 | output-values:
33 | enabled: false
34 | from: ""
35 |
36 | sort:
37 | enabled: true
38 | by: name
39 |
40 | settings:
41 | anchor: true
42 | color: true
43 | default: true
44 | description: false
45 | escape: true
46 | hide-empty: false
47 | html: true
48 | indent: 2
49 | lockfile: true
50 | read-comments: true
51 | required: true
52 | sensitive: true
53 | type: true
54 |
--------------------------------------------------------------------------------
/0_remote_state/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Terraform remote state
4 |
5 | This module deploys AWS infrastructure to store Terraform remote state in S3 bucket and lock Terraform execution in DynamoDB table.
6 |
7 | 
8 |
9 | ## Deployment
10 |
11 | ```sh
12 | terraform init
13 | terraform plan
14 | terraform apply -auto-approve
15 | ```
16 |
17 | ## Tier down
18 |
19 | ```sh
20 | terraform destroy -auto-approve
21 | ```
22 | ## Providers
23 |
24 | | Name | Version |
25 | |------|---------|
26 | | [aws](#provider\_aws) | n/a |
27 | ## Resources
28 |
29 | | Name | Type |
30 | |------|------|
31 | | [aws_dynamodb_table.lock_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
32 | | [aws_s3_bucket.remote_state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
33 | | [aws_s3_bucket_policy.remote_state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
34 | | [aws_s3_bucket_public_access_block.s3Public_remote_state](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
35 | | [aws_ssm_parameter.locks_table_arn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
36 | | [aws_ssm_parameter.remote_state_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
37 | ## Outputs
38 |
39 | | Name | Description |
40 | |------|-------------|
41 | | [dynamodb-lock-table](#output\_dynamodb-lock-table) | DynamoDB table for Terraform execution locks |
42 | | [dynamodb-lock-table-ssm-parameter](#output\_dynamodb-lock-table-ssm-parameter) | SSM parameter containing DynamoDB table for Terraform execution locks |
43 | | [s3-state-bucket](#output\_s3-state-bucket) | S3 bucket for storing Terraform state |
44 | | [s3-state-bucket-ssm-parameter](#output\_s3-state-bucket-ssm-parameter) | SSM parameter containing S3 bucket for storing Terraform state |
45 |
46 |
--------------------------------------------------------------------------------
/0_remote_state/data.tf:
--------------------------------------------------------------------------------
1 | data "aws_caller_identity" "current_account" {}
2 |
--------------------------------------------------------------------------------
/0_remote_state/dynamodb.tf:
--------------------------------------------------------------------------------
1 | resource "aws_dynamodb_table" "lock_table" {
2 | name = "${local.prefix}-dynamodb"
3 | billing_mode = "PROVISIONED"
4 | read_capacity = 5
5 | write_capacity = 5
6 | hash_key = "LockID"
7 | tags = local.common_tags
8 |
9 | attribute {
10 | name = "LockID"
11 | type = "S"
12 | }
13 | }
14 |
15 | resource "aws_ssm_parameter" "locks_table_arn" {
16 | name = "${local.ssm_prefix}/tf-locks-table-arn"
17 | type = "String"
18 | value = aws_dynamodb_table.lock_table.arn
19 | }
20 |
--------------------------------------------------------------------------------
/0_remote_state/img/Remote-state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hands-on-cloud/aws-codepipeline-terraform-cicd-pipeline/7a44c6733a51da39eda9356e9e60a61334559db6/0_remote_state/img/Remote-state.png
--------------------------------------------------------------------------------
/0_remote_state/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | aws_region = "us-west-2"
3 | prefix = "hands-on-cloud-terraform-remote-state"
4 | ssm_prefix = "/org/hands-on-cloud/terraform"
5 | common_tags = {
6 | Project = "hands-on-cloud"
7 | ManagedBy = "Terraform"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/0_remote_state/outputs.tf:
--------------------------------------------------------------------------------
1 | output "dynamodb-lock-table" {
2 | value = aws_dynamodb_table.lock_table.name
3 | description = "DynamoDB table for Terraform execution locks"
4 | }
5 |
6 | output "dynamodb-lock-table-ssm-parameter" {
7 | value = "${local.ssm_prefix}/tf-locks-table-arn"
8 | description = "SSM parameter containing DynamoDB table for Terraform execution locks"
9 | }
10 |
11 | output "s3-state-bucket" {
12 | value = aws_s3_bucket.remote_state.id
13 | description = "S3 bucket for storing Terraform state"
14 | }
15 |
16 | output "s3-state-bucket-ssm-parameter" {
17 | value = "${local.ssm_prefix}/tf-remote-state-bucket"
18 | description = "SSM parameter containing S3 bucket for storing Terraform state"
19 | }
20 |
--------------------------------------------------------------------------------
/0_remote_state/provider.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = local.aws_region
3 | }
4 |
--------------------------------------------------------------------------------
/0_remote_state/s3.tf:
--------------------------------------------------------------------------------
1 | resource "aws_s3_bucket" "remote_state" {
2 | #checkov:skip=CKV_AWS_144: "Cross Region Unneccessary"
3 | #checkov:skip=CKV_AWS_145: "Bucket Encryption IS enabled separately"
4 |
5 | bucket = "${local.prefix}-${data.aws_caller_identity.current_account.id}"
6 | force_destroy = true
7 |
8 | lifecycle {
9 | prevent_destroy = false
10 | }
11 |
12 | tags = local.common_tags
13 | }
14 |
15 | resource "aws_s3_bucket_server_side_encryption_configuration" "remote_state" {
16 | bucket = aws_s3_bucket.remote_state.id
17 | rule {
18 | apply_server_side_encryption_by_default {
19 | sse_algorithm = "AES256"
20 | }
21 | bucket_key_enabled = true
22 | }
23 | }
24 |
25 | resource "aws_s3_bucket_versioning" "remote_state_versioning" {
26 | bucket = aws_s3_bucket.remote_state.id
27 | versioning_configuration {
28 | status = "Enabled"
29 | }
30 | }
31 |
32 |
33 | resource "aws_s3_bucket_acl" "remote_state_acl" {
34 | bucket = aws_s3_bucket.remote_state.id
35 | acl = "private"
36 | }
37 |
38 |
39 | resource "aws_s3_bucket_public_access_block" "s3Public_remote_state" {
40 | depends_on = [aws_s3_bucket_policy.remote_state]
41 | bucket = aws_s3_bucket.remote_state.id
42 | block_public_acls = true
43 | block_public_policy = true
44 | ignore_public_acls = true
45 | restrict_public_buckets = true
46 | }
47 |
48 | resource "aws_s3_bucket_policy" "remote_state" {
49 | bucket = aws_s3_bucket.remote_state.id
50 |
51 | policy = <
27 |
28 | {{ .Content }}
29 |
30 |
31 |
32 | output-values:
33 | enabled: false
34 | from: ""
35 |
36 | sort:
37 | enabled: true
38 | by: name
39 |
40 | settings:
41 | anchor: true
42 | color: true
43 | default: true
44 | description: false
45 | escape: true
46 | hide-empty: false
47 | html: true
48 | indent: 2
49 | lockfile: true
50 | read-comments: true
51 | required: true
52 | sensitive: true
53 | type: true
54 |
--------------------------------------------------------------------------------
/1_pipeline/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # AWS CodePipeline demo CICD pipeline for testing Terraform projects
4 |
5 | This module deploys AWS CodePipeline, which uses tflint, Checkov, OPA, Terrascan, and Terratest to test Terraform modules.
6 |
7 | Check out [How to use CodePipeline CICD pipeline to test Terraform](https://hands-on.cloud/how-to-use-codepipeline-cicd-pipeline-to-test-terraform/) article for more information.
8 |
9 | 
10 |
11 | ## Deployment
12 |
13 | ```sh
14 | terraform init
15 | terraform plan
16 | terraform apply -auto-approve
17 | ```
18 |
19 | ## Tier down
20 |
21 | ```sh
22 | terraform destroy -auto-approve
23 | ```
24 | ## Providers
25 |
26 | | Name | Version |
27 | |------|---------|
28 | | [aws](#provider\_aws) | 3.63.0 |
29 | ## Resources
30 |
31 | | Name | Type |
32 | |------|------|
33 | | [aws_codebuild_project.checkov](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
34 | | [aws_codebuild_project.opa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
35 | | [aws_codebuild_project.terrascan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
36 | | [aws_codebuild_project.terratest](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
37 | | [aws_codebuild_project.tf_apply](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
38 | | [aws_codebuild_project.tflint](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) | resource |
39 | | [aws_codepipeline.demo](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codepipeline) | resource |
40 | | [aws_iam_role.codebuild](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
41 | | [aws_iam_role.codepipeline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
42 | | [aws_iam_role_policy.codebuild](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
43 | | [aws_iam_role_policy.codepipeline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
44 | | [aws_s3_bucket.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
45 | | [aws_s3_bucket_public_access_block.s3Public_artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
46 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
47 | | [aws_ssm_parameter.locks_table_arn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
48 | | [aws_ssm_parameter.remote_state_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
49 | ## Outputs
50 |
51 | No outputs.
52 |
53 |
--------------------------------------------------------------------------------
/1_pipeline/artifacts_s3.tf:
--------------------------------------------------------------------------------
1 | resource "aws_s3_bucket" "artifacts" {
2 | #checkov:skip=CKV_AWS_144: "Cross Region Unneccessary"
3 | #checkov:skip=CKV_AWS_145: "Bucket Encryption IS enabled separately"
4 |
5 | bucket = "${var.project_name}-s3"
6 | force_destroy = true
7 | lifecycle {
8 | prevent_destroy = false
9 | }
10 |
11 | tags = local.common_tags
12 | }
13 |
14 | resource "aws_s3_bucket_public_access_block" "s3Public_artifacts" {
15 | bucket = aws_s3_bucket.artifacts.id
16 | block_public_acls = true
17 | block_public_policy = true
18 | ignore_public_acls = true
19 | restrict_public_buckets = true
20 | }
21 |
22 |
23 |
24 | resource "aws_s3_bucket_logging" "example" {
25 | bucket = aws_s3_bucket.artifacts.id
26 | target_bucket = var.logging_bucket
27 | target_prefix = "s3/${aws_s3_bucket.artifacts.id}/"
28 | }
29 |
30 |
31 | resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
32 | bucket = aws_s3_bucket.artifacts.id
33 | rule {
34 | apply_server_side_encryption_by_default {
35 | sse_algorithm = "AES256"
36 | }
37 | bucket_key_enabled = true
38 | }
39 | }
40 |
41 | resource "aws_s3_bucket_versioning" "versioning" {
42 | bucket = aws_s3_bucket.artifacts.id
43 | versioning_configuration {
44 | status = "Enabled"
45 | }
46 | }
47 |
48 |
49 | resource "aws_s3_bucket_acl" "bucket_acl" {
50 | bucket = aws_s3_bucket.artifacts.id
51 | acl = "private"
52 | }
53 |
54 |
55 | resource "aws_s3_bucket_logging" "artifacts" {
56 | bucket = aws_s3_bucket.artifacts.id
57 | target_bucket = var.logging_bucket
58 | target_prefix = "s3/${aws_s3_bucket.artifacts.id}/"
59 | }
60 |
61 | resource "aws_s3_bucket_policy" "artifacts" {
62 | bucket = aws_s3_bucket.artifacts.id
63 |
64 | policy = < tf.json
19 | - opa eval --format pretty --data ./test/opa/terraform.rego --input tf.json "data.terraform"
20 | post_build:
21 | commands:
22 | - echo "OPA Test completed on `date`"
23 |
--------------------------------------------------------------------------------
/1_pipeline/buildspecs/terrascan.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 | env:
3 | variables:
4 | TF_VERSION: "1.0.6"
5 | TERRASCAN_VERSION: "1.9.0"
6 | phases:
7 | install:
8 | runtime-versions:
9 | python: latest
10 | commands:
11 | - cd /usr/bin
12 | - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
13 | - unzip -o terraform.zip
14 | - "curl -L -o terrascan_${TERRASCAN_VERSION}_Linux_x86_64.tar.gz https://github.com/accurics/terrascan/releases/download/v${TERRASCAN_VERSION}/terrascan_${TERRASCAN_VERSION}_Linux_x86_64.tar.gz"
15 | - "tar -xf terrascan_${TERRASCAN_VERSION}_Linux_x86_64.tar.gz terrascan"
16 | build:
17 | commands:
18 | - cd "$CODEBUILD_SRC_DIR"
19 | - terrascan init
20 | - terrascan scan -i terraform
21 | post_build:
22 | commands:
23 | - echo "Terrascan test is completed on `date`"
24 |
--------------------------------------------------------------------------------
/1_pipeline/buildspecs/terratest.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 | env:
3 | variables:
4 | TF_VERSION: "1.0.6"
5 | phases:
6 | install:
7 | commands:
8 | - cd /usr/bin
9 | - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
10 | - unzip -o terraform.zip
11 | build:
12 | commands:
13 | - cd "$CODEBUILD_SRC_DIR"
14 | - cd test/terratest
15 | - go mod init "tftest"
16 | - go get github.com/gruntwork-io/terratest/modules/aws
17 | - go get github.com/gruntwork-io/terratest/modules/terraform@v0.38.2
18 | - go test -v
19 | post_build:
20 | commands:
21 | - echo "terratest completed on `date`"
22 |
--------------------------------------------------------------------------------
/1_pipeline/buildspecs/tflint.yml:
--------------------------------------------------------------------------------
1 | version: 0.2
2 | env:
3 | variables:
4 | TF_VERSION: "1.0.6"
5 | phases:
6 | install:
7 | commands:
8 | - cd /usr/bin
9 | - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
10 | - unzip -o terraform.zip
11 | - "curl --location https://github.com/terraform-linters/tflint/releases/download/v0.33.0/tflint_linux_amd64.zip --output tflint_linux_amd64.zip"
12 | - unzip -o tflint_linux_amd64.zip
13 | build:
14 | commands:
15 | - cd "$CODEBUILD_SRC_DIR"
16 | - terraform init
17 | - terraform validate
18 | - tflint --init
19 | - tflint
20 | post_build:
21 | commands:
22 | - echo "terraform validate completed on `date`"
23 | - echo "tflint completed on `date`"
24 |
--------------------------------------------------------------------------------
/1_pipeline/codebuild.tf:
--------------------------------------------------------------------------------
1 | # IAM
2 |
3 | resource "aws_iam_role" "codebuild" {
4 | description = "CodeBuild Service Role - Managed by Terraform"
5 | tags = local.common_tags
6 |
7 | assume_role_policy = jsonencode(
8 | {
9 | "Version" : "2012-10-17",
10 | "Statement" : [
11 | {
12 | "Effect" : "Allow",
13 | "Principal" : {
14 | "Service" : "codebuild.amazonaws.com"
15 | },
16 | "Action" : "sts:AssumeRole"
17 | }
18 | ]
19 | }
20 | )
21 | }
22 |
23 | resource "aws_iam_role_policy" "codebuild" {
24 | role = aws_iam_role.codebuild.id
25 |
26 | policy = jsonencode(
27 | {
28 | "Version" : "2012-10-17",
29 | "Statement" : [
30 | {
31 | "Effect" : "Allow",
32 | "Action" : [
33 | "s3:*"
34 | ],
35 | "Resource" : "*"
36 | },
37 | {
38 | "Effect" : "Allow",
39 | "Action" : [
40 | "dynamodb:GetItem",
41 | "dynamodb:PutItem",
42 | "dynamodb:DeleteItem"
43 | ],
44 | "Resource" : data.aws_ssm_parameter.locks_table_arn.value
45 | },
46 | {
47 | "Effect" : "Allow",
48 | "Action" : [
49 | "kms:*"
50 | ],
51 | "Resource" : "*"
52 | },
53 | {
54 | "Effect" : "Allow",
55 | "Action" : [
56 | "ssm:*"
57 | ],
58 | "Resource" : "*"
59 | },
60 | {
61 | "Effect" : "Allow",
62 | "Action" : [
63 | "logs:CreateLogGroup",
64 | "logs:CreateLogStream",
65 | "logs:PutLogEvents"
66 | ],
67 | "Resource" : "*"
68 | }
69 | ]
70 | }
71 | )
72 | }
73 |
74 | # CodeBuild
75 | resource "aws_codebuild_project" "tflint" {
76 | #checkov:skip=CKV_AWS_147: "temp"
77 |
78 | name = "${var.project_name}-tflint"
79 | description = "Managed using Terraform"
80 | service_role = aws_iam_role.codebuild.arn
81 | tags = local.common_tags
82 |
83 | artifacts {
84 | type = "CODEPIPELINE"
85 | }
86 |
87 | environment {
88 | compute_type = "BUILD_GENERAL1_SMALL"
89 | image = "aws/codebuild/standard:6.0"
90 | type = "LINUX_CONTAINER"
91 | }
92 |
93 | source {
94 | type = "CODEPIPELINE"
95 | buildspec = data.local_file.tflint.content
96 | }
97 | }
98 |
99 | resource "aws_codebuild_project" "checkov" {
100 | #checkov:skip=CKV_AWS_147: "temp"
101 |
102 | name = "${var.project_name}-checkov"
103 | description = "Managed using Terraform"
104 | service_role = aws_iam_role.codebuild.arn
105 | tags = local.common_tags
106 |
107 | artifacts {
108 | type = "CODEPIPELINE"
109 | }
110 |
111 | environment {
112 | compute_type = "BUILD_GENERAL1_MEDIUM"
113 | image = "aws/codebuild/standard:6.0"
114 | type = "LINUX_CONTAINER"
115 | }
116 |
117 | source {
118 | type = "CODEPIPELINE"
119 | buildspec = data.local_file.checkov.content
120 | }
121 | }
122 |
123 | resource "aws_codebuild_project" "opa" {
124 | #checkov:skip=CKV_AWS_147: "temp"
125 |
126 | name = "${var.project_name}-opa"
127 | description = "Managed using Terraform"
128 | service_role = aws_iam_role.codebuild.arn
129 | tags = local.common_tags
130 |
131 | artifacts {
132 | type = "CODEPIPELINE"
133 | }
134 |
135 | environment {
136 | compute_type = "BUILD_GENERAL1_SMALL"
137 | image = "aws/codebuild/standard:6.0"
138 | type = "LINUX_CONTAINER"
139 | }
140 |
141 | source {
142 | type = "CODEPIPELINE"
143 | buildspec = data.local_file.opa.content
144 | }
145 | }
146 |
147 | resource "aws_codebuild_project" "terrascan" {
148 | #checkov:skip=CKV_AWS_147: "temp"
149 |
150 | name = "${var.project_name}-terrascan"
151 | description = "Managed using Terraform"
152 | service_role = aws_iam_role.codebuild.arn
153 | tags = local.common_tags
154 |
155 | artifacts {
156 | type = "CODEPIPELINE"
157 | }
158 |
159 | environment {
160 | compute_type = "BUILD_GENERAL1_SMALL"
161 | image = "aws/codebuild/standard:6.0"
162 | type = "LINUX_CONTAINER"
163 | }
164 |
165 | source {
166 | type = "CODEPIPELINE"
167 | buildspec = data.local_file.terrascan.content
168 | }
169 | }
170 |
171 | resource "aws_codebuild_project" "terratest" {
172 | #checkov:skip=CKV_AWS_147: "temp"
173 |
174 | name = "${var.project_name}-terratest"
175 | description = "Managed using Terraform"
176 | service_role = aws_iam_role.codebuild.arn
177 | tags = local.common_tags
178 |
179 | artifacts {
180 | type = "CODEPIPELINE"
181 | }
182 |
183 | environment {
184 | compute_type = "BUILD_GENERAL1_SMALL"
185 | image = "aws/codebuild/standard:6.0"
186 | type = "LINUX_CONTAINER"
187 | }
188 |
189 | source {
190 | type = "CODEPIPELINE"
191 | buildspec = data.local_file.terratest.content
192 | }
193 | }
194 |
195 | resource "aws_codebuild_project" "infracost" {
196 | #checkov:skip=CKV_AWS_147: "temp"
197 |
198 | name = "${var.project_name}-infracost"
199 | description = "Managed using Terraform"
200 | service_role = aws_iam_role.codebuild.arn
201 | tags = local.common_tags
202 |
203 | artifacts {
204 | type = "CODEPIPELINE"
205 | }
206 |
207 | environment {
208 | compute_type = "BUILD_GENERAL1_SMALL"
209 | image = "aws/codebuild/standard:6.0"
210 | type = "LINUX_CONTAINER"
211 |
212 | environment_variable {
213 | name = "INFRACOST_API_KEY_SSM_PARAM_NAME"
214 | value = "${local.ssm_prefix}/infracost_api_key"
215 | }
216 | }
217 |
218 | source {
219 | type = "CODEPIPELINE"
220 | buildspec = data.local_file.infracost.content
221 | }
222 | }
223 |
224 | resource "aws_codebuild_project" "tf_apply" {
225 | #checkov:skip=CKV_AWS_147: "temp"
226 |
227 | name = "${var.project_name}-tf-apply"
228 | description = "Managed using Terraform"
229 | service_role = aws_iam_role.codebuild.arn
230 | tags = local.common_tags
231 |
232 | artifacts {
233 | type = "CODEPIPELINE"
234 | }
235 |
236 | environment {
237 | compute_type = "BUILD_GENERAL1_SMALL"
238 | image = "aws/codebuild/standard:6.0"
239 | type = "LINUX_CONTAINER"
240 | }
241 |
242 | source {
243 | type = "CODEPIPELINE"
244 | buildspec = data.local_file.buildspec.content
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/1_pipeline/codepipeline.tf:
--------------------------------------------------------------------------------
1 | # IAM
2 |
3 | resource "aws_iam_role" "codepipeline" {
4 | description = "CodePipeline Service Role - Managed by Terraform"
5 | tags = local.common_tags
6 |
7 | assume_role_policy = jsonencode(
8 | {
9 | "Version" : "2012-10-17",
10 | "Statement" : [
11 | {
12 | "Effect" : "Allow",
13 | "Principal" : {
14 | "Service" : "codepipeline.amazonaws.com"
15 | },
16 | "Action" : "sts:AssumeRole"
17 | }
18 | ]
19 | }
20 | )
21 | }
22 |
23 | resource "aws_iam_role_policy" "codepipeline" {
24 | role = aws_iam_role.codepipeline.id
25 |
26 | policy = jsonencode(
27 | {
28 | "Version" : "2012-10-17",
29 | "Statement" : [
30 | {
31 | "Effect":"Allow",
32 | "Action": [
33 | "s3:*"
34 | ],
35 | "Resource": "*"
36 | },
37 | {
38 | "Effect" : "Allow",
39 | "Action" : "iam:PassRole",
40 | "Resource" : aws_iam_role.codebuild.arn
41 | },
42 | {
43 | "Effect" : "Allow",
44 | "Action" : [
45 | "codecommit:BatchGet*",
46 | "codecommit:BatchDescribe*",
47 | "codecommit:Describe*",
48 | "codecommit:Get*",
49 | "codecommit:List*",
50 | "codecommit:GitPull",
51 | "codecommit:UploadArchive",
52 | "codecommit:GetBranch",
53 | ],
54 | "Resource" : "*"
55 | },
56 | {
57 | "Effect" : "Allow",
58 | "Action" : [
59 | "codebuild:StartBuild",
60 | "codebuild:StopBuild",
61 | "codebuild:BatchGetBuilds"
62 | ],
63 | "Resource" : [
64 | aws_codebuild_project.tflint.arn,
65 | aws_codebuild_project.checkov.arn,
66 | aws_codebuild_project.opa.arn,
67 | aws_codebuild_project.terrascan.arn,
68 | aws_codebuild_project.terratest.arn,
69 | aws_codebuild_project.infracost.arn,
70 | aws_codebuild_project.tf_apply.arn
71 | ]
72 | }
73 | ]
74 | }
75 | )
76 | }
77 |
78 | # CodePipeline
79 |
80 | resource "aws_codepipeline" "demo" {
81 | name = "${local.prefix}-demo"
82 | role_arn = aws_iam_role.codepipeline.arn
83 | tags = local.common_tags
84 |
85 | artifact_store {
86 | location = aws_s3_bucket.artifacts.id
87 | type = "S3"
88 | }
89 |
90 | stage {
91 | name = "Clone"
92 |
93 | action {
94 | name = "Source"
95 | category = "Source"
96 | owner = "AWS"
97 | provider = "CodeCommit"
98 | version = "1"
99 | output_artifacts = ["CodeWorkspace"]
100 |
101 | configuration = {
102 | RepositoryName = var.repository_name
103 | BranchName = var.listen_branch_name
104 | PollForSourceChanges = true
105 | }
106 | }
107 | }
108 |
109 | stage {
110 | name = "Terraform-Project-Testing"
111 |
112 | action {
113 | run_order = 1
114 | name = "tflint"
115 | category = "Build"
116 | owner = "AWS"
117 | provider = "CodeBuild"
118 | input_artifacts = ["CodeWorkspace"]
119 | output_artifacts = []
120 | version = "1"
121 |
122 | configuration = {
123 | ProjectName = aws_codebuild_project.tflint.name
124 | }
125 | }
126 |
127 | action {
128 | run_order = 1
129 | name = "checkov"
130 | category = "Build"
131 | owner = "AWS"
132 | provider = "CodeBuild"
133 | input_artifacts = ["CodeWorkspace"]
134 | output_artifacts = []
135 | version = "1"
136 |
137 | configuration = {
138 | ProjectName = aws_codebuild_project.checkov.name
139 | }
140 | }
141 |
142 | action {
143 | run_order = 1
144 | name = "opa"
145 | category = "Build"
146 | owner = "AWS"
147 | provider = "CodeBuild"
148 | input_artifacts = ["CodeWorkspace"]
149 | output_artifacts = []
150 | version = "1"
151 |
152 | configuration = {
153 | ProjectName = aws_codebuild_project.opa.name
154 | }
155 | }
156 |
157 | action {
158 | run_order = 1
159 | name = "terrascan"
160 | category = "Build"
161 | owner = "AWS"
162 | provider = "CodeBuild"
163 | input_artifacts = ["CodeWorkspace"]
164 | output_artifacts = []
165 | version = "1"
166 |
167 | configuration = {
168 | ProjectName = aws_codebuild_project.terrascan.name
169 | }
170 | }
171 |
172 | action {
173 | run_order = 2
174 | name = "terratest"
175 | category = "Build"
176 | owner = "AWS"
177 | provider = "CodeBuild"
178 | input_artifacts = ["CodeWorkspace"]
179 | output_artifacts = []
180 | version = "1"
181 |
182 | configuration = {
183 | ProjectName = aws_codebuild_project.terratest.name
184 | }
185 | }
186 |
187 | action {
188 | run_order = 1
189 | name = "infracost"
190 | category = "Build"
191 | owner = "AWS"
192 | provider = "CodeBuild"
193 | input_artifacts = ["CodeWorkspace"]
194 | output_artifacts = []
195 | version = "1"
196 |
197 | configuration = {
198 | ProjectName = aws_codebuild_project.infracost.name
199 | }
200 | }
201 | }
202 |
203 | stage {
204 | name = "Manual-Approval"
205 |
206 | action {
207 | run_order = 1
208 | name = "DevOps-Lead-Approval"
209 | category = "Approval"
210 | owner = "AWS"
211 | provider = "Manual"
212 | version = "1"
213 | }
214 | }
215 |
216 | stage {
217 | name = "Deploy"
218 |
219 | action {
220 | run_order = 1
221 | name = "terraform-apply"
222 | category = "Build"
223 | owner = "AWS"
224 | provider = "CodeBuild"
225 | input_artifacts = ["CodeWorkspace"]
226 | output_artifacts = []
227 | version = "1"
228 |
229 | configuration = {
230 | ProjectName = aws_codebuild_project.tf_apply.name
231 | }
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/1_pipeline/data.tf:
--------------------------------------------------------------------------------
1 |
2 | data "aws_caller_identity" "current_account" {
3 | # To retrieve the account ID -- needed for KMS key policy
4 | }
5 |
6 |
7 | data "aws_region" "current_region" {
8 | # To retrieve the current AWS region
9 | }
10 |
11 | ##### Buildspecs #####
12 | data "local_file" "buildspec" {
13 | filename = "${path.module}/buildspecs/buildspec.yml"
14 | }
15 |
16 | data "local_file" "checkov" {
17 | filename = "${path.module}/buildspecs/checkov.yml"
18 | }
19 |
20 |
21 | data "local_file" "infracost" {
22 | filename = "${path.module}/buildspecs/infracost.yml"
23 | }
24 |
25 | data "local_file" "opa" {
26 | filename = "${path.module}/buildspecs/opa.yml"
27 | }
28 |
29 | data "local_file" "terrascan" {
30 | filename = "${path.module}/buildspecs/terrascan.yml"
31 | }
32 |
33 | data "local_file" "terratest" {
34 | filename = "${path.module}/buildspecs/terratest.yml"
35 | }
36 |
37 | data "local_file" "tflint" {
38 | filename = "${path.module}/buildspecs/tflint.yml"
39 | }
40 |
--------------------------------------------------------------------------------
/1_pipeline/img/CICD-pipeline-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hands-on-cloud/aws-codepipeline-terraform-cicd-pipeline/7a44c6733a51da39eda9356e9e60a61334559db6/1_pipeline/img/CICD-pipeline-architecture.png
--------------------------------------------------------------------------------
/1_pipeline/main.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | backend "s3" {
3 | bucket = "hands-on-cloud-terraform-remote-state-s3"
4 | key = "hands-on-cloud-terraform-demo-pipeline.tfstate"
5 | region = "us-west-2"
6 | encrypt = "true"
7 | }
8 | }
9 |
10 | locals {
11 | aws_region = "us-west-2"
12 | prefix = "${var.repository_name}-${var.listen_branch_name}-pipeline"
13 | ssm_prefix = "/org/hands-on-cloud/terraform"
14 | common_tags = {
15 | Project = local.prefix
16 | ManagedBy = "Terraform"
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/1_pipeline/provider.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = local.aws_region
3 | }
4 |
--------------------------------------------------------------------------------
/1_pipeline/ssm_parameters.tf:
--------------------------------------------------------------------------------
1 | data "aws_caller_identity" "current" {}
2 |
3 | data "aws_ssm_parameter" "remote_state_bucket" {
4 | name = "${local.ssm_prefix}/tf-remote-state-bucket"
5 | }
6 |
7 | data "aws_ssm_parameter" "locks_table_arn" {
8 | name = "${local.ssm_prefix}/tf-locks-table-arn"
9 | }
10 |
--------------------------------------------------------------------------------
/1_pipeline/usage.md:
--------------------------------------------------------------------------------
1 | # AWS CodePipeline demo CICD pipeline for testing Terraform projects
2 |
3 | This module deploys AWS CodePipeline, which uses tflint, Checkov, OPA, Terrascan, and Terratest to test Terraform modules.
4 |
5 | Check out [How to use CodePipeline CICD pipeline to test Terraform](https://hands-on.cloud/how-to-use-codepipeline-cicd-pipeline-to-test-terraform/) article for more information.
6 |
7 | 
8 |
9 | ## Deployment
10 |
11 | Manually create SSM Parameter store parameter to store Infracost API key. For example:
12 |
13 | * Key name: `/org/hands-on-cloud/terraform/infracost_api_key`
14 | * Type: `SecureString`
15 | * Description: `Infracost API key`
16 | * Value: `YOUR_INFRACOST_API_KEY` (Use `infracost register` to get one)
17 |
18 | By default, we're using the following prefix for SSM Parameter Store keys `/org/hands-on-cloud/terraform` (defined in [ssm_parameters](ssm_parameters.tf) file).
19 |
20 | ```sh
21 | terraform init
22 | terraform plan
23 | terraform apply -auto-approve
24 | ```
25 |
26 | ## Tier down
27 |
28 | ```sh
29 | terraform destroy -auto-approve
30 | ```
31 |
--------------------------------------------------------------------------------
/1_pipeline/variables.tf:
--------------------------------------------------------------------------------
1 | variable "repository_name" {
2 | default = "tf-demo-project"
3 | description = "CodeCommit repository name for CodePipeline builds"
4 | }
5 |
6 | variable "listen_branch_name" {
7 | default = "master"
8 | description = "CodeCommit branch name for CodePipeline builds"
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to use CodePipeline CICD pipeline to test Terraform modules
2 |
3 | This is a demo Terraform repository to set up AWS CodePipeline to test Terraform projects using tflint, Checkov, OPA, Terrascan, and Terratest.
4 |
5 | Check out [How to use CodePipeline CICD pipeline to test Terraform](https://hands-on.cloud/how-to-use-codepipeline-cicd-pipeline-to-test-terraform/) article for more information.
6 |
7 | 
8 |
9 | ## Set up Terraform remote state infrastructure
10 |
11 | This step is required to set up an infrastructure to store Terraform remote state files
12 |
13 | ```sh
14 | cd 0_remote_state
15 | terraform init
16 | terraform plan
17 | terraform apply -auto-approve
18 | ```
19 |
20 | ## Set up AWS CodePipeline
21 |
22 | This step is required to set up an AWS CodePipeline to test Terraform projects using tflint, Checkov, OPA, Terrascan, and Terratest.
23 |
24 | ```sh
25 | cd 1_pipeline
26 | terraform init
27 | terraform plan
28 | terraform apply -auto-approve
29 | ```
30 |
--------------------------------------------------------------------------------