Skip to content

Commit a7e87a4

Browse files
authored
Restore support for Terraform Cloud (#57)
1 parent 80b790b commit a7e87a4

6 files changed

Lines changed: 103 additions & 127 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
locals {
2+
remote_workspace = var.workspace != null ? var.workspace : local.workspace
3+
ds_backend = contains(["remote", "s3"], local.backend_type) ? local.backend_type : "local"
4+
ds_workspace = local.ds_backend == "local" ? null : local.remote_workspace
5+
6+
ds_configurations = {
7+
local = {
8+
path = "${path.module}/dummy-remote-state.json"
9+
}
10+
11+
remote = local.ds_backend == "remote" ? {
12+
organization = local.backend.organization
13+
14+
workspaces = {
15+
name = local.remote_workspace
16+
}
17+
} : null
18+
19+
s3 = local.ds_backend == "s3" ? {
20+
encrypt = local.backend.encrypt
21+
bucket = local.backend.bucket
22+
key = local.backend.key
23+
dynamodb_table = local.backend.dynamodb_table
24+
region = local.backend.region
25+
26+
# NOTE: component types
27+
# Privileged components are those that require elevated (root-level) permissions to provision and access their remote state.
28+
# For example: `tfstate-backend`, `account`, `account-map`, `account-settings`, `iam-primary`.
29+
# Privileged components are usually provisioned during cold-start (when we don't have any IAM roles provisioned yet) by using an admin user credentials.
30+
# To access the remote state of privileged components, the caller needs to have permissions to access the backend and the remote state without assuming roles.
31+
# Regular components, on the other hand, don't require root-level permissions and are provisioned and their remote state is accessed by assuming IAM roles (or using profiles).
32+
# For example: `vpc`, `eks`, `rds`
33+
34+
# NOTE: global `backend` config
35+
# The global `backend` config should be declared in a global YAML stack config file (e.g. `globals.yaml`)
36+
# where all stacks can import it and have access to it (note that the global `backend` config is organization-wide and will not change after cold-start).
37+
# The global `backend` config in the global config file should always have the `role_arn` or `profile` specified (added after the cold-start).
38+
39+
# NOTE: components `backend` config
40+
# The `backend` portion for each individual component should be declared in a catalog file (e.g. `stacks/catalog/<component>.yaml`)
41+
# along with all the default values for a component.
42+
# The `privileged` attribute should always be declared in the `backend` portion for each individual component in the catalog.
43+
# Top-level stacks where a component is provisioned import the component's catalog (the default values and the component's backend config portion) and can override the default values.
44+
45+
# NOTE: `cold-start`
46+
# During cold-start we don't have any IAM roles provisioned yet, so we use an admin user credentials to provision the privileged components.
47+
# The `privileged` attribute for the privileged components should be set to `true` in the components' catalog,
48+
# and the privileged components should be provisioned using an admin user credentials.
49+
50+
# NOTE: after `cold-start`
51+
# After the privileged components (including the primary IAM roles) are provisioned, we update the global `backend` config in the global config file
52+
# to add the IAM role or profile to access the backend (after this, the global `backend` config should never change).
53+
# For some privileged components we can change the `privileged` attribute in the YAML config from `true` to `false`
54+
# to allow the regular components to access their remote state (e.g. we set the `privileged` attribute to `false` in the `account-map` component
55+
# since we use `account-map` in almost all regular components.
56+
# For each regular component, set the `privileged` attribute to `false` in the components' portion of `backend` config (in `stacks/catalog/<component>.yaml`)
57+
58+
# Advantages:
59+
# The global `backend` config is specified just once in the global config file, IAM role or profile is added to it after the cold start,
60+
# and after that the global `backend` config never changed.
61+
# We can make a component privileged or not any time by just updating its `privileged` attribute in the component's catalog file.
62+
# We can change a component's `backend` portion any time without touching/affection the backend configs of all other components (e.g. when we add a new
63+
# component, we don't touch the `globals.yaml` file at all, and we don't update the component's `role_arn` and `profile` settings).
64+
65+
# Use the role to access the remote state if the component is not privileged and `role_arn` is specified
66+
role_arn = !coalesce(try(local.backend.privileged, null), var.privileged) && contains(keys(local.backend), "role_arn") ? local.backend.role_arn : null
67+
68+
# Use the profile to access the remote state if the component is not privileged and `profile` is specified
69+
profile = !coalesce(try(local.backend.privileged, null), var.privileged) && contains(keys(local.backend), "profile") ? local.backend.profile : null
70+
71+
workspace_key_prefix = local.workspace_key_prefix
72+
} : null
73+
} # ds_configurations
74+
75+
76+
}
77+
78+
# Due to issues like
79+
# - https://github.com/hashicorp/terraform/issues/32023
80+
# - https://github.com/hashicorp/terraform/issues/27849
81+
# we want to avoid using `count` to enable or disable the data source,
82+
# so instead we use a dummy remote state (a local file) when otherwise
83+
# we would disable the data source via `count = 0`.
84+
data "terraform_remote_state" "data_source" {
85+
backend = local.ds_backend
86+
workspace = local.ds_workspace
87+
config = local.ds_configurations[local.ds_backend]
88+
defaults = var.defaults
89+
}

modules/remote-state/main.tf

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ locals {
3434
remote_state_enabled = !var.bypass
3535

3636
remote_states = {
37-
s3 = data.terraform_remote_state.s3
38-
remote = data.terraform_remote_state.remote
39-
bypass = [{ outputs = var.defaults }]
40-
static = [{ outputs = local.backend }]
37+
# s3 = data.terraform_remote_state.s3
38+
# remote = data.terraform_remote_state.remote
39+
data_source = try(data.terraform_remote_state.data_source.outputs, var.defaults)
40+
bypass = var.defaults
41+
static = local.backend
4142
}
4243

4344
remote_state_backend_key = var.bypass ? "bypass" : local.backend_type
4445
computed_remote_state_backend_key = try(length(local.remote_states[local.remote_state_backend_key]), 0) > 0 ? local.remote_state_backend_key : "bypass"
4546

46-
outputs = local.remote_states[local.computed_remote_state_backend_key][0].outputs
47+
outputs = local.remote_states[local.computed_remote_state_backend_key]
4748
}

modules/remote-state/outputs.tf

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ output "backend" {
99
}
1010

1111
output "s3_workspace_name" {
12-
value = local.s3_workspace
13-
description = "Terraform workspace name for the component s3 backend"
12+
value = local.ds_backend == "s3" ? local.remote_workspace : null
13+
description = "(DEPRECATED: use `workspace_name` instead): Terraform workspace name for the component s3 backend"
1414
}
1515

1616
output "remote_workspace_name" {
17+
value = local.ds_backend == "remote" ? local.remote_workspace : null
18+
description = "(DEPRECATED: use `workspace_name` instead): Terraform workspace name for the component remote backend"
19+
}
20+
21+
output "workspace_name" {
1722
value = local.remote_workspace
18-
description = "Terraform workspace name for the component remote backend"
23+
description = "Terraform workspace name from which to retrieve the Terraform state"
1924
}
2025

2126
output "outputs" {

modules/remote-state/remote.tf

Lines changed: 0 additions & 21 deletions
This file was deleted.

modules/remote-state/s3.tf

Lines changed: 0 additions & 84 deletions
This file was deleted.

modules/remote-state/variables.tf

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,3 @@ variable "atmos_base_path" {
5656
description = "atmos base path to components and stacks"
5757
default = null
5858
}
59-
60-
variable "backend_type" {
61-
type = string
62-
# Due to Terraform [issue #32023](https://github.com/hashicorp/terraform/issues/32023),
63-
# we cannot reliably get the backend type from the stack configuration, even when
64-
# the stack has it. So we need to pass it in as a variable.
65-
description = <<-EOF
66-
Set to "auto" to get the backend type from the stack configuration.
67-
Unfortunately, the "auto" setting causes Terraform [issue #32023](https://github.com/hashicorp/terraform/issues/32023).
68-
However, please continue to configure the backend type in the stack configuration,
69-
because when the Terraform issue is fixed, the default will be quietly changed to "auto".
70-
EOF
71-
default = "s3"
72-
}

0 commit comments

Comments
 (0)