Simplify bootstrap inputs to YAML config
This commit is contained in:
parent
dd2afb0e13
commit
6943ab67fc
@ -21,7 +21,7 @@ Terragrunt `run-all` handles the ordering; no manual sequencing is required.
|
||||
|
||||
- **Data plane**: S3 bucket enforces AES256 SSE, public access block, and versioning. DynamoDB enables server-side encryption and PITR for forensic recovery.
|
||||
- **Control plane**: IAM policies are externalized in `identity/policies/*.json` and rendered via `aws_iam_policy_document` to keep Terraform code lean and auditable.
|
||||
- **Config source of truth**: The GitOps repo (`https://github.com/cloud-neutral-workshop/gitops.git`) stores `config/accounts/bootstrap.yaml`, defining canonical names, regions, and tags. Terragrunt reads it via `GITOPS_REPO_ROOT` (defaults to `../gitops` relative to this repo). Clone that repository locally or set `GITOPS_REPO_ROOT` to your desired path to keep configuration and modules separated. You can also override the config file path with `GITOPS_BOOTSTRAP_CONFIG` (for example, `config/xzerolab/sit/aws-cloud/account/bootstrap.yaml` inside the GitOps repo).
|
||||
- **Config source of truth**: Provide the bootstrap YAML path via `TF_CONFIG` (absolute or repo-root relative). When unset, Terragrunt defaults to `gitops/${GITOPS_BOOTSTRAP_CONFIG:-config/bootstrap.yaml}` relative to the repo root inferred from `TG_ROOT` (`terraform-hcl-standard/aws-cloud/bootstrap`).
|
||||
|
||||
## How to Run with Terragrunt
|
||||
|
||||
@ -70,3 +70,18 @@ Document the teardown in your change log for auditability.
|
||||
- **Idempotent automation**: All configurations are declarative, version-controlled, and runnable via Terragrunt without manual steps.
|
||||
- **Auditability**: Policies live in external JSON files; DynamoDB PITR and S3 versioning preserve history for compliance.
|
||||
- **Portability**: Inputs are read from YAML configuration and Terragrunt dependencies, making the stack reusable across accounts and regions.
|
||||
|
||||
Optional YAML fields supported by the bootstrap modules:
|
||||
|
||||
```yaml
|
||||
state:
|
||||
create_bucket: true
|
||||
iam:
|
||||
create_role: true
|
||||
existing_role_name: null
|
||||
existing_role_arn: null
|
||||
create_user: true
|
||||
existing_user_name: null
|
||||
managed_policy_arns:
|
||||
- arn:aws:iam::aws:policy/AdministratorAccess
|
||||
```
|
||||
|
||||
@ -1,22 +1,27 @@
|
||||
locals {
|
||||
config_root = coalesce(var.config_root, abspath("${path.module}/../../../../../gitops"))
|
||||
bootstrap_config_path = coalesce(
|
||||
var.bootstrap_config_path,
|
||||
"${local.config_root}/config/accounts/bootstrap.yaml"
|
||||
)
|
||||
bootstrap_config_path = abspath(var.bootstrap_config_path)
|
||||
config_root = dirname(dirname(dirname(local.bootstrap_config_path)))
|
||||
bootstrap = yamldecode(file(local.bootstrap_config_path))
|
||||
|
||||
config_account_name = coalesce(var.account_name, local.bootstrap.account_name)
|
||||
config_region = coalesce(var.region, local.bootstrap.region)
|
||||
config_role_name = coalesce(var.role_name, local.bootstrap.iam.role_name)
|
||||
config_terraform_user = coalesce(var.terraform_user_name, local.bootstrap.iam.terraform_user_name)
|
||||
config_account_name = local.bootstrap.account_name
|
||||
config_region = local.bootstrap.region
|
||||
config_role_name = local.bootstrap.iam.role_name
|
||||
config_terraform_user = local.bootstrap.iam.terraform_user_name
|
||||
environment = coalesce(try(local.bootstrap.environment, null), try(local.bootstrap.iam.environment, null), "bootstrap")
|
||||
extra_tags = try(local.bootstrap.tags, {})
|
||||
|
||||
role_name = coalesce(var.existing_role_name, local.config_role_name)
|
||||
terraform_user_name = coalesce(var.existing_user_name, local.config_terraform_user)
|
||||
state_bucket_name = coalesce(var.state_bucket_name, try(local.bootstrap.state.bucket_name, null))
|
||||
lock_table_name = coalesce(var.state_lock_table_name, try(local.bootstrap.state.dynamodb_table_name, null))
|
||||
create_role = try(local.bootstrap.iam.create_role, true)
|
||||
existing_role_name = try(local.bootstrap.iam.existing_role_name, null)
|
||||
existing_role_arn = try(local.bootstrap.iam.existing_role_arn, null)
|
||||
role_name = coalesce(local.existing_role_name, local.config_role_name)
|
||||
|
||||
create_user = try(local.bootstrap.iam.create_user, true)
|
||||
existing_user_name = try(local.bootstrap.iam.existing_user_name, null)
|
||||
terraform_user_name = coalesce(local.existing_user_name, local.config_terraform_user)
|
||||
|
||||
state_bucket_name = try(local.bootstrap.state.bucket_name, null)
|
||||
lock_table_name = try(local.bootstrap.state.dynamodb_table_name, null)
|
||||
managed_policy_arns = try(local.bootstrap.iam.managed_policy_arns, ["arn:aws:iam::aws:policy/AdministratorAccess"])
|
||||
}
|
||||
|
||||
locals {
|
||||
|
||||
@ -56,7 +56,7 @@ data "aws_iam_policy_document" "terraform_deploy_assume_role" {
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "terraform_deploy_role" {
|
||||
count = var.create_role ? 1 : 0
|
||||
count = local.create_role ? 1 : 0
|
||||
|
||||
name = local.role_name
|
||||
assume_role_policy = data.aws_iam_policy_document.terraform_deploy_assume_role.json
|
||||
@ -87,7 +87,7 @@ data "aws_iam_policy_document" "terraform_deploy_inline" {
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "terraform_deploy_role_policy" {
|
||||
count = var.create_role ? 1 : 0
|
||||
count = local.create_role ? 1 : 0
|
||||
|
||||
name = "${local.role_name}-bootstrap-minimal"
|
||||
role = aws_iam_role.terraform_deploy_role[0].id
|
||||
@ -95,17 +95,17 @@ resource "aws_iam_role_policy" "terraform_deploy_role_policy" {
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "terraform_deploy_role_managed" {
|
||||
count = var.create_role ? length(var.managed_policy_arns) : 0
|
||||
count = local.create_role ? length(local.managed_policy_arns) : 0
|
||||
|
||||
role = aws_iam_role.terraform_deploy_role[0].name
|
||||
policy_arn = var.managed_policy_arns[count.index]
|
||||
policy_arn = local.managed_policy_arns[count.index]
|
||||
}
|
||||
|
||||
#
|
||||
# IAM User for Terraform (AK/SK)
|
||||
# ----------------------------------------
|
||||
resource "aws_iam_user" "terraform_user" {
|
||||
count = var.create_user ? 1 : 0
|
||||
count = local.create_user ? 1 : 0
|
||||
|
||||
name = local.terraform_user_name
|
||||
}
|
||||
@ -126,7 +126,7 @@ data "aws_iam_policy_document" "terraform_user" {
|
||||
}
|
||||
|
||||
resource "aws_iam_user_policy" "terraform_user_policy" {
|
||||
count = var.create_user ? 1 : 0
|
||||
count = local.create_user ? 1 : 0
|
||||
|
||||
name = "${local.terraform_user_name}-iac-policy"
|
||||
user = aws_iam_user.terraform_user[0].name
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
output "iam_role_arn" {
|
||||
value = var.create_role ? aws_iam_role.terraform_deploy_role[0].arn : var.existing_role_arn
|
||||
value = local.create_role ? aws_iam_role.terraform_deploy_role[0].arn : local.existing_role_arn
|
||||
description = "The ARN of the role assumed by Terraform"
|
||||
}
|
||||
|
||||
output "terraform_user_name" {
|
||||
value = var.create_user ? aws_iam_user.terraform_user[0].name : local.terraform_user_name
|
||||
value = local.create_user ? aws_iam_user.terraform_user[0].name : local.terraform_user_name
|
||||
description = "Terraform IAM User"
|
||||
}
|
||||
|
||||
|
||||
@ -11,18 +11,16 @@ terraform {
|
||||
}
|
||||
|
||||
locals {
|
||||
gitops_repo_root = get_env(
|
||||
"GITOPS_REPO_ROOT",
|
||||
abspath("${get_parent_terragrunt_dir()}/../../../../../gitops")
|
||||
)
|
||||
config_root = "${local.gitops_repo_root}/config"
|
||||
bootstrap_config_path = get_env(
|
||||
"GITOPS_BOOTSTRAP_CONFIG",
|
||||
"${local.config_root}/accounts/bootstrap.yaml"
|
||||
)
|
||||
tg_root = get_env("TG_ROOT", get_parent_terragrunt_dir())
|
||||
repo_root = abspath("${local.tg_root}/../../../../../")
|
||||
gitops_root = "${local.repo_root}/gitops"
|
||||
|
||||
tf_config_env = trimspace(get_env("TF_CONFIG", ""))
|
||||
bootstrap_config_path = local.tf_config_env != "" ? (
|
||||
startswith(local.tf_config_env, "/") ? local.tf_config_env : abspath("${local.repo_root}/${local.tf_config_env}")
|
||||
) : abspath("${local.gitops_root}/${get_env("GITOPS_BOOTSTRAP_CONFIG", "config/bootstrap.yaml")}")
|
||||
}
|
||||
|
||||
inputs = {
|
||||
bootstrap_config_path = local.bootstrap_config_path
|
||||
config_root = local.gitops_repo_root
|
||||
}
|
||||
|
||||
@ -1,93 +1,9 @@
|
||||
variable "region" {
|
||||
description = "AWS region"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "account_name" {
|
||||
type = string
|
||||
description = "Which account configuration to load (e.g., dev)"
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "create_role" {
|
||||
description = "Whether to create the Terraform deploy IAM role"
|
||||
type = bool
|
||||
default = true
|
||||
|
||||
validation {
|
||||
condition = var.create_role || (var.existing_role_arn != null && var.existing_role_name != null)
|
||||
error_message = "existing_role_name and existing_role_arn must be provided when create_role is false."
|
||||
}
|
||||
}
|
||||
|
||||
variable "existing_role_name" {
|
||||
description = "Existing IAM role name to reference when create_role is false"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "existing_role_arn" {
|
||||
description = "Existing IAM role ARN to reference when create_role is false"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "role_name" {
|
||||
type = string
|
||||
description = "IAM role name to create (e.g., TerraformDeployRole-Dev)"
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "existing_user_name" {
|
||||
description = "Existing IAM username to reference when create_user is false"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "terraform_user_name" {
|
||||
type = string
|
||||
description = "IAM username for Terraform IAC runner"
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "create_user" {
|
||||
description = "Whether to create the IAM user for Terraform"
|
||||
type = bool
|
||||
default = true
|
||||
|
||||
validation {
|
||||
condition = var.create_user || var.existing_user_name != null
|
||||
error_message = "existing_user_name must be provided when create_user is false."
|
||||
}
|
||||
}
|
||||
|
||||
variable "state_bucket_name" {
|
||||
description = "Name of the Terraform state bucket (overrides bootstrap config when provided)"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "state_lock_table_name" {
|
||||
description = "Name of the DynamoDB state lock table (overrides bootstrap config when provided)"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "bootstrap_config_path" {
|
||||
description = "Path to the bootstrap account configuration YAML"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "config_root" {
|
||||
description = "Local path to the gitops repository root."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "managed_policy_arns" {
|
||||
description = "List of managed policy ARNs to attach to the Terraform deploy role"
|
||||
type = list(string)
|
||||
default = ["arn:aws:iam::aws:policy/AdministratorAccess"]
|
||||
validation {
|
||||
condition = var.bootstrap_config_path != null && trimspace(var.bootstrap_config_path) != ""
|
||||
error_message = "Set bootstrap_config_path (TF_CONFIG) to the bootstrap YAML file path."
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
locals {
|
||||
config_root = coalesce(var.config_root, abspath("${path.module}/../../../../../gitops"))
|
||||
bootstrap_config_path = coalesce(
|
||||
var.bootstrap_config_path,
|
||||
"${local.config_root}/config/accounts/bootstrap.yaml"
|
||||
)
|
||||
|
||||
bootstrap_config_path = abspath(var.bootstrap_config_path)
|
||||
bootstrap = yamldecode(file(local.bootstrap_config_path))
|
||||
|
||||
dynamodb_table_name = coalesce(var.table_name, local.bootstrap.state.dynamodb_table_name)
|
||||
region = coalesce(var.region, local.bootstrap.region)
|
||||
dynamodb_table_name = local.bootstrap.state.dynamodb_table_name
|
||||
region = local.bootstrap.region
|
||||
environment = try(local.bootstrap.environment, "bootstrap")
|
||||
tags = try(local.bootstrap.tags, {})
|
||||
}
|
||||
|
||||
@ -11,18 +11,16 @@ terraform {
|
||||
}
|
||||
|
||||
locals {
|
||||
gitops_repo_root = get_env(
|
||||
"GITOPS_REPO_ROOT",
|
||||
abspath("${get_parent_terragrunt_dir()}/../../../../../gitops")
|
||||
)
|
||||
config_root = "${local.gitops_repo_root}/config"
|
||||
bootstrap_config_path = get_env(
|
||||
"GITOPS_BOOTSTRAP_CONFIG",
|
||||
"${local.config_root}/accounts/bootstrap.yaml"
|
||||
)
|
||||
tg_root = get_env("TG_ROOT", get_parent_terragrunt_dir())
|
||||
repo_root = abspath("${local.tg_root}/../../../../../")
|
||||
gitops_root = "${local.repo_root}/gitops"
|
||||
|
||||
tf_config_env = trimspace(get_env("TF_CONFIG", ""))
|
||||
bootstrap_config_path = local.tf_config_env != "" ? (
|
||||
startswith(local.tf_config_env, "/") ? local.tf_config_env : abspath("${local.repo_root}/${local.tf_config_env}")
|
||||
) : abspath("${local.gitops_root}/${get_env("GITOPS_BOOTSTRAP_CONFIG", "config/bootstrap.yaml")}")
|
||||
}
|
||||
|
||||
inputs = {
|
||||
bootstrap_config_path = local.bootstrap_config_path
|
||||
config_root = local.gitops_repo_root
|
||||
}
|
||||
|
||||
@ -1,23 +1,9 @@
|
||||
variable "table_name" {
|
||||
description = "DynamoDB table name for Terraform state lock"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "AWS region"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "bootstrap_config_path" {
|
||||
description = "Path to the bootstrap account configuration YAML"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "config_root" {
|
||||
description = "Local path to the gitops repository root."
|
||||
type = string
|
||||
default = null
|
||||
validation {
|
||||
condition = var.bootstrap_config_path != null && trimspace(var.bootstrap_config_path) != ""
|
||||
error_message = "Set bootstrap_config_path (TF_CONFIG) to the bootstrap YAML file path."
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,11 +3,12 @@ locals {
|
||||
|
||||
bootstrap = yamldecode(file(local.bootstrap_config_path))
|
||||
|
||||
bucket_name = coalesce(var.bucket_name, local.bootstrap.state.bucket_name)
|
||||
region = coalesce(var.region, local.bootstrap.region)
|
||||
bucket_name = local.bootstrap.state.bucket_name
|
||||
region = local.bootstrap.region
|
||||
environment = try(local.bootstrap.environment, "bootstrap")
|
||||
tags = try(local.bootstrap.tags, {})
|
||||
|
||||
bucket_arn = var.create_bucket ? aws_s3_bucket.state[0].arn : data.aws_s3_bucket.existing[0].arn
|
||||
bucket_id = var.create_bucket ? aws_s3_bucket.state[0].id : data.aws_s3_bucket.existing[0].id
|
||||
create_bucket = try(local.bootstrap.state.create_bucket, true)
|
||||
bucket_arn = local.create_bucket ? aws_s3_bucket.state[0].arn : data.aws_s3_bucket.existing[0].arn
|
||||
bucket_id = local.create_bucket ? aws_s3_bucket.state[0].id : data.aws_s3_bucket.existing[0].id
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
resource "aws_s3_bucket" "state" {
|
||||
count = var.create_bucket ? 1 : 0
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = local.bucket_name
|
||||
|
||||
tags = merge(
|
||||
@ -12,12 +12,12 @@ resource "aws_s3_bucket" "state" {
|
||||
}
|
||||
|
||||
data "aws_s3_bucket" "existing" {
|
||||
count = var.create_bucket ? 0 : 1
|
||||
count = local.create_bucket ? 0 : 1
|
||||
bucket = local.bucket_name
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "versioning" {
|
||||
count = var.create_bucket ? 1 : 0
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = local.bucket_id
|
||||
|
||||
versioning_configuration {
|
||||
@ -26,7 +26,7 @@ resource "aws_s3_bucket_versioning" "versioning" {
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "sse" {
|
||||
count = var.create_bucket ? 1 : 0
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = local.bucket_id
|
||||
|
||||
rule {
|
||||
@ -37,7 +37,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "sse" {
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "block" {
|
||||
count = var.create_bucket ? 1 : 0
|
||||
count = local.create_bucket ? 1 : 0
|
||||
bucket = local.bucket_id
|
||||
|
||||
block_public_acls = true
|
||||
|
||||
@ -7,18 +7,16 @@ terraform {
|
||||
}
|
||||
|
||||
locals {
|
||||
gitops_repo_root = get_env(
|
||||
"GITOPS_REPO_ROOT",
|
||||
abspath("${get_parent_terragrunt_dir()}/../../../../../gitops")
|
||||
)
|
||||
config_root = "${local.gitops_repo_root}/config"
|
||||
bootstrap_config_path = get_env(
|
||||
"GITOPS_BOOTSTRAP_CONFIG",
|
||||
"${local.config_root}/accounts/bootstrap.yaml"
|
||||
)
|
||||
tg_root = get_env("TG_ROOT", get_parent_terragrunt_dir())
|
||||
repo_root = abspath("${local.tg_root}/../../../../../")
|
||||
gitops_root = "${local.repo_root}/gitops"
|
||||
|
||||
tf_config_env = trimspace(get_env("TF_CONFIG", ""))
|
||||
bootstrap_config_path = local.tf_config_env != "" ? (
|
||||
startswith(local.tf_config_env, "/") ? local.tf_config_env : abspath("${local.repo_root}/${local.tf_config_env}")
|
||||
) : abspath("${local.gitops_root}/${get_env("GITOPS_BOOTSTRAP_CONFIG", "config/bootstrap.yaml")}")
|
||||
}
|
||||
|
||||
inputs = {
|
||||
bootstrap_config_path = local.bootstrap_config_path
|
||||
config_root = local.gitops_repo_root
|
||||
}
|
||||
|
||||
@ -1,33 +1,9 @@
|
||||
variable "bucket_name" {
|
||||
description = "S3 bucket name for Terraform state"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "AWS region"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "bootstrap_config_path" {
|
||||
description = "Path to the bootstrap account configuration YAML"
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = var.bootstrap_config_path != null && trimspace(var.bootstrap_config_path) != ""
|
||||
error_message = "Set bootstrap_config_path to the GitHub Action environment input that points to the bootstrap YAML file."
|
||||
error_message = "Set bootstrap_config_path (TF_CONFIG) to the bootstrap YAML file path."
|
||||
}
|
||||
}
|
||||
|
||||
variable "config_root" {
|
||||
description = "Local path to the gitops repository root."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "create_bucket" {
|
||||
description = "Whether to create the Terraform state bucket. Set to false to use an existing bucket."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user