diff --git a/README.md b/README.md index 8aba9f3..a4e0173 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ module "aws_account" { | Name | Version | |------|---------| | [aws.account](#provider\_aws.account) | >= 4.9.0 | +| [tfe](#provider\_tfe) | >= 0.51.0 | | [tls](#provider\_tls) | >= 4.0.4 | ## Modules @@ -217,8 +218,8 @@ module "aws_account" { | Name | Source | Version | |------|--------|---------| | [account](#module\_account) | schubergphilis/mcaf-account/aws | ~> 0.5.1 | -| [additional\_tfe\_workspaces](#module\_additional\_tfe\_workspaces) | schubergphilis/mcaf-workspace/aws | ~> 1.2.0 | -| [tfe\_workspace](#module\_tfe\_workspace) | schubergphilis/mcaf-workspace/aws | ~> 1.2.0 | +| [additional\_tfe\_workspaces](#module\_additional\_tfe\_workspaces) | schubergphilis/mcaf-workspace/aws | ~> 1.3.0 | +| [tfe\_workspace](#module\_tfe\_workspace) | schubergphilis/mcaf-workspace/aws | ~> 1.3.0 | ## Resources @@ -231,6 +232,10 @@ module "aws_account" { | [aws_iam_openid_connect_provider.tfc_provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | | [aws_iam_policy.workload_boundary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.workspace_boundary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [tfe_variable.account_variable_set_clear_text_env_variables](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | +| [tfe_variable.account_variable_set_clear_text_hcl_variables](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | +| [tfe_variable.account_variable_set_clear_text_terraform_variables](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) | resource | +| [tfe_variable_set.account](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable_set) | resource | | [tls_certificate.oidc_certificate](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | ## Inputs @@ -239,8 +244,9 @@ module "aws_account" { |------|-------------|------|---------|:--------:| | [account](#input\_account) | AWS account settings |
object({
alias_prefix = optional(string)
contact_billing = optional(object({
email_address = string
name = string
phone_number = string
title = string
}), null)
contact_operations = optional(object({
email_address = string
name = string
phone_number = string
title = string
}), null)
contact_security = optional(object({
email_address = string
name = string
phone_number = string
title = string
}), null)
email = string
environment = optional(string)
organizational_unit = string
provisioned_product_name = optional(string)
sso_email = string
sso_firstname = optional(string, "AWS Control Tower")
sso_lastname = optional(string, "Admin")
})
| n/a | yes | | [name](#input\_name) | Name of the account and default TFE workspace | `string` | n/a | yes | -| [tfe\_workspace](#input\_tfe\_workspace) | TFE workspace settings |
object({
add_permissions_boundary = optional(bool, false)
agent_pool_id = optional(string)
agent_role_arns = optional(list(string))
allow_destroy_plan = optional(bool, true)
assessments_enabled = optional(bool, true)
auth_method = optional(string, "iam_role_oidc")
auto_apply = optional(bool, false)
auto_apply_run_trigger = optional(bool, false)
branch = optional(string, "main")
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
connect_vcs_repo = optional(bool, true)
default_region = string
description = optional(string)
execution_mode = optional(string, "remote")
file_triggers_enabled = optional(bool, true)
global_remote_state = optional(bool, false)
name = optional(string)
organization = string
policy = optional(string)
policy_arns = optional(list(string), ["arn:aws:iam::aws:policy/AdministratorAccess"])
project_id = optional(string)
queue_all_runs = optional(bool)
remote_state_consumer_ids = optional(set(string))
repository_identifier = optional(string)
role_name = optional(string, "TFEPipeline")
sensitive_env_variables = optional(map(string), {})
sensitive_hcl_variables = optional(map(object({ sensitive = string })), {})
sensitive_terraform_variables = optional(map(string), {})
ssh_key_id = optional(string)
terraform_version = optional(string)
trigger_patterns = optional(list(string))
trigger_prefixes = optional(list(string), ["modules"])
username = optional(string, "TFEPipeline")
vcs_oauth_token_id = string
working_directory = optional(string)
workspace_tags = optional(list(string))

notification_configuration = optional(list(object({
destination_type = string
enabled = optional(bool, true)
url = string
triggers = optional(list(string), [
"run:created",
"run:planning",
"run:needs_attention",
"run:applying",
"run:completed",
"run:errored",
])
})), [])

team_access = optional(map(object({
access = optional(string, null),
permissions = optional(object({
run_tasks = bool
runs = string
sentinel_mocks = string
state_versions = string
variables = string
workspace_locking = bool
}), null)
})), {})
})
| n/a | yes | -| [additional\_tfe\_workspaces](#input\_additional\_tfe\_workspaces) | Additional TFE workspaces |
map(object({
add_permissions_boundary = optional(bool, false)
agent_pool_id = optional(string)
agent_role_arns = optional(list(string))
allow_destroy_plan = optional(bool)
assessments_enabled = optional(bool)
auth_method = optional(string)
auto_apply = optional(bool, false)
auto_apply_run_trigger = optional(bool, false)
branch = optional(string)
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
connect_vcs_repo = optional(bool, true)
default_region = optional(string)
description = optional(string)
execution_mode = optional(string)
file_triggers_enabled = optional(bool, true)
global_remote_state = optional(bool, false)
name = optional(string)
policy = optional(string)
policy_arns = optional(list(string), ["arn:aws:iam::aws:policy/AdministratorAccess"])
project_id = optional(string)
queue_all_runs = optional(bool)
remote_state_consumer_ids = optional(set(string))
repository_identifier = optional(string)
role_name = optional(string)
sensitive_env_variables = optional(map(string), {})
sensitive_hcl_variables = optional(map(object({ sensitive = string })), {})
sensitive_terraform_variables = optional(map(string), {})
ssh_key_id = optional(string)
terraform_version = optional(string)
trigger_patterns = optional(list(string))
trigger_prefixes = optional(list(string))
username = optional(string)
vcs_oauth_token_id = optional(string)
working_directory = optional(string)
workspace_tags = optional(list(string))

notification_configuration = optional(list(object({
destination_type = string
enabled = optional(bool, true)
url = string
triggers = optional(list(string), [
"run:created",
"run:planning",
"run:needs_attention",
"run:applying",
"run:completed",
"run:errored",
])
})), [])

team_access = optional(map(object({
access = optional(string, null),
permissions = optional(object({
run_tasks = bool
runs = string
sentinel_mocks = string
state_versions = string
variables = string
workspace_locking = bool
}), null)
})), {})
}))
| `{}` | no | +| [tfe\_workspace](#input\_tfe\_workspace) | TFE workspace settings |
object({
add_permissions_boundary = optional(bool, false)
agent_pool_id = optional(string)
agent_role_arns = optional(list(string))
allow_destroy_plan = optional(bool, true)
assessments_enabled = optional(bool, true)
auth_method = optional(string, "iam_role_oidc")
auto_apply = optional(bool, false)
auto_apply_run_trigger = optional(bool, false)
branch = optional(string, "main")
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
connect_vcs_repo = optional(bool, true)
default_region = string
description = optional(string)
execution_mode = optional(string, "remote")
file_triggers_enabled = optional(bool, true)
global_remote_state = optional(bool, false)
name = optional(string)
organization = string
policy = optional(string)
policy_arns = optional(list(string), ["arn:aws:iam::aws:policy/AdministratorAccess"])
project_id = optional(string)
queue_all_runs = optional(bool)
remote_state_consumer_ids = optional(set(string))
repository_identifier = optional(string)
role_name = optional(string, "TFEPipeline")
sensitive_env_variables = optional(map(string), {})
sensitive_hcl_variables = optional(map(object({ sensitive = string })), {})
sensitive_terraform_variables = optional(map(string), {})
ssh_key_id = optional(string)
terraform_version = optional(string)
trigger_patterns = optional(list(string))
trigger_prefixes = optional(list(string), ["modules"])
username = optional(string, "TFEPipeline")
vcs_oauth_token_id = string
variable_set_ids = optional(map(string), {})
working_directory = optional(string)
workspace_tags = optional(list(string))

notification_configuration = optional(list(object({
destination_type = string
enabled = optional(bool, true)
url = string
triggers = optional(list(string), [
"run:created",
"run:planning",
"run:needs_attention",
"run:applying",
"run:completed",
"run:errored",
])
})), [])

team_access = optional(map(object({
access = optional(string, null),
permissions = optional(object({
run_tasks = bool
runs = string
sentinel_mocks = string
state_versions = string
variables = string
workspace_locking = bool
}), null)
})), {})
})
| n/a | yes | +| [account\_variable\_set](#input\_account\_variable\_set) | Settings of variable set that is attached to each workspace |
object({
name = optional(string)
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
})
| `{}` | no | +| [additional\_tfe\_workspaces](#input\_additional\_tfe\_workspaces) | Additional TFE workspaces |
map(object({
add_permissions_boundary = optional(bool, false)
agent_pool_id = optional(string)
agent_role_arns = optional(list(string))
allow_destroy_plan = optional(bool)
assessments_enabled = optional(bool)
auth_method = optional(string)
auto_apply = optional(bool, false)
auto_apply_run_trigger = optional(bool, false)
branch = optional(string)
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
connect_vcs_repo = optional(bool, true)
default_region = optional(string)
description = optional(string)
execution_mode = optional(string)
file_triggers_enabled = optional(bool, true)
global_remote_state = optional(bool, false)
name = optional(string)
policy = optional(string)
policy_arns = optional(list(string), ["arn:aws:iam::aws:policy/AdministratorAccess"])
project_id = optional(string)
queue_all_runs = optional(bool)
remote_state_consumer_ids = optional(set(string))
repository_identifier = optional(string)
role_name = optional(string)
sensitive_env_variables = optional(map(string), {})
sensitive_hcl_variables = optional(map(object({ sensitive = string })), {})
sensitive_terraform_variables = optional(map(string), {})
ssh_key_id = optional(string)
terraform_version = optional(string)
trigger_patterns = optional(list(string))
trigger_prefixes = optional(list(string))
username = optional(string)
vcs_oauth_token_id = optional(string)
variable_set_ids = optional(map(string), {})
working_directory = optional(string)
workspace_tags = optional(list(string))

notification_configuration = optional(list(object({
destination_type = string
enabled = optional(bool, true)
url = string
triggers = optional(list(string), [
"run:created",
"run:planning",
"run:needs_attention",
"run:applying",
"run:completed",
"run:errored",
])
})), [])

team_access = optional(map(object({
access = optional(string, null),
permissions = optional(object({
run_tasks = bool
runs = string
sentinel_mocks = string
state_versions = string
variables = string
workspace_locking = bool
}), null)
})), {})
}))
| `{}` | no | | [create\_default\_workspace](#input\_create\_default\_workspace) | Set to false to skip creating default workspace | `bool` | `true` | no | | [path](#input\_path) | Optional path for all IAM users, user groups, roles, and customer managed policies created by this module | `string` | `"/"` | no | | [permissions\_boundaries](#input\_permissions\_boundaries) | n/a |
object({
workspace_boundary = optional(string)
workspace_boundary_name = optional(string)
workload_boundary = optional(string)
workload_boundary_name = optional(string)
})
| `{}` | no | @@ -250,6 +256,7 @@ module "aws_account" { | Name | Description | |------|-------------| +| [account\_variable\_set\_id](#output\_account\_variable\_set\_id) | The ID of the account variable set | | [additional\_tfe\_workspaces](#output\_additional\_tfe\_workspaces) | Map of any additional Terraform Cloud workspace names and IDs | | [environment](#output\_environment) | The environment name | | [id](#output\_id) | The AWS account ID | diff --git a/main.tf b/main.tf index bf5ca99..30450e8 100644 --- a/main.tf +++ b/main.tf @@ -1,5 +1,7 @@ locals { - tfe_workspace = { + account_variable_set = { + name = var.account_variable_set.name != null ? var.account_variable_set.name : "account-${var.name}" + clear_text_terraform_variables = merge( // always add account = var.name { account = var.name }, @@ -8,7 +10,9 @@ locals { // if workload_boundary_arn, add workload_permissions_boundary_arn = aws_iam_policy.workload_boundary[0].arn var.permissions_boundaries.workload_boundary != null && var.permissions_boundaries.workload_boundary != null ? { workload_permissions_boundary_arn = aws_iam_policy.workload_boundary[0].arn } : {} ) + } + tfe_workspace = { working_directory = var.account.environment != null ? "terraform/${var.account.environment}" : "terraform" } @@ -116,6 +120,43 @@ resource "aws_iam_policy" "workload_boundary" { policy = templatefile(var.permissions_boundaries.workload_boundary, { account_id = module.account.id }) } +################################################################################ +# Terraform Cloud Variable Set +################################################################################ + +resource "tfe_variable_set" "account" { + name = local.account_variable_set.name + description = "Variable set for the account and all its linked workspaces" + organization = var.tfe_workspace.organization +} + +resource "tfe_variable" "account_variable_set_clear_text_env_variables" { + for_each = var.account_variable_set.clear_text_env_variables + + key = each.key + value = each.value + category = "env" + variable_set_id = tfe_variable_set.account.id +} + +resource "tfe_variable" "account_variable_set_clear_text_hcl_variables" { + for_each = var.account_variable_set.clear_text_hcl_variables + + key = each.key + value = each.value + category = "terraform" + hcl = true + variable_set_id = tfe_variable_set.account.id +} + +resource "tfe_variable" "account_variable_set_clear_text_terraform_variables" { + for_each = local.account_variable_set.clear_text_terraform_variables + + key = each.key + value = each.value + category = "terraform" + variable_set_id = tfe_variable_set.account.id +} ################################################################################ # Terraform Cloud Workspace(s) @@ -123,10 +164,11 @@ resource "aws_iam_policy" "workload_boundary" { module "tfe_workspace" { count = var.create_default_workspace ? 1 : 0 + providers = { aws = aws.account } source = "schubergphilis/mcaf-workspace/aws" - version = "~> 1.2.0" + version = "~> 1.3.0" agent_pool_id = var.tfe_workspace.agent_pool_id agent_role_arns = var.tfe_workspace.agent_role_arns @@ -138,7 +180,7 @@ module "tfe_workspace" { branch = var.tfe_workspace.connect_vcs_repo != false ? var.tfe_workspace.branch : null clear_text_env_variables = var.tfe_workspace.clear_text_env_variables clear_text_hcl_variables = var.tfe_workspace.clear_text_hcl_variables - clear_text_terraform_variables = merge(local.tfe_workspace.clear_text_terraform_variables, var.tfe_workspace.clear_text_terraform_variables) + clear_text_terraform_variables = var.tfe_workspace.clear_text_terraform_variables description = var.tfe_workspace.description execution_mode = var.tfe_workspace.execution_mode file_triggers_enabled = var.tfe_workspace.connect_vcs_repo != false ? var.tfe_workspace.file_triggers_enabled : null @@ -167,16 +209,18 @@ module "tfe_workspace" { trigger_patterns = var.tfe_workspace.trigger_patterns trigger_prefixes = var.tfe_workspace.connect_vcs_repo != false ? var.tfe_workspace.trigger_prefixes : null username = var.tfe_workspace.username + variable_set_ids = merge({ (local.account_variable_set.name) : tfe_variable_set.account.id }, var.tfe_workspace.variable_set_ids) working_directory = coalesce(var.tfe_workspace.working_directory, local.tfe_workspace.working_directory) workspace_tags = var.tfe_workspace.workspace_tags } module "additional_tfe_workspaces" { for_each = var.additional_tfe_workspaces + providers = { aws = aws.account } source = "schubergphilis/mcaf-workspace/aws" - version = "~> 1.2.0" + version = "~> 1.3.0" agent_pool_id = each.value.agent_pool_id != null ? each.value.agent_pool_id : var.tfe_workspace.agent_pool_id agent_role_arns = each.value.agent_role_arns != null ? each.value.agent_role_arns : var.tfe_workspace.agent_role_arns @@ -188,7 +232,7 @@ module "additional_tfe_workspaces" { branch = each.value.connect_vcs_repo != false ? coalesce(each.value.branch, var.tfe_workspace.branch) : null clear_text_env_variables = each.value.clear_text_env_variables clear_text_hcl_variables = each.value.clear_text_hcl_variables - clear_text_terraform_variables = merge(local.tfe_workspace.clear_text_terraform_variables, each.value.clear_text_terraform_variables) + clear_text_terraform_variables = each.value.clear_text_terraform_variables description = each.value.description execution_mode = coalesce(each.value.execution_mode, var.tfe_workspace.execution_mode) file_triggers_enabled = each.value.connect_vcs_repo != false ? each.value.file_triggers_enabled : null @@ -217,6 +261,7 @@ module "additional_tfe_workspaces" { trigger_patterns = each.value.trigger_patterns != null ? each.value.trigger_patterns : var.tfe_workspace.trigger_patterns trigger_prefixes = each.value.connect_vcs_repo != false ? coalesce(each.value.trigger_prefixes, var.tfe_workspace.trigger_prefixes) : null username = coalesce(each.value.username, "TFEPipeline-${each.key}") + variable_set_ids = merge({ (local.account_variable_set.name) : tfe_variable_set.account.id }, each.value.variable_set_ids) working_directory = coalesce(each.value.working_directory, "terraform/${coalesce(each.value.name, each.key)}") workspace_tags = each.value.workspace_tags } diff --git a/outputs.tf b/outputs.tf index a4a6f52..371e632 100644 --- a/outputs.tf +++ b/outputs.tf @@ -36,6 +36,13 @@ output "tfe_workspaces" { description = "List of Terraform Cloud workspaces" } + +output "account_variable_set_id" { + value = tfe_variable_set.account.id + description = "The ID of the account variable set" +} + + output "workload_permissions_boundary_arn" { value = try(aws_iam_policy.workload_boundary[0].arn, "") description = "The ARN of the workload permissions boundary" @@ -45,4 +52,3 @@ output "workspace_permissions_boundary_arn" { value = try(aws_iam_policy.workspace_boundary[0].arn, "") description = "The ARN of the workspace permissions boundary" } - diff --git a/variables.tf b/variables.tf index 5181fbd..53d2a1d 100644 --- a/variables.tf +++ b/variables.tf @@ -30,6 +30,17 @@ variable "account" { description = "AWS account settings" } +variable "account_variable_set" { + type = object({ + name = optional(string) + clear_text_env_variables = optional(map(string), {}) + clear_text_hcl_variables = optional(map(string), {}) + clear_text_terraform_variables = optional(map(string), {}) + }) + default = {} + description = "Settings of variable set that is attached to each workspace" +} + variable "additional_tfe_workspaces" { type = map(object({ add_permissions_boundary = optional(bool, false) @@ -67,6 +78,7 @@ variable "additional_tfe_workspaces" { trigger_prefixes = optional(list(string)) username = optional(string) vcs_oauth_token_id = optional(string) + variable_set_ids = optional(map(string), {}) working_directory = optional(string) workspace_tags = optional(list(string)) @@ -171,6 +183,7 @@ variable "tfe_workspace" { trigger_prefixes = optional(list(string), ["modules"]) username = optional(string, "TFEPipeline") vcs_oauth_token_id = string + variable_set_ids = optional(map(string), {}) working_directory = optional(string) workspace_tags = optional(list(string))