generated from masterpointio/terraform-module-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.tf
322 lines (290 loc) · 13.4 KB
/
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# This Terraform code automates the creation and management of Spacelift stacks based on the structure
# and configurations defined in the Git repository, default Stack values and additional input variables.
# It primarily relies on dynamic local expressions to generate configurations based on the
# input variables and Git structure so it can be consumed by Spacelift resources.
# This module can also manage the automation stack itself, but it should be bootstrapped manually.
#
# It handles the following:
#
# 1. Stack Configurations (see ## Stack Configurations)
# Reads the Spacelift stack configurations strictly based on the root modules structure in Git and file names.
# These are the configurations required to be set for a stack, e.g. project_root, terraform_workspace, root_module.
#
# 2. Common Stack configurations (see ## Common Stack configurations)
# Some configurations are equal across the whole root module, and can be set it on a root module level:
# * Space IDs: in the majority of cases all the workspaces in a root module belong to the same Spacelift space, so
# we allow setting a "global" space_id for all stacks on a root module level.
# * Autodeploy: if all the stacks in a root module should be autodeployed.
# * Administrative: if all the stacks in a root module are administrative, e.g stacks that manage Spacelift resources.
#
# 3. Labels (see ## Labels)
# Generates labels for the stacks based on administrative, dependency, and folder information.
#
# Syntax note:
# The local expression started with an underscore `_` is used to store intermediate values
# that are not directly used in the resource creation.
locals {
_all_stack_files = fileset("${path.root}/${var.root_modules_path}/*/stacks", "*.yaml")
_all_root_modules = distinct([for file in local._all_stack_files : dirname(replace(replace(file, "../", ""), "stacks/", ""))])
enabled_root_modules = var.all_root_modules_enabled ? local._all_root_modules : var.enabled_root_modules
# Read and decode Stack YAML files from the root directory
# Example:
# {
# "random-pet" = {
# "common.yaml" = {
# "stack_settings" = {
# "description" = "This stack generates random pet names"
# "manage_state" = true
# }
# "tfvars" = {
# "enabled" = false
# }
# }
# "example.yaml" = {
# "stack_settings" = {
# "manage_state" = true
# }
# "tfvars" = {
# "enabled" = true
# }
# }
# }
# }
_root_module_yaml_decoded = {
for module in local.enabled_root_modules : module => {
for yaml_file in fileset("${path.root}/${var.root_modules_path}/${module}/stacks", "*.yaml") :
yaml_file => yamldecode(file("${path.root}/${var.root_modules_path}/${module}/stacks/${yaml_file}"))
}
}
## Common Stack configurations
# Retrieve common Stack configurations for each root module
# Example:
# {
# "random-pet" = {
# "stack_settings" = {
# "description" = "This stack generates random pet names"
# "manage_state" = true
# }
# "tfvars" = {
# "enabled" = false
# }
# }
# }
_common_configs = {
for module, files in local._root_module_yaml_decoded : module => lookup(files, var.common_config_file, {})
}
## Stack Configurations
# Merge all Stack configurations from the root modules into a single map, and filter out the common config.
# Example:
# {
# "random-pet-example" = {
# "project_root" = "examples/complete/components/random-pet"
# "root_module" = "random-pet"
# "stack_settings" = {
# "manage_state" = true
# }
# "terraform_workspace" = "example"
# "tfvars" = {
# "enabled" = true
# }
# }
# }
_root_module_stack_configs = merge([for module, files in local._root_module_yaml_decoded : {
for file, content in files : "${module}-${trimsuffix(file, ".yaml")}" =>
merge(
{
"project_root" = replace(format("%s/%s", var.root_modules_path, module), "../", "")
"root_module" = module,
"terraform_workspace" = trimsuffix(file, ".yaml"),
},
content
) if file != var.common_config_file
}
]...)
# Get the configs for each stack, merged with the common configurations
# Example:
# {
# "random-pet-example" = {
# "project_root" = "examples/complete/components/random-pet"
# "root_module" = "random-pet"
# "stack_settings" = {
# "manage_state" = true
# }
# "terraform_workspace" = "example"
# "tfvars" = {
# "enabled" = false
# }
# }
# }
configs = {
for key, value in module.deep : key => value.merged
}
# Get the Stacks configs, this is just to improve code readability
# Example:
# {
# "random-pet-example" = {
# "manage_state" = true
# }
# }
stack_configs = {
for key, value in local.configs : key => value.stack_settings
}
# Get the list of all stack names
stacks = toset(keys(local.stack_configs))
## Labels
# Сreates a map of administrative labels for each stack that has the administrative property set to true.
# Example:
# {
# "spacelift-automation-mp-main" = [
# "administrative",
# ]
# "spacelift-policies-notify-tf-completed" = [
# "administrative",
# ]
# }
_administrative_labels = {
for stack, configs in local.stack_configs : stack => ["administrative"] if tobool(try(configs.administrative, false)) == true
}
# Creates a map of `depends-on` labels for each stack based on the root module level dependency configuration.
# Example:
# {
# "random-pet-example" = [
# "depends-on:spacelift-automation-default",
# ]
# }
_dependency_labels = {
for stack in local.stacks : stack => [
"depends-on:spacelift-automation-${terraform.workspace}"
]
}
# Creates a map of folder labels for each stack based on Git structure for a proper grouping stacks in Spacelift UI.
# https://docs.spacelift.io/concepts/stack/organizing-stacks#label-based-folders
# Example:
# {
# "random-pet-example" = [
# "folder:random-pet/example",
# ]
# }
_folder_labels = {
for stack in local.stacks : stack => [
"folder:${local.configs[stack].root_module}/${local.configs[stack].terraform_workspace}"
]
}
# Merge all the labels into a single map for each stack.
# Example:
# {
# "random-pet-example" = tolist([
# "folder:random-pet/example",
# "depends-on:spacelift-automation-default",
# ])
# }
labels = {
for stack in local.stacks :
stack => compact(flatten([
lookup(local._administrative_labels, stack, []),
lookup(local._folder_labels, stack, []),
lookup(local._dependency_labels, stack, []),
try(local.stack_configs[stack].labels, []),
]))
}
# Merge all before_init steps into a single map for each stack.
before_init = {
for stack in local.stacks : stack => compact(concat(
var.before_init,
try(local.stack_configs[stack].before_init, []),
# This command is required for each stack.
# It copies the tfvars file from the stack's workspace to the root module's directory
# and renames it to `spacelift.auto.tfvars` to automatically load variable definitions for each run/task.
["cp tfvars/${local.configs[stack].terraform_workspace}.tfvars spacelift.auto.tfvars"],
)) if try(local.configs[stack].tfvars.enabled, true)
}
}
# Perform deep merge for common configurations and stack configurations
module "deep" {
source = "cloudposse/config/yaml//modules/deepmerge"
version = "1.0.2"
for_each = local._root_module_stack_configs
# Stack configuration will take precedence and overwrite the conflicting value from the common configuration (if any)
maps = [local._common_configs[each.value.root_module], each.value]
}
resource "spacelift_stack" "default" {
for_each = local.stacks
space_id = coalesce(try(local.stack_configs[each.key].space_id, null), var.space_id)
name = each.key
administrative = coalesce(try(local.stack_configs[each.key].administrative, null), var.administrative)
after_apply = compact(concat(try(local.stack_configs[each.key].after_apply, []), var.after_apply))
after_destroy = compact(concat(try(local.stack_configs[each.key].after_destroy, []), var.after_destroy))
after_init = compact(concat(try(local.stack_configs[each.key].after_init, []), var.after_init))
after_perform = compact(concat(try(local.stack_configs[each.key].after_perform, []), var.after_perform))
after_plan = compact(concat(try(local.stack_configs[each.key].after_plan, []), var.after_plan))
autodeploy = coalesce(try(local.stack_configs[each.key].autodeploy, null), var.autodeploy)
autoretry = try(local.stack_configs[each.key].autoretry, var.autoretry)
before_apply = compact(coalesce(try(local.stack_configs[each.key].before_apply, []), var.before_apply))
before_destroy = compact(coalesce(try(local.stack_configs[each.key].before_destroy, []), var.before_destroy))
before_init = compact(coalesce(try(local.before_init[each.key], []), var.before_init))
before_perform = compact(coalesce(try(local.stack_configs[each.key].before_perform, []), var.before_perform))
before_plan = compact(coalesce(try(local.stack_configs[each.key].before_plan, []), var.before_plan))
description = coalesce(try(local.stack_configs[each.key].description, null), var.description)
repository = try(local.stack_configs[each.key].repository, var.repository)
branch = try(local.stack_configs[each.key].branch, var.branch)
project_root = local.configs[each.key].project_root
manage_state = try(local.stack_configs[each.key].manage_state, var.manage_state)
labels = local.labels[each.key]
enable_local_preview = try(local.stack_configs[each.key].enable_local_preview, var.enable_local_preview)
terraform_smart_sanitization = try(local.stack_configs[each.key].terraform_smart_sanitization, var.terraform_smart_sanitization)
terraform_version = try(local.stack_configs[each.key].terraform_version, var.terraform_version)
terraform_workflow_tool = var.terraform_workflow_tool
terraform_workspace = local.configs[each.key].terraform_workspace
protect_from_deletion = try(local.stack_configs[each.key].protect_from_deletion, var.protect_from_deletion)
worker_pool_id = try(local.stack_configs[each.key].worker_pool_id, var.worker_pool_id)
dynamic "github_enterprise" {
for_each = var.github_enterprise != null ? [var.github_enterprise] : []
content {
namespace = github_enterprise.value["namespace"]
}
}
}
# The Spacelift Destructor is a feature designed to automatically clean up the resources no longer managed by our IaC.
# Don't toggle the creation/destruction of this resource with var.destructor_enabled,
# as it will delete all resources in the stack when toggled from 'true' to 'false'.
# Use the 'deactivated' attribute to disable the stack destructor functionality instead.
# https://github.com/spacelift-io/terraform-provider-spacelift/blob/master/spacelift/resource_stack_destructor.go
resource "spacelift_stack_destructor" "default" {
for_each = local.stacks
stack_id = spacelift_stack.default[each.key].id
deactivated = !try(local.stack_configs[each.key].destructor_enabled, var.destructor_enabled)
# `depends_on` should be used to make sure that all necessary resources (environment variables, roles, integrations, etc.)
# are still in place when the destruction run is executed.
# See https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs/resources/stack_destructor
depends_on = [
spacelift_drift_detection.default,
spacelift_aws_integration_attachment.default
]
}
resource "spacelift_aws_integration_attachment" "default" {
for_each = {
for stack, configs in local.stack_configs : stack => configs
if try(configs.aws_integration_enabled, var.aws_integration_enabled)
}
integration_id = try(local.stack_configs[each.key].aws_integration_id, var.aws_integration_id)
stack_id = spacelift_stack.default[each.key].id
read = var.aws_integration_attachment_read
write = var.aws_integration_attachment_write
}
resource "spacelift_drift_detection" "default" {
for_each = {
for stack, configs in local.stack_configs : stack => configs
if try(configs.drift_detection_enabled, var.drift_detection_enabled)
}
stack_id = spacelift_stack.default[each.key].id
ignore_state = try(local.stack_configs[each.key].drift_detection_ignore_state, var.drift_detection_ignore_state)
reconcile = try(local.stack_configs[each.key].drift_detection_reconcile, var.drift_detection_reconcile)
schedule = try(local.stack_configs[each.key].drift_detection_schedule, var.drift_detection_schedule)
timezone = try(local.stack_configs[each.key].drift_detection_timezone, var.drift_detection_timezone)
lifecycle {
precondition {
condition = can(regex("^([0-9,\\-\\*]+\\s+){4}[0-9,\\-\\*]+$", try(local.stack_configs[each.key].drift_detection_schedule, var.drift_detection_schedule)))
error_message = "Invalid cron schedule format for drift detection"
}
}
}