-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.tf
183 lines (156 loc) · 9.57 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
// Copyright 2023 Viget Labs, LLC
// Licensed under the Apache License 2.0 (see LICENSE-TF-CONTEXT file for details)
locals {
defaults = {
label_order = ["namespace", "tenant", "stage", "component", "attributes"]
regex_replace_chars = "/[^-a-zA-Z0-9]/"
delimiter = "-"
replacement = ""
id_length_limit = 0
id_hash_length = 5
label_key_case = "title"
label_value_case = "lower"
# The default value of labels_as_tags cannot be included in this
# defaults` map because it creates a circular dependency
}
default_labels_as_tags = keys(local.tags_context)
# Unlike other inputs, the first setting of `labels_as_tags` cannot be later overridden. However,
# we still have to pass the `input` map as the context to the next module. So we need to distinguish
# between the first setting of var.labels_as_tags == null as meaning set the default and do not change
# it later, versus later settings of var.labels_as_tags that should be ignored. So, we make the
# default value in context be "unset", meaning it can be changed, but when it is unset and
# var.labels_as_tags is null, we change it to "default". Once it is set to "default" we will
# not allow it to be changed again, but of course we have to detect "default" and replace it
# with local.default_labels_as_tags when we go to use it.
#
# We do not want to use null as default or unset, because Terraform has issues with
# the value of an object field being null in some places and [] in others.
# We do not want to use [] as default or unset because that is actually a valid setting
# that we want to have override the default.
#
# To determine whether that context.labels_as_tags is not set,
# we have to cover 2 cases: 1) context does not have a labels_as_tags key, 2) it is present and set to ["unset"]
context_labels_as_tags_is_unset = try(contains(var.context.labels_as_tags, "unset"), true)
# So far, we have decided not to allow overriding replacement or id_hash_length
replacement = local.defaults.replacement
id_hash_length = local.defaults.id_hash_length
# The values provided by variables supersede the values inherited from the context object,
# except for tags and attributes which are merged.
input = {
# It would be nice to use coalesce here, but we cannot, because it
# is an error for all the arguments to coalesce to be empty.
enabled = var.enabled == null ? var.context.enabled : var.enabled
namespace = var.namespace == null ? var.context.namespace : var.namespace
tenant = var.tenant == null ? var.context.tenant : var.tenant
stage = var.stage == null ? var.context.stage : var.stage
component = var.component == null ? var.context.component : var.component
delimiter = var.delimiter == null ? var.context.delimiter : var.delimiter
# modules tack on attributes (passed by var) to the end of the list (passed by context)
attributes = compact(distinct(concat(coalesce(var.context.attributes, []), coalesce(var.attributes, []))))
tags = merge(var.context.tags, var.tags)
additional_tag_map = merge(var.context.additional_tag_map, var.additional_tag_map)
label_order = var.label_order == null ? var.context.label_order : var.label_order
regex_replace_chars = var.regex_replace_chars == null ? var.context.regex_replace_chars : var.regex_replace_chars
id_length_limit = var.id_length_limit == null ? var.context.id_length_limit : var.id_length_limit
label_key_case = var.label_key_case == null ? lookup(var.context, "label_key_case", null) : var.label_key_case
label_value_case = var.label_value_case == null ? lookup(var.context, "label_value_case", null) : var.label_value_case
descriptor_formats = merge(lookup(var.context, "descriptor_formats", {}), var.descriptor_formats)
labels_as_tags = local.context_labels_as_tags_is_unset ? var.labels_as_tags : var.context.labels_as_tags
}
enabled = local.input.enabled
regex_replace_chars = coalesce(local.input.regex_replace_chars, local.defaults.regex_replace_chars)
# string_label_names are names of inputs that are strings (not list of strings) used as labels
string_label_names = ["namespace", "tenant", "stage", "component"]
normalized_labels = { for k in local.string_label_names : k =>
local.input[k] == null ? "" : replace(local.input[k], local.regex_replace_chars, local.replacement)
}
normalized_attributes = compact(distinct([for v in local.input.attributes : replace(v, local.regex_replace_chars, local.replacement)]))
formatted_labels = { for k in local.string_label_names : k => local.label_value_case == "none" ? local.normalized_labels[k] :
local.label_value_case == "title" ? title(lower(local.normalized_labels[k])) :
local.label_value_case == "upper" ? upper(local.normalized_labels[k]) : lower(local.normalized_labels[k])
}
attributes = compact(distinct([
for v in local.normalized_attributes : (local.label_value_case == "none" ? v :
local.label_value_case == "title" ? title(lower(v)) :
local.label_value_case == "upper" ? upper(v) : lower(v))
]))
namespace = local.formatted_labels["namespace"]
tenant = local.formatted_labels["tenant"]
stage = local.formatted_labels["stage"]
component = local.formatted_labels["component"]
delimiter = local.input.delimiter == null ? local.defaults.delimiter : local.input.delimiter
label_order = local.input.label_order == null ? local.defaults.label_order : coalescelist(local.input.label_order, local.defaults.label_order)
id_length_limit = local.input.id_length_limit == null ? local.defaults.id_length_limit : local.input.id_length_limit
label_key_case = local.input.label_key_case == null ? local.defaults.label_key_case : local.input.label_key_case
label_value_case = local.input.label_value_case == null ? local.defaults.label_value_case : local.input.label_value_case
# labels_as_tags is an exception to the rule that input vars override context values (see above)
labels_as_tags = contains(local.input.labels_as_tags, "default") ? local.default_labels_as_tags : local.input.labels_as_tags
# Just for standardization and completeness
descriptor_formats = local.input.descriptor_formats
additional_tag_map = merge(var.context.additional_tag_map, var.additional_tag_map)
tags = merge(local.generated_tags, local.input.tags)
tags_as_list_of_maps = flatten([
for key in keys(local.tags) : merge(
{
key = key
value = local.tags[key]
}, local.additional_tag_map)
])
tags_context = {
namespace = local.namespace
tenant = local.tenant
stage = local.stage
component = local.component
name = local.id_full
attributes = local.id_context.attributes
}
generated_tags = {
for l in setintersection(keys(local.tags_context), local.labels_as_tags) :
local.label_key_case == "upper" ? upper(l) : (
local.label_key_case == "lower" ? lower(l) : title(lower(l))
) => local.tags_context[l] if length(local.tags_context[l]) > 0
}
id_context = {
namespace = local.namespace
tenant = local.tenant
stage = local.stage
component = local.component
attributes = join(local.delimiter, local.attributes)
}
labels = [for l in local.label_order : local.id_context[l] if length(local.id_context[l]) > 0]
id_full = join(local.delimiter, local.labels)
# Create a truncated ID if needed
delimiter_length = length(local.delimiter)
# Calculate length of normal part of ID, leaving room for delimiter and hash
id_truncated_length_limit = local.id_length_limit - (local.id_hash_length + local.delimiter_length)
# Truncate the ID and ensure a single (not double) trailing delimiter
id_truncated = local.id_truncated_length_limit <= 0 ? "" : "${trimsuffix(substr(local.id_full, 0, local.id_truncated_length_limit), local.delimiter)}${local.delimiter}"
# Support usages that disallow numeric characters. Would prefer tr 0-9 q-z but Terraform does not support it.
# Probably would have been better to take the hash of only the characters being removed,
# so identical removed strings would produce identical hashes, but it is not worth breaking existing IDs for.
id_hash_plus = "${md5(local.id_full)}qrstuvwxyz"
id_hash_case = local.label_value_case == "title" ? title(local.id_hash_plus) : local.label_value_case == "upper" ? upper(local.id_hash_plus) : local.label_value_case == "lower" ? lower(local.id_hash_plus) : local.id_hash_plus
id_hash = replace(local.id_hash_case, local.regex_replace_chars, local.replacement)
# Create the short ID by adding a hash to the end of the truncated ID
id_short = substr("${local.id_truncated}${local.id_hash}", 0, local.id_length_limit)
id = local.id_length_limit != 0 && length(local.id_full) > local.id_length_limit ? local.id_short : local.id_full
# Context of this label to pass to other label modules
output_context = {
enabled = local.enabled
namespace = local.namespace
tenant = local.tenant
stage = local.stage
component = local.component
delimiter = local.delimiter
attributes = local.attributes
tags = local.tags
additional_tag_map = local.additional_tag_map
label_order = local.label_order
regex_replace_chars = local.regex_replace_chars
id_length_limit = local.id_length_limit
label_key_case = local.label_key_case
label_value_case = local.label_value_case
labels_as_tags = local.labels_as_tags
descriptor_formats = local.descriptor_formats
}
}