From 9d9053ca8c599b8161a3a429ed6f285ec4cffe12 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 10:29:56 +0000
Subject: [PATCH 01/15] ci(trunk): Linter and version upgrade
---
.trunk/trunk.yaml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 8df0b6d..421b950 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -2,12 +2,12 @@
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
version: 0.1
cli:
- version: 1.22.6
+ version: 1.22.7
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
plugins:
sources:
- id: trunk
- ref: v1.6.3
+ ref: v1.6.4
uri: https://github.com/trunk-io/plugins
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
runtimes:
@@ -19,17 +19,17 @@ lint:
disabled:
- regal
enabled:
- - renovate@38.117.1
+ - renovate@38.135.2
- trivy@0.56.2
- tflint@0.53.0
- terraform@1.9.7:
commands: [fmt, validate]
- actionlint@1.7.3
- - checkov@3.2.257
+ - checkov@3.2.276
- git-diff-check
- markdownlint@0.42.0
- prettier@3.3.3
- - trufflehog@3.82.8
+ - trufflehog@3.83.1
- yamllint@1.35.1
definitions:
- name: tflint
From 65a8007c2ad3bc4d236213ca535edf9a29d9bd6d Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 10:31:44 +0000
Subject: [PATCH 02/15] chore(terraform): Improve version constraints
---
terraform/development/.terraform.lock.hcl | 22 +---------------------
terraform/development/providers.tf | 15 ---------------
terraform/development/versions.tf | 14 ++++++++++++--
3 files changed, 13 insertions(+), 38 deletions(-)
diff --git a/terraform/development/.terraform.lock.hcl b/terraform/development/.terraform.lock.hcl
index 3bd1093..3a9e66b 100644
--- a/terraform/development/.terraform.lock.hcl
+++ b/terraform/development/.terraform.lock.hcl
@@ -3,7 +3,7 @@
provider "registry.terraform.io/hashicorp/aws" {
version = "5.69.0"
- constraints = "5.69.0"
+ constraints = "~> 5.69.0"
hashes = [
"h1:unGIj/eLOrl42LQm7u0fjtjQHp+FHKinpSxR1ZuWsfI=",
"zh:123af8815a80abfd62eab5f9fc3d9226735cfea3627e834a1b48321cd8d391a6",
@@ -23,23 +23,3 @@ provider "registry.terraform.io/hashicorp/aws" {
"zh:eb71c9b2071ab2caa7aba577902df41c25ded1251c28560f0ac45f5e0f47360e",
]
}
-
-provider "registry.terraform.io/hashicorp/http" {
- version = "3.4.5"
- constraints = "3.4.5"
- hashes = [
- "h1:eSVCYfvn5JyV3LC0+mrLlLtgLv4B+RWeNqz02miBcMY=",
- "zh:2072006c177efc101471f3d5eb8e1d8e6c68778cbfd6db3d3f22f59cfe6ce6ae",
- "zh:3ac4cc0efe11ee054300769cfcc37491433937a8824621d1f8f7a18e7401da87",
- "zh:63997e5457c9ddf9cfff17bd7bf9f083cbeff3105452045662109dd6be499ef9",
- "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
- "zh:826819bb8ab7d6e3095f597083d5b1ab93d1854312b9e1b6c18288fff9664f34",
- "zh:8ad74e7d8ec2e226a73d49c7c317108f61a4cb803972fb3f945d1709d5115fcd",
- "zh:a609ca9e0c91d250ac80295e39d5f524e8c0872d33ba8fde3c3e41893b4b015d",
- "zh:ae07d19babc452f63f6a6511b944990e819dc20687b6c8f01d1676812f5ada53",
- "zh:b7c827dc32a1a5d77185a78cd391b01217894b384f58169f98a96d683730d8ce",
- "zh:d045e3db9f5e39ce78860d3fd94e04604fcbe246f6fe346ee50a971f936e9ccd",
- "zh:ec28f9b52c74edd47eebbb5c254a6df5706360cde5ccd65097976efca23a2977",
- "zh:f24982eaa7d34fd66554c3cf94873713a0dff14da9ea4c4be0cc76f1a6146d59",
- ]
-}
diff --git a/terraform/development/providers.tf b/terraform/development/providers.tf
index 113d838..230cfdd 100644
--- a/terraform/development/providers.tf
+++ b/terraform/development/providers.tf
@@ -1,18 +1,3 @@
-terraform {
- # Must be above 1.9.0 to allow cross-object referencing for input variable validations
- required_version = ">=1.9.0, <=2.0.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = "~>5.69.0"
- }
- # http = {
- # source = "hashicorp/http"
- # version = "~>3.4.5"
- # }
- }
-}
-
locals {
valid_account_no = {
development = "713881824542"
diff --git a/terraform/development/versions.tf b/terraform/development/versions.tf
index f759628..7d3ed22 100644
--- a/terraform/development/versions.tf
+++ b/terraform/development/versions.tf
@@ -1,6 +1,16 @@
terraform {
- required_version = ">= 1.8.2"
-
+ # Must be above 1.9.0 to allow cross-object referencing for input variable validations
+ required_version = ">=1.9.0, < 2.0.0"
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~>5.69.0"
+ }
+ # http = {
+ # source = "hashicorp/http"
+ # version = "~>3.4.5"
+ # }
+ }
cloud {
organization = "3ware"
hostname = "app.terraform.io"
From 2f41485c3de57bdf73e4f1f35e064b5f238e382f Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 10:57:01 +0000
Subject: [PATCH 03/15] refactor(terraform): Replace aws account id
postcondition
Use allowed_account_ids in the provider instead.
---
.gitignore | 5 +-
.../.sops-files/sensitive.enc.yaml | 21 ++++++++
terraform/development/.terraform.lock.hcl | 15 ++++++
terraform/development/outputs.tf | 11 -----
terraform/development/providers.tf | 48 ++-----------------
terraform/development/terraform.tfvars | 1 +
terraform/development/variables.tf | 20 ++++++++
terraform/development/versions.tf | 4 ++
8 files changed, 70 insertions(+), 55 deletions(-)
create mode 100644 terraform/development/.sops-files/sensitive.enc.yaml
diff --git a/.gitignore b/.gitignore
index c1e28d3..7682486 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,7 @@ override.tf.json
# Ignore CLI configuration files
.terraformrc
-terraform.rc
\ No newline at end of file
+terraform.rc
+
+# Ignore direnv files
+.envrc
diff --git a/terraform/development/.sops-files/sensitive.enc.yaml b/terraform/development/.sops-files/sensitive.enc.yaml
new file mode 100644
index 0000000..c673d03
--- /dev/null
+++ b/terraform/development/.sops-files/sensitive.enc.yaml
@@ -0,0 +1,21 @@
+dev_aws_account_id: ENC[AES256_GCM,data:Gt68lai3WHmLHp8v,iv:i2iKvHiJhykRqDQLZgHdH5pJpYzCtiK2Tx8QPeLSYNE=,tag:bckp7FyvFvm9jr5meSSXAA==,type:int]
+sops:
+ kms: []
+ gcp_kms: []
+ azure_kv: []
+ hc_vault: []
+ age:
+ - recipient: age1wpy4kcrhan5ffwwv9dke50v9e302lhravg2njkze9qu33xgnr42q9p2d22
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOVXQwQ1pPMURkUTlWc2xW
+ UGJvb0F3ZEpwQTI4eGYreWxJWTNTck80YkVrCi84UURkQ1dxYm5qeXBZVFRzY1pp
+ bnRmN3hRWmxEcGFyd0NIU295bzdaNFUKLS0tIGRxOGQvbW00bDhaeU1EK1dFUDVy
+ emdPRmxhWjJuWEY0VW9hV2NCd0FWaUEKcqVm/pyha5BNPxDNQCaR5I706PNXIUC7
+ quSJRPZb1RdN8QlTNhlJBBZzOR6YcbxAqbHFDFEoBYc1LulyCznAAg==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2024-11-01T10:46:39Z"
+ mac: ENC[AES256_GCM,data:hcKcV58p0zy3WlwCu7H+h9/Vpq4HIralQwVvxuWI6jOO/pFM8oWJ03oCyXen4axNw+5wTj86s+t9YngLXdD05z2V61ESfmGbi71ce/FsZfS2rOxlfojmTJBxMbh2xYYCsXUnpGDrjzpTkqTn8FdIzHgDx8AJ9sZVn1rMZceAzko=,iv:bdUc/wKv/b97NyAI/X52Sr06zgt45FWigAQi/RjV9oY=,tag:CiggdrIun7yxnI+tEcnElA==,type:str]
+ pgp: []
+ unencrypted_suffix: _unencrypted
+ version: 3.9.1
diff --git a/terraform/development/.terraform.lock.hcl b/terraform/development/.terraform.lock.hcl
index 3a9e66b..336a268 100644
--- a/terraform/development/.terraform.lock.hcl
+++ b/terraform/development/.terraform.lock.hcl
@@ -1,6 +1,21 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
+provider "registry.terraform.io/carlpett/sops" {
+ version = "1.1.1"
+ constraints = "~> 1.1.1"
+ hashes = [
+ "h1:0lv+4VCaIRTkBAMXmCxSJC1dsYPuoyBAvnrLsofcseA=",
+ "zh:175ec198e1b4d1cad1ae559ebe8cdf574617805010c22dfb8af93a2057ba8332",
+ "zh:2b550b2372f71408e7b47b099f314d981bbb82b263cb55248a36a9af8afd44a1",
+ "zh:684544ed3460c34585b090b5de1d4e0caf8eba8e6ba50ad0734cda818a6c86f0",
+ "zh:6ab656d3f3645b8158769f34c16820523a621b9e735c1b3233cecf010ac61dda",
+ "zh:6b1f0007569ea36903c9b2b1b114c3cec7c163d9b83946362c3e165e255f64e7",
+ "zh:7d562f2fc76c954f974f2745557059a4d33dacb8d46e9f1cf09323348dcf5ddc",
+ "zh:cc7e97d8b55ebd90a4c1424cf9cb930af76e98a11c6eeb07e51d648369859fa1",
+ ]
+}
+
provider "registry.terraform.io/hashicorp/aws" {
version = "5.69.0"
constraints = "~> 5.69.0"
diff --git a/terraform/development/outputs.tf b/terraform/development/outputs.tf
index ad6a9fd..ffa5917 100644
--- a/terraform/development/outputs.tf
+++ b/terraform/development/outputs.tf
@@ -1,14 +1,3 @@
-output "aws_account_id" {
- description = "AWS account number resources are deployed into"
- value = data.aws_caller_identity.current.account_id
- sensitive = true
-}
-
-output "default_tags" {
- description = "A map of default tags applied to resources."
- value = data.aws_default_tags.this.tags
-}
-
output "grafana_ip" {
description = "The connection details of the grafana server."
value = "http://${aws_instance.grafana_server.public_ip}:3000"
diff --git a/terraform/development/providers.tf b/terraform/development/providers.tf
index 230cfdd..b24a839 100644
--- a/terraform/development/providers.tf
+++ b/terraform/development/providers.tf
@@ -1,52 +1,14 @@
-locals {
- valid_account_no = {
- development = "713881824542"
- production = "535002868697"
- }
-}
-
-data "aws_caller_identity" "current" {
- lifecycle {
- postcondition {
- condition = contains(values(local.valid_account_no), self.id)
- error_message = format(
- "Invalid AWS account ID specified. Received: '%s', Require: '%s'.\n%s",
- self.id,
- join(", ", values(local.valid_account_no)),
- "Configure AWS credentials to assume the correct role."
- )
- }
- }
-}
-
-locals {
- # Defines a list of permitted environment tag values. Used by the postcondition in the aws_default_tags data source
- # to validate the environment tag extrapolated from the workspace name in data.tf
- valid_environment = ["development", "production"]
-}
-
-data "aws_default_tags" "this" {
- lifecycle {
- postcondition {
- condition = anytrue([
- for tag in values(self.tags) : contains(local.valid_environment, tag)
- ])
- error_message = format(
- "Invalid environment tag specified. Received: '%s', Require: '%s'.\n%s",
- self.tags["3ware:environment"],
- join(", ", local.valid_environment),
- "Rename workspace with a valid environment suffix."
- )
- }
- }
+data "sops_file" "aws_account_id" {
+ source_file = "${path.module}/.sops-files/sensitive.enc.yaml"
}
provider "aws" {
- region = var.region
+ region = var.region
+ allowed_account_ids = [data.sops_file.aws_account_id.data["dev_aws_account_id"]]
default_tags {
tags = {
"3ware:project-id" = var.project_id
- "3ware:environment" = local.environment
+ "3ware:environment" = var.environment
"3ware:managed-by-terraform" = true
"3ware:workspace" = terraform.workspace
}
diff --git a/terraform/development/terraform.tfvars b/terraform/development/terraform.tfvars
index 450e657..25200d6 100644
--- a/terraform/development/terraform.tfvars
+++ b/terraform/development/terraform.tfvars
@@ -1,3 +1,4 @@
+environment = "development"
instance_type = "t2.micro"
project_id = "gitops-2024"
region = "us-east-1"
diff --git a/terraform/development/variables.tf b/terraform/development/variables.tf
index d711087..0c5e66d 100644
--- a/terraform/development/variables.tf
+++ b/terraform/development/variables.tf
@@ -1,3 +1,23 @@
+locals {
+ valid_environment = ["development"]
+}
+
+variable "environment" {
+ description = "(Required) Terraform deployment environment"
+ type = string
+
+ validation {
+ condition = contains(local.valid_environment, var.environment)
+ error_message = format(
+ "Invalid environment provided. Received: '%s', Require: '%v'.\n%s",
+ var.environment,
+ join(", ", local.valid_environment),
+ "Change the environment variable value to one that is permitted."
+ )
+ }
+}
+
+
locals {
valid_instance_types = ["t2.micro"]
}
diff --git a/terraform/development/versions.tf b/terraform/development/versions.tf
index 7d3ed22..9c00970 100644
--- a/terraform/development/versions.tf
+++ b/terraform/development/versions.tf
@@ -10,6 +10,10 @@ terraform {
# source = "hashicorp/http"
# version = "~>3.4.5"
# }
+ sops = {
+ source = "carlpett/sops"
+ version = "~> 1.1.1"
+ }
}
cloud {
organization = "3ware"
From 0699ec253148bacd18225a22ecf557db562e7a97 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 11:25:16 +0000
Subject: [PATCH 04/15] feat(terraform): Add production environment
---
.../production/.sops-files/sensitive.yaml | 1 +
terraform/production/.terraform.lock.hcl | 40 ++++++
terraform/production/README.md | 57 ++++++++
terraform/production/data.tf | 5 +
terraform/production/main.tf | 131 ++++++++++++++++++
terraform/production/outputs.tf | 4 +
terraform/production/providers.tf | 16 +++
terraform/production/terraform.tfvars | 6 +
terraform/production/userdata.tftpl | 9 ++
terraform/production/variables.tf | 90 ++++++++++++
terraform/production/versions.tf | 30 ++++
11 files changed, 389 insertions(+)
create mode 100644 terraform/production/.sops-files/sensitive.yaml
create mode 100644 terraform/production/.terraform.lock.hcl
create mode 100644 terraform/production/README.md
create mode 100644 terraform/production/data.tf
create mode 100644 terraform/production/main.tf
create mode 100644 terraform/production/outputs.tf
create mode 100644 terraform/production/providers.tf
create mode 100644 terraform/production/terraform.tfvars
create mode 100644 terraform/production/userdata.tftpl
create mode 100644 terraform/production/variables.tf
create mode 100644 terraform/production/versions.tf
diff --git a/terraform/production/.sops-files/sensitive.yaml b/terraform/production/.sops-files/sensitive.yaml
new file mode 100644
index 0000000..2e289f6
--- /dev/null
+++ b/terraform/production/.sops-files/sensitive.yaml
@@ -0,0 +1 @@
+production_aws_account_id: 535002868697
diff --git a/terraform/production/.terraform.lock.hcl b/terraform/production/.terraform.lock.hcl
new file mode 100644
index 0000000..336a268
--- /dev/null
+++ b/terraform/production/.terraform.lock.hcl
@@ -0,0 +1,40 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/carlpett/sops" {
+ version = "1.1.1"
+ constraints = "~> 1.1.1"
+ hashes = [
+ "h1:0lv+4VCaIRTkBAMXmCxSJC1dsYPuoyBAvnrLsofcseA=",
+ "zh:175ec198e1b4d1cad1ae559ebe8cdf574617805010c22dfb8af93a2057ba8332",
+ "zh:2b550b2372f71408e7b47b099f314d981bbb82b263cb55248a36a9af8afd44a1",
+ "zh:684544ed3460c34585b090b5de1d4e0caf8eba8e6ba50ad0734cda818a6c86f0",
+ "zh:6ab656d3f3645b8158769f34c16820523a621b9e735c1b3233cecf010ac61dda",
+ "zh:6b1f0007569ea36903c9b2b1b114c3cec7c163d9b83946362c3e165e255f64e7",
+ "zh:7d562f2fc76c954f974f2745557059a4d33dacb8d46e9f1cf09323348dcf5ddc",
+ "zh:cc7e97d8b55ebd90a4c1424cf9cb930af76e98a11c6eeb07e51d648369859fa1",
+ ]
+}
+
+provider "registry.terraform.io/hashicorp/aws" {
+ version = "5.69.0"
+ constraints = "~> 5.69.0"
+ hashes = [
+ "h1:unGIj/eLOrl42LQm7u0fjtjQHp+FHKinpSxR1ZuWsfI=",
+ "zh:123af8815a80abfd62eab5f9fc3d9226735cfea3627e834a1b48321cd8d391a6",
+ "zh:1298f312e239768c1846541e89b4fbec7eb21769c4a488c87181909049219fbe",
+ "zh:4edc950b39f3653beb8cd3e0b86a7dc9b6a77e90e543ed7be72639107bbc48a9",
+ "zh:5f24c916d6d2ce51e18210628b3b1aca8b85b383982a920b2a6adc259bdbd4e9",
+ "zh:66f0b2f5869a4dfed7154444c272022c6d9350dc4dfa0fc6d87ccbfc983ec560",
+ "zh:67e3be60863cf1c51c5be866d8646d433cc31e07514b9121611f812e73f2400d",
+ "zh:884672345a1d0362644a4d1588085fd4c4f56d3ca61b10c0d25cd1940d828fec",
+ "zh:8ab0f92da124171c80a2361beb79822fb0f074ffab74e506f58e953a69b283ce",
+ "zh:908d879139f2246024b5510a38f00f61489eeee6f3f72be10acc5b424c8fc723",
+ "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+ "zh:9db6331398d648d9f2f4aa4db1eb9081e9bff584dcfe8f5350e04e6c5d339899",
+ "zh:a809bbd43bc392e91485b72bd9693874972bc5697b4f24fbcd61b461618ebb6d",
+ "zh:b9e9464458e7beb9fbf59f8db02f56138f398aaa6173b58a8bfa76aca82106d9",
+ "zh:cd7f041edaeeb1c4b06152ac8f3ce7b31c39a80a949083255f8fc81bbb11aeac",
+ "zh:eb71c9b2071ab2caa7aba577902df41c25ded1251c28560f0ac45f5e0f47360e",
+ ]
+}
diff --git a/terraform/production/README.md b/terraform/production/README.md
new file mode 100644
index 0000000..c4965fa
--- /dev/null
+++ b/terraform/production/README.md
@@ -0,0 +1,57 @@
+# More Than Certified GitOps 2024 Minicamp Terraform documentation
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >=1.9.0, <=2.0.0 |
+| [terraform](#requirement\_terraform) | >= 1.8.2 |
+| [aws](#requirement\_aws) | ~>5.69.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 5.69.0 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_default_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group) | resource |
+| [aws_instance.grafana_server](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource |
+| [aws_internet_gateway.gitops_igw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource |
+| [aws_route_table.gitops_rt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource |
+| [aws_route_table_association.gitops_rta](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource |
+| [aws_security_group.grafana_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_subnet.gitops_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource |
+| [aws_vpc.gitops_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource |
+| [aws_vpc_security_group_egress_rule.grafana_egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
+| [aws_vpc_security_group_ingress_rule.grafana_ingress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
+| [aws_ami.ubuntu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_default_tags.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/default_tags) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [instance\_type](#input\_instance\_type) | (Required) Instance type to use. Should be within the free tier | `string` | n/a | yes |
+| [project\_id](#input\_project\_id) | (Required) Name of the project | `string` | n/a | yes |
+| [region](#input\_region) | (Required) Name of the AWS region resources will be deployed into. | `string` | n/a | yes |
+| [subnet\_cidr\_block](#input\_subnet\_cidr\_block) | (Required) A valid CIDR block to assign to the Grafana Server subnet | `string` | n/a | yes |
+| [vpc\_cidr\_block](#input\_vpc\_cidr\_block) | (Required) A valid CIDR block to assign to the VPC | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [aws\_account\_id](#output\_aws\_account\_id) | AWS account number resources are deployed into |
+| [default\_tags](#output\_default\_tags) | A map of default tags applied to resources. |
+| [grafana\_ip](#output\_grafana\_ip) | The connection details of the grafana server. |
+
diff --git a/terraform/production/data.tf b/terraform/production/data.tf
new file mode 100644
index 0000000..c7de0ff
--- /dev/null
+++ b/terraform/production/data.tf
@@ -0,0 +1,5 @@
+# The locals in this file are used across multiple .tf files
+locals {
+ workspace_split = split("-", terraform.workspace)
+ environment = element(local.workspace_split, length(local.workspace_split) - 1)
+}
diff --git a/terraform/production/main.tf b/terraform/production/main.tf
new file mode 100644
index 0000000..9a7c87e
--- /dev/null
+++ b/terraform/production/main.tf
@@ -0,0 +1,131 @@
+resource "aws_vpc" "gitops_vpc" {
+ cidr_block = var.vpc_cidr_block
+ enable_dns_support = true
+ enable_dns_hostnames = true
+
+ tags = {
+ Name = "gitops-vpc-${local.environment}"
+ }
+}
+
+resource "aws_default_security_group" "default" {
+ vpc_id = aws_vpc.gitops_vpc.id
+}
+
+resource "aws_internet_gateway" "gitops_igw" {
+ vpc_id = aws_vpc.gitops_vpc.id
+
+ tags = {
+ Name = "gitops-igw-${local.environment}"
+ }
+}
+
+resource "aws_route_table" "gitops_rt" {
+ vpc_id = aws_vpc.gitops_vpc.id
+
+ route {
+ cidr_block = "0.0.0.0/0"
+ gateway_id = aws_internet_gateway.gitops_igw.id
+ }
+
+ tags = {
+ Name = "gitops-rt-${local.environment}"
+ }
+}
+
+resource "aws_subnet" "gitops_subnet" {
+ vpc_id = aws_vpc.gitops_vpc.id
+ cidr_block = var.subnet_cidr_block
+ map_public_ip_on_launch = true
+
+ tags = {
+ Name = "gitops-subnet-${local.environment}"
+ }
+}
+
+resource "aws_route_table_association" "gitops_rta" {
+ subnet_id = aws_subnet.gitops_subnet.id
+ route_table_id = aws_route_table.gitops_rt.id
+}
+
+resource "aws_security_group" "grafana_sg" {
+ name = "grafana_sg"
+ description = "Grafana Server security group"
+ vpc_id = aws_vpc.gitops_vpc.id
+}
+
+resource "aws_vpc_security_group_ingress_rule" "grafana_ingress" {
+ description = "Inbound traffic to grafana web interface"
+ security_group_id = aws_security_group.grafana_sg.id
+ cidr_ipv4 = "0.0.0.0/0"
+ from_port = 3000
+ ip_protocol = "tcp"
+ to_port = 3000
+
+ tags = {
+ Name = "grafana-ingress-sg-rule-${local.environment}"
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_vpc_security_group_egress_rule" "grafana_egress" {
+ description = "Outbound traffic from grafana server"
+ security_group_id = aws_security_group.grafana_sg.id
+ cidr_ipv4 = "0.0.0.0/0"
+ ip_protocol = "-1"
+
+ tags = {
+ Name = "grafana-egress-sg-rule-${local.environment}"
+ }
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+data "aws_ami" "ubuntu" {
+ most_recent = true
+
+ filter {
+ name = "name"
+ values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
+ }
+
+ filter {
+ name = "virtualization-type"
+ values = ["hvm"]
+ }
+
+ owners = ["099720109477"] # Canonical
+}
+
+resource "aws_instance" "grafana_server" {
+ ami = data.aws_ami.ubuntu.id
+ instance_type = var.instance_type
+ subnet_id = aws_subnet.gitops_subnet.id
+ vpc_security_group_ids = [aws_security_group.grafana_sg.id]
+ user_data = file("userdata.tftpl")
+
+ tags = {
+ Name = "grafana-server-${local.environment}"
+ }
+}
+
+#! Disabling for now because it break the workflow - see PR33
+# check "grafana_check" {
+# data "http" "grafana" {
+# url = "http://${aws_instance.grafana_server.public_ip}:3000"
+# method = "HEAD"
+# retry {
+# attempts = 5
+# }
+# }
+
+# assert {
+# condition = data.http.grafana.status_code == 200
+# error_message = "${data.http.grafana.url} returned an unhealthy status code"
+# }
+# }
diff --git a/terraform/production/outputs.tf b/terraform/production/outputs.tf
new file mode 100644
index 0000000..ffa5917
--- /dev/null
+++ b/terraform/production/outputs.tf
@@ -0,0 +1,4 @@
+output "grafana_ip" {
+ description = "The connection details of the grafana server."
+ value = "http://${aws_instance.grafana_server.public_ip}:3000"
+}
diff --git a/terraform/production/providers.tf b/terraform/production/providers.tf
new file mode 100644
index 0000000..9c473e8
--- /dev/null
+++ b/terraform/production/providers.tf
@@ -0,0 +1,16 @@
+data "sops_file" "aws_account_id" {
+ source_file = "${path.module}/.sops-files/sensitive.enc.yaml"
+}
+
+provider "aws" {
+ region = var.region
+ allowed_account_ids = [data.sops_file.aws_account_id.data["${var.environment}_aws_account_id"]]
+ default_tags {
+ tags = {
+ "3ware:project-id" = var.project_id
+ "3ware:environment" = var.environment
+ "3ware:managed-by-terraform" = true
+ "3ware:workspace" = terraform.workspace
+ }
+ }
+}
diff --git a/terraform/production/terraform.tfvars b/terraform/production/terraform.tfvars
new file mode 100644
index 0000000..4ca3fe6
--- /dev/null
+++ b/terraform/production/terraform.tfvars
@@ -0,0 +1,6 @@
+environment = "production"
+instance_type = "t2.micro"
+project_id = "gitops-2024"
+region = "us-east-1"
+subnet_cidr_block = "10.0.2.0/24"
+vpc_cidr_block = "10.0.0.0/16"
diff --git a/terraform/production/userdata.tftpl b/terraform/production/userdata.tftpl
new file mode 100644
index 0000000..6bb94de
--- /dev/null
+++ b/terraform/production/userdata.tftpl
@@ -0,0 +1,9 @@
+#!/bin/bash
+sudo apt-get install -y apt-transport-https software-properties-common wget &&
+sudo mkdir -p /etc/apt/keyrings/ &&
+wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null &&
+echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list &&
+sudo apt-get update &&
+sudo apt-get install -y grafana &&
+sudo systemctl start grafana-server &&
+sudo systemctl enable grafana-server
\ No newline at end of file
diff --git a/terraform/production/variables.tf b/terraform/production/variables.tf
new file mode 100644
index 0000000..88db2ae
--- /dev/null
+++ b/terraform/production/variables.tf
@@ -0,0 +1,90 @@
+locals {
+ valid_environment = ["production"]
+}
+
+variable "environment" {
+ description = "(Required) Terraform deployment environment"
+ type = string
+
+ validation {
+ condition = contains(local.valid_environment, var.environment)
+ error_message = format(
+ "Invalid environment provided. Received: '%s', Require: '%v'.\n%s",
+ var.environment,
+ join(", ", local.valid_environment),
+ "Change the environment variable value to one that is permitted."
+ )
+ }
+}
+
+
+locals {
+ valid_instance_types = ["t2.micro"]
+}
+
+variable "instance_type" {
+ description = "(Required) Instance type to use. Should be within the free tier"
+ type = string
+
+ validation {
+ condition = contains(local.valid_instance_types, var.instance_type)
+ error_message = format(
+ "Invalid instance type provided. Received: '%s', Require: '%v'.\n%s",
+ var.instance_type,
+ join(", ", local.valid_instance_types),
+ "Change the instance type variable to one that is permitted."
+ )
+ }
+}
+
+variable "project_id" {
+ description = "(Required) Name of the project"
+ type = string
+}
+
+locals {
+ valid_regions = ["us-east-1"]
+}
+
+variable "region" {
+ description = "(Required) Name of the AWS region resources will be deployed into."
+ type = string
+
+ validation {
+ condition = contains(local.valid_regions, var.region)
+ error_message = format(
+ "Invalid AWS region provided. Received: '%s', Require: '%v'.\n%s",
+ var.region,
+ join(", ", local.valid_regions),
+ "Change the region variable to one that is permitted."
+ )
+ }
+}
+
+variable "subnet_cidr_block" {
+ description = "(Required) A valid CIDR block to assign to the Grafana Server subnet"
+ type = string
+
+ validation {
+ condition = can(cidrhost(var.subnet_cidr_block, 0))
+ error_message = format(
+ "Invalid CIDR block provided. Received: '%s'\n%s",
+ var.vpc_cidr_block,
+ "Check the syntax of the CIDR block is valid."
+ )
+ }
+}
+
+variable "vpc_cidr_block" {
+ description = "(Required) A valid CIDR block to assign to the VPC"
+ type = string
+
+ validation {
+ condition = can(cidrhost(var.vpc_cidr_block, 0))
+ error_message = format(
+ "Invalid CIDR block provided. Received: '%s'\n%s",
+ var.vpc_cidr_block,
+ "Check the syntax of the CIDR block is valid."
+ )
+ }
+}
diff --git a/terraform/production/versions.tf b/terraform/production/versions.tf
new file mode 100644
index 0000000..18271d9
--- /dev/null
+++ b/terraform/production/versions.tf
@@ -0,0 +1,30 @@
+terraform {
+ # Must be above 1.9.0 to allow cross-object referencing for input variable validations
+ required_version = ">=1.9.0, < 2.0.0"
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~>5.69.0"
+ }
+ # http = {
+ # source = "hashicorp/http"
+ # version = "~>3.4.5"
+ # }
+ sops = {
+ source = "carlpett/sops"
+ version = "~> 1.1.1"
+ }
+ }
+ cloud {
+ organization = "3ware"
+ hostname = "app.terraform.io"
+
+ workspaces {
+ # Tags are used to when the workspace exists locally and workspace are used to separate the configuration
+ # Set the TF_WORKSPACE environment variable in CI
+ # tags = ["gitops", "mtc", "aws"]
+ name = "app-us-east-1-production"
+ project = "gitops-2024"
+ }
+ }
+}
From ea387a05ebe13f3b45700da8657d70b7cc90836e Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 11:25:50 +0000
Subject: [PATCH 05/15] refactor(terraform): Add environment interpolation to
account id
---
.../development/.sops-files/sensitive.enc.yaml | 16 ++++++++--------
terraform/development/providers.tf | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/terraform/development/.sops-files/sensitive.enc.yaml b/terraform/development/.sops-files/sensitive.enc.yaml
index c673d03..53c8a24 100644
--- a/terraform/development/.sops-files/sensitive.enc.yaml
+++ b/terraform/development/.sops-files/sensitive.enc.yaml
@@ -1,4 +1,4 @@
-dev_aws_account_id: ENC[AES256_GCM,data:Gt68lai3WHmLHp8v,iv:i2iKvHiJhykRqDQLZgHdH5pJpYzCtiK2Tx8QPeLSYNE=,tag:bckp7FyvFvm9jr5meSSXAA==,type:int]
+development_aws_account_id: ENC[AES256_GCM,data:DJFVyfC1L2sU3Rg3,iv:/tY8GG2lda8IP2ITG72Xh4sMs+Tt4VNAP1Qb1LdTZoM=,tag:GwieQ56POhsSxjexHYx9fg==,type:int]
sops:
kms: []
gcp_kms: []
@@ -8,14 +8,14 @@ sops:
- recipient: age1wpy4kcrhan5ffwwv9dke50v9e302lhravg2njkze9qu33xgnr42q9p2d22
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOVXQwQ1pPMURkUTlWc2xW
- UGJvb0F3ZEpwQTI4eGYreWxJWTNTck80YkVrCi84UURkQ1dxYm5qeXBZVFRzY1pp
- bnRmN3hRWmxEcGFyd0NIU295bzdaNFUKLS0tIGRxOGQvbW00bDhaeU1EK1dFUDVy
- emdPRmxhWjJuWEY0VW9hV2NCd0FWaUEKcqVm/pyha5BNPxDNQCaR5I706PNXIUC7
- quSJRPZb1RdN8QlTNhlJBBZzOR6YcbxAqbHFDFEoBYc1LulyCznAAg==
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoSml5M1p6MHIzaitVaGVW
+ OTBZNVBQRmVXU2FENXFnb2UvdFNFYTJhSG1ZCk5FSEUvZFZiVUJFdzVXdC9hclNj
+ T0NicFFSZ212QkdlRTFuQ3lSZUpVMWsKLS0tIFVta2pYaC9VMXlnbU1KNW1Zcnk4
+ aXRkOUhWakRBUEtxSWdFVkw2R3ZscFEKm9zke6+CQFYyFohhm2XLMqW3ffkPs10d
+ Lk5rBlAmGTsneyVHNdBrF/zjD6nKOqs7MZudWX+rZFgeSBnSjxo8qA==
-----END AGE ENCRYPTED FILE-----
- lastmodified: "2024-11-01T10:46:39Z"
- mac: ENC[AES256_GCM,data:hcKcV58p0zy3WlwCu7H+h9/Vpq4HIralQwVvxuWI6jOO/pFM8oWJ03oCyXen4axNw+5wTj86s+t9YngLXdD05z2V61ESfmGbi71ce/FsZfS2rOxlfojmTJBxMbh2xYYCsXUnpGDrjzpTkqTn8FdIzHgDx8AJ9sZVn1rMZceAzko=,iv:bdUc/wKv/b97NyAI/X52Sr06zgt45FWigAQi/RjV9oY=,tag:CiggdrIun7yxnI+tEcnElA==,type:str]
+ lastmodified: "2024-11-01T11:23:38Z"
+ mac: ENC[AES256_GCM,data:UItGJx1VhU+OtH0B7wcrBdiLjS3sZQwvZ+9pRLA7alkUU56AkdhSGHIIOrstdO8Nnb3Yjt4mPpwyKHUx1r3eIczQUGqUxa3h8kaF7fzyhf3RkJdLsnYRxvjvk7iHRKr/Ey0wtFFa5cGo7bSj2Ar0RTARq1pglLGJ6kRDjMzWaJo=,iv:HHzgmVOWktFiW5YaFEFyMy2wlhPc/v5v1+ccLpRZsB0=,tag:ipbezFjdtACwvEosS+p0Lg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.1
diff --git a/terraform/development/providers.tf b/terraform/development/providers.tf
index b24a839..9c473e8 100644
--- a/terraform/development/providers.tf
+++ b/terraform/development/providers.tf
@@ -4,7 +4,7 @@ data "sops_file" "aws_account_id" {
provider "aws" {
region = var.region
- allowed_account_ids = [data.sops_file.aws_account_id.data["dev_aws_account_id"]]
+ allowed_account_ids = [data.sops_file.aws_account_id.data["${var.environment}_aws_account_id"]]
default_tags {
tags = {
"3ware:project-id" = var.project_id
From fc91de612d63290c1a57152d06cb27d48638deb6 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 11:26:28 +0000
Subject: [PATCH 06/15] ci(trunk): Disable trivy and checkov
trivy checks are difficult to ignore
---
.envrc | 4 ----
.trunk/trunk.yaml | 8 ++++----
2 files changed, 4 insertions(+), 8 deletions(-)
delete mode 100644 .envrc
diff --git a/.envrc b/.envrc
deleted file mode 100644
index 4a099a4..0000000
--- a/.envrc
+++ /dev/null
@@ -1,4 +0,0 @@
-# Read sensitive environment variables from 1password.
-
-# User token for access to remote state on terraform cloud.
-export TF_TOKEN_APP_TERRAFORM_IO="$(op read op://Servers/TF_TOKEN_APP_TERRAFORM_IO/password)"
\ No newline at end of file
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 421b950..8f141b0 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -17,19 +17,19 @@ runtimes:
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
lint:
disabled:
+ - checkov
+ - trivy
- regal
enabled:
- - renovate@38.135.2
- - trivy@0.56.2
+ - renovate@38.142.0
- tflint@0.53.0
- terraform@1.9.7:
commands: [fmt, validate]
- actionlint@1.7.3
- - checkov@3.2.276
- git-diff-check
- markdownlint@0.42.0
- prettier@3.3.3
- - trufflehog@3.83.1
+ - trufflehog@3.83.2
- yamllint@1.35.1
definitions:
- name: tflint
From 9c91c508a8c0e520a26d2e70c7667f511375b688 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 14:26:38 +0000
Subject: [PATCH 07/15] ci(terraform-docs): Update trigger to run when
Terraform CI succeeds
---
.github/workflows/terraform-docs.yaml | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/terraform-docs.yaml b/.github/workflows/terraform-docs.yaml
index f496c70..4968b2b 100644
--- a/.github/workflows/terraform-docs.yaml
+++ b/.github/workflows/terraform-docs.yaml
@@ -1,22 +1,19 @@
name: Terraform Docs
+run-name: ${{ github.event.workflow_run.display_title }}
on:
- pull_request:
- types: [closed]
- branches: [main]
- paths:
- - "**/*.tf"
- - "**/*.tfvars"
- - "**/*.tftpl"
+ workflow_run:
+ workflows: [Terraform CI]
+ types: [completed]
# Disable permissions for all available scopes
permissions: {}
jobs:
terraform-docs:
- if: ${{ github.event.pull_request.merged == true }}
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
name: Terraform Docs
uses: 3ware/workflows/.github/workflows/terraform-docs.yaml@7880d6b986d1d689f5d219e901b863f1378fea9c # v4.4.0
secrets: inherit
with:
- tf-directory: terraform/development
+ tf-directory: terraform
From 9cd450e8e1d348f33c0b682b115a817183aa17f2 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 14:29:23 +0000
Subject: [PATCH 08/15] ci(terraform): Refactor to use matrix for multi
environment
---
.github/workflows/terraform-ci.yaml | 299 +++++++++-------------------
1 file changed, 92 insertions(+), 207 deletions(-)
diff --git a/.github/workflows/terraform-ci.yaml b/.github/workflows/terraform-ci.yaml
index 6de2d82..186b0a7 100644
--- a/.github/workflows/terraform-ci.yaml
+++ b/.github/workflows/terraform-ci.yaml
@@ -1,15 +1,13 @@
name: Terraform CI
+run-name: ${{ github.event_name == 'merge_group' && github.event.merge_group.head_commit.message || ''}}
on:
pull_request:
+ types: [opened, synchronize]
branches: [main]
- types: [opened, labeled, synchronize]
- paths:
- - "**/*.tf"
- - "**/*.tfvars"
- - "**/*.tftpl"
+ merge_group:
+ types: [checks_requested]
-# Disable permissions for all available scopes.
permissions: {}
concurrency:
@@ -19,26 +17,63 @@ concurrency:
defaults:
run:
shell: bash
- #TODO: Move this to job level if using matrix
- working-directory: ./terraform/development
jobs:
- test-terraform:
- # Do not run the test workflow if the 'approved' label is applied
- if: ${{ !(contains(github.event.pull_request.labels.*.name , 'approved')) }}
- name: Test Terraform
+ targets:
+ name: Terraform Targets
runs-on: ubuntu-latest
permissions:
- checks: write
contents: read
- id-token: write
- pull-requests: write
- #TODO: Use matrix for different environments
- environment: development
+ outputs:
+ targets: ${{ steps.directories.outputs.all_changed_files }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - name: Check changed directories
+ id: directories
+ uses: tj-actions/changed-files@c3a1bb2c992d77180ae65be6ae6c166cf40f857c # v45.0.3
+ with:
+ dir_names: true
+ dir_names_max_depth: 3
+ files: terraform/**
+ matrix: true
+
+ terraform-deploy:
+ needs: [targets]
+ if: ${{ needs.targets.outputs.targets != '[]' }}
+ name: Terraform Deploy
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read # Required to identify workflow run.
+ checks: write # Required to add status summary.
+ contents: read # Required to checkout repository.
+ id-token: write # Require for OIDC.
+ pull-requests: write # Required to add comment and label.
+ strategy:
+ fail-fast: true
+ max-parallel: 1
+ matrix:
+ targets: ${{ fromJson(needs.targets.outputs.targets) }}
+ environment: ${{ contains(matrix.targets, 'production') && 'production' || 'development' }}
+ env:
+ TF_TOKEN_APP_TERRAFORM_IO: ${{ secrets.TF_TOKEN_APP_TERRAFORM_IO }}
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
steps:
- name: Checkout repository
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ # This is required because the 'environment' context does not exist, do cannot be referenced
+ # my the steps that need it
+ - name: Set deployment environment as an environment variable
+ run: |
+ echo "ENVIRONMENT=${{ contains(matrix.targets, 'production') && 'production' || 'development' }}" >> $GITHUB_ENV
# AWS Credentials required for tflint deep check
- name: Configure AWS credentials via OIDC
@@ -46,11 +81,11 @@ jobs:
with:
aws-region: us-east-1
mask-aws-account-id: true
- #TODO: Secrets will need renaming if using matrix for environments. eg AWS_${{ matrix.environment }}_OIDC_ROLE_ARN
- role-to-assume: ${{ secrets.AWS_DEV_OIDC_ROLE_ARN }}
- role-session-name: tflint-deep-check
+ role-to-assume: ${{ secrets[format('GHA_3WARE_OIDC_{0}', env.ENVIRONMENT)] }}
+ role-session-name: ${{ github.event_name == 'merge_group' && 'aws-net-sec-terraform-apply' || 'aws-net-sec-terraform-plan' }}
- name: Cache TFLint plugin directory
+ if: ${{ github.event_name == 'pull_request' }}
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: .trunk/plugins/
@@ -58,20 +93,23 @@ jobs:
# Run terraform format
- name: Terraform fmt
+ if: ${{ github.event_name == 'pull_request' }}
id: tf-fmt
- run: terraform fmt -check
+ run: terraform -chdir=${{ matrix.targets }} fmt -check
continue-on-error: true
# Initialise terraform in the directory where terraform file have changed.
- name: Initialise terraform
+ if: ${{ github.event_name == 'pull_request' }}
id: tf-init
run: |
- terraform init -backend=false
+ terraform -chdir=${{ matrix.targets }} init -backend=false
# Run terraform validate
- name: Terraform validate
+ if: ${{ github.event_name == 'pull_request' }}
id: tf-validate
- run: terraform validate -no-color
+ run: terraform -chdir=${{ matrix.targets }} validate -no-color
continue-on-error: true
# Add PR comment with formatting and validation errors
@@ -79,15 +117,16 @@ jobs:
if: ${{ steps.tf-fmt.outputs.exitcode != 0 || steps.tf-validate.outputs.exitcode != 0 }}
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
with:
- header: Terraform validation error
- hide_and_recreate: true
- hide_classify: OUTDATED
+ header: test-${{ matrix.targets }}
+ recreate: true
message: |
- #### Terraform Format and Style ${{ steps.tf-fmt.outcome }} :x:
+ #### ${{ matrix.targets }}
+
+ #### :x: Terraform Format and Style ${{ steps.tf-fmt.outcome }}
${{ steps.tf-fmt.outputs.stdout }}
- #### Terraform Validation ${{ steps.tf-validate.outcome }} :x:
+ #### :x: Terraform Validation ${{ steps.tf-validate.outcome }}
```
${{ steps.tf-validate.outputs.stderr }}
@@ -103,21 +142,24 @@ jobs:
# Install TFLint; required to download plugins
- name: Install TFLint
+ if: ${{ github.event_name == 'pull_request' }}
uses: terraform-linters/setup-tflint@15ef44638cb215296e7ba9e7a41f20b5b06f4784 # v4.0.0
with:
tflint_version: v0.53.0
tflint_wrapper: true
- # Initialise TFLint using the configuration file for CI in the trunk directory
- # The CI configuration enables deep check
+ # Initialise TFLint using the configuration file in the trunk directory
- name: Initialise TFLint
+ if: ${{ github.event_name == 'pull_request' }}
+ shell: bash
run: |
- tflint --init --config=$GITHUB_WORKSPACE/.trunk/configs/.tflint_ci.hcl
+ tflint -chdir=${{ matrix.targets }} --init --config=$GITHUB_WORKSPACE/.trunk/configs/.tflint_ci.hcl
- name: Run TFLint
+ if: ${{ github.event_name == 'pull_request' }}
id: tflint
run: |
- tflint --config=$GITHUB_WORKSPACE/.trunk/configs/.tflint_ci.hcl --format compact
+ tflint -chdir=${{ matrix.targets }} --config=$GITHUB_WORKSPACE/.trunk/configs/.tflint_ci.hcl --format compact
continue-on-error: true
# Add PR comment when TFLint detects a violation
@@ -125,11 +167,12 @@ jobs:
if: ${{ steps.tflint.outputs.exitcode != 0 }}
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
with:
- header: TLint error
- hide_and_recreate: true
- hide_classify: RESOLVED
+ header: test-${{ matrix.targets }}
+ recreate: true
message: |
- #### TFLint failure :x:
+ #### ${{ matrix.targets }}
+
+ #### :x: TFLint failure
```
${{ steps.tflint.outputs.stdout }}
@@ -143,182 +186,24 @@ jobs:
run: |
exit 1
- # Run Trunk Check without terraform and tflint due to deep check issue
- - name: Trunk Check
- uses: trunk-io/trunk-action@2eaee169140ec559bd556208f9f99cdfdf468da8 # v1.1.17
- with:
- arguments: --filter checkov,trivy
-
- - name: Add PR comment for Test success
+ - name: Update PR comment for Test success
+ if: ${{ github.event_name == 'pull_request' }}
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
with:
- header: TF Success
- message: |
- #### Terraform Format and Style ${{ steps.tf-fmt.outcome }} :white_check_mark:
- #### Terraform Validation ${{ steps.tf-validate.outcome }} :white_check_mark:
- #### TFLint ${{ steps.tf-validate.outcome }} :white_check_mark:
-
- plan-and-apply:
- name: Terraform Deploy
- runs-on: ubuntu-latest
- needs: [test-terraform]
- # This job should run `terraform plan` following the successful completion of test-terraform
- # `terraform apply` is triggered when the 'approved' label is added to the pull request. An 'approved' label does not trigger
- # the test workflow, so `terraform apply` should run when test-terraform is skipped
- if: |
- always() &&
- github.event_name == 'pull_request' && needs.test-terraform.result == 'success' ||
- github.event.label.name =='approved' && needs.test-terraform.result == 'skipped'
- permissions:
- actions: read # Required to download repository artifact.
- checks: write # Required to add status summary.
- contents: read # Required to checkout repository.
- id-token: write # Required to authenticate via OIDC.
- pull-requests: write # Required to add PR comment and label.
- environment: development
- env:
- TF_TOKEN_APP_TERRAFORM_IO: ${{ secrets.TF_TOKEN_APP_TERRAFORM_IO }}
-
- steps:
- # Assign the label and comment early in the workflow when approved to inform the user in the PR that something is happening
- # Because the workflow is running in the issue_comment context the PR checks are not updated
- - name: Update status comment
- if: ${{ contains(github.event.pull_request.labels.*.name , 'approved') }}
- uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
- with:
- header: status
+ header: test-${{ matrix.targets }}
recreate: true
message: |
- #### Terraform Plan Approved.
+ #### ${{ matrix.targets }}
- Apply workflow is running. [View output](https://github.com/3ware/gitops-2024/actions/runs/${{ github.run_id }})
+ #### :white_check_mark: Terraform Format and Style ${{ steps.tf-fmt.outcome }}
+ #### :white_check_mark: Terraform Validation ${{ steps.tf-validate.outcome }}
+ #### :white_check_mark: TFLint ${{ steps.tf-validate.outcome }}
- > [!CAUTION]
- > Do not merge the pull request until the apply workflow has completed.
-
- # - name: Set environment variables
- # # Replace '/' with '-' Most things like dashes and do not like forward slashes
- # run: |
- # echo "REPOSITORY=$(echo ${{ github.repository }} | tr / -)" >> "$GITHUB_ENV"
-
- - name: Checkout repository
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
-
- - name: Remove approval label
- if: ${{ contains(github.event.pull_request.labels.*.name , 'approved') }}
- run: |
- gh pr edit ${{ github.event.pull_request.number }} --remove-label "approval required"
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Configure AWS credentials via OIDC
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
- with:
- aws-region: us-east-1
- role-to-assume: ${{ secrets.AWS_DEV_OIDC_ROLE_ARN }}
- # $REPOSITORY, as described in the github docs, does not work
- role-session-name: gitops2024-development-terraform-deploy
-
- - name: Terraform
+ - name: Provision TF
uses: devsectop/tf-via-pr@f1acaae1d94826457fa57bc65f1df318fd81b3bc # v12.0.0
- id: terraform
with:
- working-directory: terraform/development
- # If the 'approved' label exits, run 'apply'; if it doesn't run 'plan'
- command: ${{ contains(github.event.pull_request.labels.*.name , 'approved') && 'apply' || 'plan' }}
- arg-lock: ${{ contains(github.event.pull_request.labels.*.name , 'approved') && 'true' || 'false' }}
- # plan-parity: ${{ contains(github.event.pull_request.labels.*.name , 'approved') && 'true' || 'false' }}
- hide-args: true
+ command: ${{ github.event_name == 'merge_group' && 'apply' || 'plan' }}
+ arg-lock: ${{ github.event_name == 'merge_group' }}
+ working-directory: ${{ matrix.targets }}
plan-encrypt: ${{ secrets.PGP_SECRET_SIGNING_PASSPHRASE }}
-
- - name: Assign approval label
- if: ${{ !(contains(github.event.pull_request.labels.*.name , 'approved')) }}
- run: |
- gh pr edit ${{ github.event.pull_request.number }} --add-label "approval required"
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Add status comment
- if: ${{ !(contains(github.event.pull_request.labels.*.name , 'approved')) }}
- uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
- with:
- header: status
- message: |
- #### Terraform Plan Approval required.
-
- Add the 'approved' label to apply the planned changes.
-
- #TODO: What to do when the plan is rejected
-
- # This plan should not produce a diff
- - name: Check idempotency
- if: ${{ contains(github.event.pull_request.labels.*.name , 'approved') }}
- id: idempotency
- uses: devsectop/tf-via-pr@f1acaae1d94826457fa57bc65f1df318fd81b3bc # v12.0.0
- with:
- working-directory: terraform/development
- command: plan
- arg-lock: false
- hide-args: true
- plan-encrypt: ${{ secrets.PGP_SECRET_SIGNING_PASSPHRASE }}
- comment-pr: none
-
- - name: Update status comment
- if: ${{ steps.idempotency.outputs.exitcode == 2 }}
- uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
- with:
- header: status
- recreate: true
- message: |
- > [!WARNING]
- > ${{ github.workflow }} detected a diff after a successful `terraform apply`
-
- Please resolve the error and commit your changes to re-run the workflow.
-
- Diff of changes.
-
- ```diff
- ${{ steps.idempotency.outputs.diff }}
- ```
-
-
- ${{ steps.idempotency.outputs.summary }}
-
- ```hcl
- ${{ steps.idempotency.outputs.result }}
- ```
-
-
-
- [View run log.](${{ steps.idempotency.outputs.run-url }})
-
- - name: Remove labels
- if: ${{ steps.idempotency.outputs.exitcode == 2 }}
- run: |
- gh pr edit ${{ github.event.pull_request.number }} --remove-label "approved"
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Diff Error
- if: ${{ steps.idempotency.outputs.exitcode == 2 }}
- run: |
- exit 1
-
- - name: Update status comment
- if: ${{ contains(github.event.pull_request.labels.*.name , 'approved') && steps.idempotency.outputs.exitcode == 0 }}
- uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
- with:
- header: status
- recreate: true
- message: |
- #### Terraform apply successful :rocket:
-
- Summary of changes.
-
- ```diff
- ${{ steps.terraform.outputs.diff }}
- ```
-
-
-
- [View run log.](${{ steps.terraform.outputs.run-url }})
+ comment-pr: recreate
From 78f3230d3f9d046b3e0935b766b98db0db046149 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 14:48:56 +0000
Subject: [PATCH 09/15] ci(terraform): Set max depth on file search to 2
To ensure the .sops-file directory in not searched.
---
.github/workflows/terraform-ci.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/terraform-ci.yaml b/.github/workflows/terraform-ci.yaml
index 186b0a7..e118986 100644
--- a/.github/workflows/terraform-ci.yaml
+++ b/.github/workflows/terraform-ci.yaml
@@ -38,7 +38,7 @@ jobs:
uses: tj-actions/changed-files@c3a1bb2c992d77180ae65be6ae6c166cf40f857c # v45.0.3
with:
dir_names: true
- dir_names_max_depth: 3
+ dir_names_max_depth: 2
files: terraform/**
matrix: true
From b989d4b2522404b3b645c6249784f064fd2cb468 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 14:56:49 +0000
Subject: [PATCH 10/15] fix(terraform): Add encrypted sensitive file for sops
to read
---
.../production/.sops-files/sensitive.enc.yaml | 21 +++++++++++++++++++
.../production/.sops-files/sensitive.yaml | 1 -
2 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 terraform/production/.sops-files/sensitive.enc.yaml
delete mode 100644 terraform/production/.sops-files/sensitive.yaml
diff --git a/terraform/production/.sops-files/sensitive.enc.yaml b/terraform/production/.sops-files/sensitive.enc.yaml
new file mode 100644
index 0000000..ec205bc
--- /dev/null
+++ b/terraform/production/.sops-files/sensitive.enc.yaml
@@ -0,0 +1,21 @@
+production_aws_account_id: ENC[AES256_GCM,data:OGQZoe74L66XGHe5,iv:FI81M4+97WLF5KzLjA3H7AkaFC4uDx+ooS0vXGv4scM=,tag:K0yrBflkL/cObMnb+HWVIw==,type:int]
+sops:
+ kms: []
+ gcp_kms: []
+ azure_kv: []
+ hc_vault: []
+ age:
+ - recipient: age1wpy4kcrhan5ffwwv9dke50v9e302lhravg2njkze9qu33xgnr42q9p2d22
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvbDlpYlJsOHE1SVd4MWZj
+ eXNuZ1dyVTVWbGZqZXpUTWdRWnc5TnFOd21zCkd2TkQrUWhwaWhJaThjZmVBZGYw
+ ck1WRkhtK0ZNYmFmaXNMQXQweVFPZFkKLS0tIGFCZytBUy9SbnNkbUFIVCtKZWJH
+ Q3dVbjg3NXZPME9sdUtEYzVlcGhPbG8KyuJvku8qDbnmOm2zG94RthEQM8ML2U3n
+ YFfHPYaKVQydgbb6lziQywZja2oJICXM1zRbGvadQNpN4VH6D7OFfw==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2024-11-01T14:56:19Z"
+ mac: ENC[AES256_GCM,data:OVSNjOmC9onsy5pQPO7nIQOsDXkY3CiJ611x+Etun5XMqVpPFaVqv6xsQeNXNth4bc0uqui8zH6hGJ8TZ6Y5idfzej3fqOJ0Qz1VoLKgYNSnUsQJ/LtIKTrVaJv6zMqIrkcTwC+4Xva+Rrb538XavQ/J6PP8JOez2ako5E3BYpc=,iv:SuPbeZ1MBySAKnMY3gryyOzX3cZ0ajblmfYMBqA+zy4=,tag:chYjPV86oIqUGm+b3XHpuQ==,type:str]
+ pgp: []
+ unencrypted_suffix: _unencrypted
+ version: 3.9.1
diff --git a/terraform/production/.sops-files/sensitive.yaml b/terraform/production/.sops-files/sensitive.yaml
deleted file mode 100644
index 2e289f6..0000000
--- a/terraform/production/.sops-files/sensitive.yaml
+++ /dev/null
@@ -1 +0,0 @@
-production_aws_account_id: 535002868697
From da93d1ca2449a9f4b01db5268de859a24f4d53f7 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Fri, 1 Nov 2024 15:50:11 +0000
Subject: [PATCH 11/15] ci(checks): Add merge_group trigger
---
.github/workflows/wait-for-checks.yaml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/wait-for-checks.yaml b/.github/workflows/wait-for-checks.yaml
index e4c989b..86f38d1 100644
--- a/.github/workflows/wait-for-checks.yaml
+++ b/.github/workflows/wait-for-checks.yaml
@@ -3,7 +3,8 @@ name: Checks
on:
pull_request:
branches: [main]
- types: [opened, edited, synchronize]
+ merge_group:
+ types: [checks_requested]
# Disable permissions for all available scopes
permissions: {}
From ace65a438bf9cbf56c89f40a797d1fdcc162d35d Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Mon, 4 Nov 2024 11:28:36 +0000
Subject: [PATCH 12/15] docs(terraform): Update repository documentation
---
.github/README.md | 137 ++++++++++++++++++++++++++++------------------
1 file changed, 84 insertions(+), 53 deletions(-)
diff --git a/.github/README.md b/.github/README.md
index 192b3d9..d57bbc1 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -19,12 +19,14 @@ The main purpose of this mini camp is to build a GitOps pipeline to deploy resou
- [Branching Strategy](#branching-strategy)
- [Diagram](#diagram)
- [Workflows](#workflows)
+ - [Actions Used](#actions-used)
- [Infracost](#infracost)
- [Terraform CI](#terraform-ci)
+ - [Targets](#targets)
- [Validate](#validate)
- [Plan](#plan)
- [Apply](#apply)
- - [Diff Check](#diff-check)
+ - [Enforce All Checks](#enforce-all-checks)
- [Terraform Docs](#terraform-docs)
- [Release](#release)
- [To do list](#to-do-list)
@@ -63,8 +65,8 @@ The main purpose of this mini camp is to build a GitOps pipeline to deploy resou
| | Infracost with comment | :white_check_mark: | See PR https://github.com/3ware/gitops-2024/pull/4 |
| | Open Policy Agent fail if cost > $10 | :white_check_mark: | See PR https://github.com/3ware/gitops-2024/pull/6 |
| **Deploy** | | | |
-| | terraform apply with human intervention | :white_check_mark: | Applied when PR is approved |
-| | Deploy to production environment | | Currently deploying to _development_ environment |
+| | terraform apply with human intervention | :white_check_mark: | Applied when PR is merged |
+| | Deploy to production environment | :white_check_mark: | Matrix strategy |
| **Operate and Monitor** | | | |
| | Scheduled drift detection | :white_check_mark: | |
| | Scheduled port accessibility check | | |
@@ -75,7 +77,7 @@ The main purpose of this mini camp is to build a GitOps pipeline to deploy resou
| | Contribution Instructions | | |
| | Explains merging strategy | :white_check_mark: | |
| **Bonus** | | | |
-| | Deploy to multiple environments | | |
+| | Deploy to multiple environments | :white_check_mark: | See PR https://github.com/3ware/gitops-2024/pull/35 |
| | Ignore non-terraform changes | :white_check_mark: | Workflow trigger use paths filter for tf and tfvars files. |
| | Comment PR with useful plan information | :white_check_mark: | See PR https://github.com/3ware/gitops-2024/pull/7 |
| | Comment PR with useful Linter information | :white_check_mark: | See PR https://github.com/3ware/gitops-2024/pull/5 |
@@ -97,12 +99,11 @@ The main purpose of this mini camp is to build a GitOps pipeline to deploy resou
> Unfortunately this also cannot be automated because action runners, using `GITHUB_TOKEN` for authentication, are unable to run `gh pr ready --undo` as the integration is unavailable. See [open discussion](https://github.com/cli/cli/issues/8910)
- The workflow will run through the tests (fmt, validate, TFLint), then run `terraform plan` and post the plan to the pull request and workflow job summary.
-- To approve the plan, add the **approved** label.
-- When the [Workflows](#workflows) have completed, mark the PR as ready to assign a reviewer from CODEOWNERS. (again cannot be automated on a runner)
+- To approve the plan, approve the pull request and add the pull request to merge queue.
### When to apply?
-The [debate rumbles on](https://terramate.io/rethinking-iac/mastering-terraform-workflows-apply-before-merge-vs-apply-after-merge/). In this case, because it's just me, apply before merge is fine.
+The [debate rumbles on](https://terramate.io/rethinking-iac/mastering-terraform-workflows-apply-before-merge-vs-apply-after-merge/). The [merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue) does a pretty good job of addressing this. If `apply` is triggered using the `merge_group` event, the workflow will attempt to apply the plan from the PR and then merge the PR. If the apply fails for any reason, then the PR is not merged.
### Directories vs Workspaces for multiple environments
@@ -110,7 +111,12 @@ Another debate. The best argument I have heard for directories was in the Q&A se
> _"anyone should be able to `cd` into a terraform working directory and simply run `terraform plan` without have to worry about workspaces and variable files"_
-The workflow currently runs in the _development_ directory, with a view to having a _production_ directory should time allow.
+The workflow uses [changed-files](https://github.com/tj-actions/changed-files) to find the directories containing terraform changes. The output of this job is used to define the matrix strategy for the terraform workflow.
+
+Each directory is mapped to an environment which achieves 2 things:
+
+- Secrets, in the case, the AWS roles, are stored in the [environment](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment) - not the repository.
+- Deployments to production require additional approval.
### Branching Strategy
@@ -140,45 +146,74 @@ config:
theme: neo
---
flowchart LR
- subgraph Fail
+ subgraph Fail Checks
direction LR
- F("`**Fail Required Checks**
+ Fail("`**Fail Required Checks**
PR Cannot be merged`")
end
- subgraph Pass
+ subgraph Pass Checks
direction LR
- P("`**Met Required Checks**
- Merge PR`") -->docs(Run terraform-docs) -->rel(Generate a release)
+ noTFPass("`**Met Required Checks**`") -->merge(Merge PR to main branch)
end
- subgraph Test
+ subgraph Pass Terraform Checks
direction LR
- setup("`**Setup**
- AWS Credentials
- Install and Initialise tofu
- Install and Initialise TFLint
- with AWS Plugin`") -->
- validate{"`**Validate**
- terraform fmt
- terraform validate
- tflint
- Infracost fail if > $10`"} -->|Fail|F
+ TFPass("`**Met Required Checks**
+ Add to Merge Queue`") -->apply{terraform apply}
+ apply -->dev(Development) -->prd(Production) -->tfMerge(Complete Merge)
+ tfMerge -->docs(Run terraform-docs) -->rel(Generate a release)
+ apply -->|Fail|Fail
end
- subgraph Development [Deploy Development Environment]
+ subgraph Infracost
direction LR
- devplan(terraform plan)-->AP{"`**Approve Plan**
- via 'approved' label`"} -->|No|F
- AP -->|Yes|devapply(terraform apply) -->testdev("`**Diff Check**
- terraform plan -detailed-exitcode`") -->E{Exit code} -->|2 - Diff|PRC(PR Comment)
+ ic{"`**Infracost**
+ Infracost fail if > $10`"} -->|Fail|Fail
end
- PR(Draft Pull Request) --> Test
- validate -->|Pass|devplan
- E{Exit code} -->|0 - Succeeded|P
- PRC -->F
- F -->|Make changes and resubmit|Test
+ subgraph Targets
+ direction LR
+ target{"`**Terraform Targets**
+ Search for terraform changes and output the directory name(s)`"} -->|No Changes|noTFPass
+ end
+ subgraph Deploy Development
+ direction LR
+ devSetup("`**Setup**
+ AWS Credentials
+ Install and Initialise TFLint
+ with AWS Plugin`") -->
+ devValidate{"`**Validate**
+ terraform fmt
+ terraform validate
+ tflint`"} -->|Fail|Fail
+ devValidate -->|Pass|devPlan(terraform plan)
+ end
+ subgraph Deploy Production
+ direction LR
+ prdSetup("`**Setup**
+ AWS Credentials
+ Install and Initialise TFLint
+ with AWS Plugin`") -->
+ prdValidate{"`**Validate**
+ terraform fmt
+ terraform validate
+ tflint`"} -->|Fail|Fail
+ prdValidate -->|Pass|prdPlan(terraform plan)
+ end
+PR(Draft Pull Request) -->target & ic
+target -->|Job Matrix|devSetup
+devPlan -->prdSetup
+prdPlan -->|Approve PR|TFPass
```
### Workflows
+#### Actions Used
+
+- [changed-files](https://github.com/tj-actions/changed-files)
+- [TF-via-PR](https://github.com/DevSecTop/TF-via-PR)
+- [Infracost](https://github.com/infracost/infracost)
+- [Terraform Docs](https://github.com/terraform-docs/gh-actions)
+- [Semantic Release](https://github.com/cycjimmy/semantic-release-action)
+- [Wait for Status Checks](https://github.com/poseidon/wait-for-status-checks)
+
#### Infracost
[Infracost](workflows/infracost.yaml) runs on pull requests when they are opened or synchronized. The workflow generates a cost difference of the resources between the main branch and the proposed changes on the feature branch.
@@ -187,8 +222,17 @@ This workflow also flags any policy violations defined in [infracost-policy.rego
#### Terraform CI
+##### Targets
+
+The initial job of the workflow uses [changed-files](https://github.com/tj-actions/changed-files) to output the directories when terraform changes have been made. This output is uses ad the matrix strategy for the deploy job.
+
##### Validate
+Uses a matrix strategy to run in each directory identified in the targets job.
+
+> [!IMPORTANT]
+> The strategy has a max-parallel value of 1, which means the jobs are run sequentially
+
- Setup AWS credentials using [config-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) using OIDC to assume a role and set the authentication parameters as environment variables on the runner. This step is required when TFLint [deep checking](https://github.com/terraform-linters/tflint-ruleset-aws/blob/master/docs/deep_checking.md) for the AWS rule plugin is enabled.
- ~~Setup terraform using [setup-terraform](https://github.com/hashicorp/setup-terraform)~~ Not required. terraform v1.9.7 already installed on runner image.
- Run `terraform fmt`
@@ -197,31 +241,21 @@ This workflow also flags any policy violations defined in [infracost-policy.rego
- Install TFLint using [setup-tflint](https://github.com/terraform-linters/setup-tflint)
- Initialise TFLint to download the AWS plugin rules.
- Run `tflint`
-- Run [trunk code quality action](https://github.com/marketplace/actions/trunk-check); this runs checkov and trivy security checks.
- Update the PR comments if any of the steps fail and exit the workflow on failure.
##### Plan
-When a draft pull request is opened, and the Test Terraform job has succeeded - a ` terraform plan` will be run.
-The workflow uses [TF-via-PR](https://github.com/DevSecTop/TF-via-PR). This action adds a high level plan and detailed drop down style plan to the workflow summary and updates the pull request with a comment.
-
-> [!NOTE] > `plan` will run on `pull_request` events when the test job is successful.
+When the validation steps have succeeded - a ` terraform plan` will be run. The conditional statement runs `plan` on a `pull_request` event. The workflow uses [TF-via-PR](https://github.com/DevSecTop/TF-via-PR). This action adds a high level plan and detailed drop down style plan to the workflow summary and updates the pull request with a comment.
##### Apply
-After `terraform plan` has been run, assuming the plan is satisfactory, add the 'approved' label to he pull request to approve the plan. The workflow will run again - this time running `terraform apply`. ~~with `plan_parity` set, to ensure the plan has not changed~~
-
-I tried using `pull_request_review` as the apply trigger, but this trigger does not support the paths filter. This means an apply could be triggered when adding non _tf_ files - not ideal. See (https://github.com/3ware/gitops-2024/pull/22).
-
-I also tried using `issue_comment` but, because this runs on the default branch, a diff was always detected between `plan` and `apply` - not ideal. See (https://github.com/3ware/gitops-2024/pull/29)
+After `terraform plan` has been run, assuming the plan is accurate, approve the PR, and click `merge when ready`. This adds the pull request to the merge queue. The conditional statement in the workflow will run `terraform apply` on a `merge_group` event.
-> [!NOTE]
-> Apply will run when the 'approved' label is added and the test workflow is skipped.
-> The test workflow is skipped because it only runs on `pull_request` events. This has been tested in PR https://github.com/3ware/gitops-2024/pull/19
+##### Enforce All Checks
-##### Diff Check
+The only required check for the pull request.
-Following a successful apply, another plan is run to check for any diffs. If a diff is detected, a pull request comment is added and the workflow exits with a failure. If a diff is not detected, the pull request can be merged.
+Uses [Wait for Status Checks](https://github.com/poseidon/wait-for-status-checks) to poll the checks API for the status of the other running checks. This helps to overcome the situation where a required check may not run. For example, we could make Terraform CI a required check but, this workflow may not run (so it is skipped) and consequently the required check is not met. This workflow will detect that Terraform CI has been skipped and return an outcome of successful for itself, so the required check passes.
#### Terraform Docs
@@ -236,7 +270,4 @@ Generate a CHANGELOG and version tag using [semantic release](https://github.com
## To do list
- [ ] Grafana Port Check
-- [ ] Pull request labels environment
-- [ ] Job matrix / branched for multiple environments
-- [ ] Replace manual terraform commands with tf-via-pr for fmt and validate now this is supported
-- [ ] Raise `plan-parity` issue with TF-via-PR maintainer
+- [ ] Fix drift detection for multiple environments
From 6c6d424c4dc510a51c8b85d24df34624ef8c4e74 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Mon, 4 Nov 2024 14:36:38 +0000
Subject: [PATCH 13/15] ci(terraform): Add setup terraform action
Used to address the case where runs have failed because the terraform
version is different on different runners.
---
.github/README.md | 4 +++-
.github/workflows/terraform-ci.yaml | 8 ++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/.github/README.md b/.github/README.md
index d57bbc1..482c0b0 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -210,6 +210,8 @@ prdPlan -->|Approve PR|TFPass
- [changed-files](https://github.com/tj-actions/changed-files)
- [TF-via-PR](https://github.com/DevSecTop/TF-via-PR)
- [Infracost](https://github.com/infracost/infracost)
+- [Setup Terraform](https://github.com/hashicorp/setup-terraform/commits/main/)
+- [Setup TFLint](https://github.com/terraform-linters/setup-tflint)
- [Terraform Docs](https://github.com/terraform-docs/gh-actions)
- [Semantic Release](https://github.com/cycjimmy/semantic-release-action)
- [Wait for Status Checks](https://github.com/poseidon/wait-for-status-checks)
@@ -234,7 +236,7 @@ Uses a matrix strategy to run in each directory identified in the targets job.
> The strategy has a max-parallel value of 1, which means the jobs are run sequentially
- Setup AWS credentials using [config-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) using OIDC to assume a role and set the authentication parameters as environment variables on the runner. This step is required when TFLint [deep checking](https://github.com/terraform-linters/tflint-ruleset-aws/blob/master/docs/deep_checking.md) for the AWS rule plugin is enabled.
-- ~~Setup terraform using [setup-terraform](https://github.com/hashicorp/setup-terraform)~~ Not required. terraform v1.9.7 already installed on runner image.
+- Install terraform using [setup-terraform](https://github.com/hashicorp/setup-terraform). _Despite being installed on the runners, `apply` jobs were failing due to version differences between the apply runner and the plan runner_
- Run `terraform fmt`
- Run `terraform init`
- Run `terraform validate`
diff --git a/.github/workflows/terraform-ci.yaml b/.github/workflows/terraform-ci.yaml
index e118986..c3263cd 100644
--- a/.github/workflows/terraform-ci.yaml
+++ b/.github/workflows/terraform-ci.yaml
@@ -91,6 +91,14 @@ jobs:
path: .trunk/plugins/
key: ${{ runner.os }}-${{ github.repository }}-tflint-${{ hashFiles('.trunk/configs/.tflint_ci.hcl') }}
+ # Required, even though terraform is installed on the run because sometimes the plan and apply jobs will fail due to versions differences between
+ # the runners
+ - name: Install Terraform
+ uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
+ with:
+ terraform_version: 1.9.7
+ terraform_wrapper: true
+
# Run terraform format
- name: Terraform fmt
if: ${{ github.event_name == 'pull_request' }}
From a0fe72aca3f9d98297f3be3f942e5f047dd85293 Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Mon, 4 Nov 2024 14:37:40 +0000
Subject: [PATCH 14/15] docs(repo): Add some info to the README
---
.github/README.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.github/README.md b/.github/README.md
index 482c0b0..f541bf5 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -30,6 +30,7 @@ The main purpose of this mini camp is to build a GitOps pipeline to deploy resou
- [Terraform Docs](#terraform-docs)
- [Release](#release)
- [To do list](#to-do-list)
+ - [Contributions](#contributions)
@@ -47,7 +48,7 @@ The main purpose of this mini camp is to build a GitOps pipeline to deploy resou
| | State is stored remotely | :white_check_mark: | |
| | State Locking mechanism is enabled | :white_check_mark: | |
| **Design and Code** | | | |
-| | Confirm Account Number | :white_check_mark: | data source post condition |
+| | Confirm Account Number | :white_check_mark: | `allowed_account_ids` provider argument |
| | Confirm Region | :white_check_mark: | variable validation |
| | Add Default Tags | :white_check_mark: | added to provider block |
| | Avoid Hardcoded Values | :white_check_mark: | |
@@ -273,3 +274,7 @@ Generate a CHANGELOG and version tag using [semantic release](https://github.com
- [ ] Grafana Port Check
- [ ] Fix drift detection for multiple environments
+
+## Contributions
+
+- Special mention to the maintainer of [TF-via-PR](https://github.com/DevSecTop/TF-via-PR) for responding to queries quickly and proactively suggesting workflow improvements.
From f0da23c18b76140873f8778c8b4f7b580816e4fc Mon Sep 17 00:00:00 2001
From: chris3ware <36608309+chris3ware@users.noreply.github.com>
Date: Mon, 4 Nov 2024 14:38:23 +0000
Subject: [PATCH 15/15] ci(checks): Add run-name
---
.github/workflows/wait-for-checks.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/wait-for-checks.yaml b/.github/workflows/wait-for-checks.yaml
index 86f38d1..7d3ce9c 100644
--- a/.github/workflows/wait-for-checks.yaml
+++ b/.github/workflows/wait-for-checks.yaml
@@ -1,4 +1,5 @@
name: Checks
+run-name: ${{ github.event_name == 'merge_group' && github.event.merge_group.head_commit.message || ''}}
on:
pull_request: