diff --git a/README.md b/README.md
index fea3fd9..050959b 100644
--- a/README.md
+++ b/README.md
@@ -658,7 +658,7 @@ Available targets:
| Name | Version |
|------|---------|
-| terraform | >= 0.12.26 |
+| terraform | >= 0.13.0 |
## Providers
@@ -670,12 +670,14 @@ No provider.
|------|-------------|------|---------|:--------:|
| additional\_tag\_map | Additional tags for appending to tags\_as\_list\_of\_maps. Not added to `tags`. | `map(string)` | `{}` | no |
| attributes | Additional attributes (e.g. `1`) | `list(string)` | `[]` | no |
-| context | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. |
object({
enabled = bool
namespace = string
environment = string
stage = string
name = string
delimiter = string
attributes = list(string)
tags = map(string)
additional_tag_map = map(string)
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
})
| {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_order": [],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {}
}
| no |
+| context | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | object({
enabled = bool
namespace = string
environment = string
stage = string
name = string
delimiter = string
attributes = list(string)
tags = map(string)
additional_tag_map = map(string)
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
label_key_case = string
label_value_case = string
})
| {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {}
}
| no |
| delimiter | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| enabled | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| environment | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
| id\_length\_limit | Limit `id` to this many characters.
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`. | `number` | `null` | no |
+| label\_key\_case | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
| label\_order | The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
+| label\_value\_case | The letter case of output label values (also used in `tags` and `id`).
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Default value: `lower`. | `string` | `null` | no |
| name | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no |
| namespace | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
| regex\_replace\_chars | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
@@ -794,7 +796,7 @@ In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow.
## Copyright
-Copyright © 2017-2020 [Cloud Posse, LLC](https://cpco.io/copyright)
+Copyright © 2017-2021 [Cloud Posse, LLC](https://cpco.io/copyright)
diff --git a/docs/terraform.md b/docs/terraform.md
index 7fd57a4..23ee90f 100644
--- a/docs/terraform.md
+++ b/docs/terraform.md
@@ -3,7 +3,7 @@
| Name | Version |
|------|---------|
-| terraform | >= 0.12.26 |
+| terraform | >= 0.13.0 |
## Providers
@@ -15,12 +15,14 @@ No provider.
|------|-------------|------|---------|:--------:|
| additional\_tag\_map | Additional tags for appending to tags\_as\_list\_of\_maps. Not added to `tags`. | `map(string)` | `{}` | no |
| attributes | Additional attributes (e.g. `1`) | `list(string)` | `[]` | no |
-| context | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | object({
enabled = bool
namespace = string
environment = string
stage = string
name = string
delimiter = string
attributes = list(string)
tags = map(string)
additional_tag_map = map(string)
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
})
| {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_order": [],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {}
}
| no |
+| context | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | object({
enabled = bool
namespace = string
environment = string
stage = string
name = string
delimiter = string
attributes = list(string)
tags = map(string)
additional_tag_map = map(string)
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
label_key_case = string
label_value_case = string
})
| {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {}
}
| no |
| delimiter | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| enabled | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| environment | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
| id\_length\_limit | Limit `id` to this many characters.
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`. | `number` | `null` | no |
+| label\_key\_case | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
| label\_order | The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
+| label\_value\_case | The letter case of output label values (also used in `tags` and `id`).
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Default value: `lower`. | `string` | `null` | no |
| name | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no |
| namespace | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
| regex\_replace\_chars | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
diff --git a/examples/autoscalinggroup/context.tf b/examples/autoscalinggroup/context.tf
index cdc8e73..b97f05f 100644
--- a/examples/autoscalinggroup/context.tf
+++ b/examples/autoscalinggroup/context.tf
@@ -58,6 +58,8 @@ variable "context" {
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
+ label_key_case = string
+ label_value_case = string
})
default = {
enabled = true
@@ -72,6 +74,8 @@ variable "context" {
regex_replace_chars = null
label_order = []
id_length_limit = null
+ label_key_case = null
+ label_value_case = null
}
description = <<-EOT
Single object for setting entire context at once.
@@ -80,6 +84,16 @@ variable "context" {
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged.
EOT
+
+ validation {
+ condition = var.context["label_key_case"] == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = var.context["label_value_case"] == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
}
variable "enabled" {
@@ -169,4 +183,34 @@ variable "id_length_limit" {
EOT
}
+variable "label_key_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+ Possible values: `lower`, `title`, `upper`.
+ Default value: `title`.
+ EOT
+
+ validation {
+ condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+}
+
+variable "label_value_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of output label values (also used in `tags` and `id`).
+ Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+ Default value: `lower`.
+ EOT
+
+ validation {
+ condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
+
#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/autoscalinggroup/versions.tf b/examples/autoscalinggroup/versions.tf
index f94b61f..450c502 100644
--- a/examples/autoscalinggroup/versions.tf
+++ b/examples/autoscalinggroup/versions.tf
@@ -1,3 +1,3 @@
terraform {
- required_version = ">= 0.12.0"
+ required_version = ">= 0.13.0"
}
diff --git a/examples/complete/context.tf b/examples/complete/context.tf
index cdc8e73..b97f05f 100644
--- a/examples/complete/context.tf
+++ b/examples/complete/context.tf
@@ -58,6 +58,8 @@ variable "context" {
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
+ label_key_case = string
+ label_value_case = string
})
default = {
enabled = true
@@ -72,6 +74,8 @@ variable "context" {
regex_replace_chars = null
label_order = []
id_length_limit = null
+ label_key_case = null
+ label_value_case = null
}
description = <<-EOT
Single object for setting entire context at once.
@@ -80,6 +84,16 @@ variable "context" {
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged.
EOT
+
+ validation {
+ condition = var.context["label_key_case"] == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = var.context["label_value_case"] == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
}
variable "enabled" {
@@ -169,4 +183,34 @@ variable "id_length_limit" {
EOT
}
+variable "label_key_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+ Possible values: `lower`, `title`, `upper`.
+ Default value: `title`.
+ EOT
+
+ validation {
+ condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+}
+
+variable "label_value_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of output label values (also used in `tags` and `id`).
+ Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+ Default value: `lower`.
+ EOT
+
+ validation {
+ condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
+
#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/complete/label8d.tf b/examples/complete/label8d.tf
new file mode 100644
index 0000000..4252419
--- /dev/null
+++ b/examples/complete/label8d.tf
@@ -0,0 +1,44 @@
+module "label8d" {
+ source = "../../"
+
+ enabled = true
+ namespace = "eg"
+ environment = "demo"
+ name = "blue"
+ attributes = ["cluster"]
+ delimiter = "-"
+
+ tags = {
+ "kubernetes.io/cluster/" = "shared"
+ }
+}
+
+module "label8d_context" {
+ source = "../../"
+
+ context = module.label8d.context
+}
+
+output "label8d_context_id" {
+ value = module.label8d_context.id
+}
+
+output "label8d_context_context" {
+ value = module.label8d_context.context
+}
+
+output "label8d_context_tags" {
+ value = module.label8d_context.tags
+}
+
+output "label8d_id" {
+ value = module.label8d.id
+}
+
+output "label8d_context" {
+ value = module.label8d.context
+}
+
+output "label8d_tags" {
+ value = module.label8d.tags
+}
diff --git a/examples/complete/label8dcd.tf b/examples/complete/label8dcd.tf
new file mode 100644
index 0000000..ccdae21
--- /dev/null
+++ b/examples/complete/label8dcd.tf
@@ -0,0 +1,24 @@
+module "label8dcd" {
+ source = "../../"
+
+ enabled = true
+ namespace = "eg"
+ environment = "demo"
+ name = "blue"
+ attributes = ["cluster"]
+ delimiter = "x"
+}
+
+module "label8dcd_context" {
+ source = "../../"
+
+ context = module.label8dcd.context
+}
+
+output "label8dcd_context_id" {
+ value = module.label8dcd_context.id
+}
+
+output "label8dcd_id" {
+ value = module.label8dcd.id
+}
diff --git a/examples/complete/label8dnd.tf b/examples/complete/label8dnd.tf
new file mode 100644
index 0000000..2edb378
--- /dev/null
+++ b/examples/complete/label8dnd.tf
@@ -0,0 +1,24 @@
+module "label8dnd" {
+ source = "../../"
+
+ enabled = true
+ namespace = "eg"
+ environment = "demo"
+ name = "blue"
+ attributes = ["cluster"]
+ delimiter = ""
+}
+
+module "label8dnd_context" {
+ source = "../../"
+
+ context = module.label8dnd.context
+}
+
+output "label8dnd_context_id" {
+ value = module.label8dnd_context.id
+}
+
+output "label8dnd_id" {
+ value = module.label8dnd.id
+}
diff --git a/examples/complete/label8l.tf b/examples/complete/label8l.tf
new file mode 100644
index 0000000..7462f9e
--- /dev/null
+++ b/examples/complete/label8l.tf
@@ -0,0 +1,46 @@
+module "label8l" {
+ source = "../../"
+ enabled = true
+ namespace = "eg"
+ environment = "demo"
+ name = "blue"
+ attributes = ["cluster"]
+ delimiter = "-"
+ label_key_case = "lower"
+ label_value_case = "lower"
+
+ tags = {
+ "kubernetes.io/cluster/" = "shared"
+ "upperTEST" = "testUPPER"
+ }
+}
+
+module "label8l_context" {
+ source = "../../"
+
+ context = module.label8l.context
+}
+
+output "label8l_context_id" {
+ value = module.label8l_context.id
+}
+
+output "label8l_context_context" {
+ value = module.label8l_context.context
+}
+
+output "label8l_context_tags" {
+ value = module.label8l_context.tags
+}
+
+output "label8l_id" {
+ value = module.label8l.id
+}
+
+output "label8l_context" {
+ value = module.label8l.context
+}
+
+output "label8l_tags" {
+ value = module.label8l.tags
+}
diff --git a/examples/complete/label8n.tf b/examples/complete/label8n.tf
new file mode 100644
index 0000000..fd4ad57
--- /dev/null
+++ b/examples/complete/label8n.tf
@@ -0,0 +1,45 @@
+module "label8n" {
+ source = "../../"
+
+ enabled = true
+ namespace = "EG"
+ environment = "demo"
+ name = "blue"
+ attributes = ["eks", "ClusteR"]
+ delimiter = "-"
+ label_value_case = "none"
+
+ tags = {
+ "kubernetes.io/cluster/" = "shared"
+ }
+}
+
+module "label8n_context" {
+ source = "../../"
+
+ context = module.label8n.context
+}
+
+output "label8n_context_id" {
+ value = module.label8n_context.id
+}
+
+output "label8n_context_context" {
+ value = module.label8n_context.context
+}
+
+output "label8n_context_tags" {
+ value = module.label8n_context.tags
+}
+
+output "label8n_id" {
+ value = module.label8n.id
+}
+
+output "label8n_context" {
+ value = module.label8n.context
+}
+
+output "label8n_tags" {
+ value = module.label8n.tags
+}
diff --git a/examples/complete/label8t.tf b/examples/complete/label8t.tf
new file mode 100644
index 0000000..cb54c7a
--- /dev/null
+++ b/examples/complete/label8t.tf
@@ -0,0 +1,45 @@
+module "label8t" {
+ source = "../../"
+ enabled = true
+ namespace = "eg"
+ environment = "demo"
+ name = "blue"
+ attributes = ["EKS", "cluster"]
+ delimiter = "-"
+ label_key_case = "title"
+ label_value_case = "title"
+
+ tags = {
+ "kubernetes.io/cluster/" = "shared"
+ }
+}
+
+module "label8t_context" {
+ source = "../../"
+
+ context = module.label8t.context
+}
+
+output "label8t_context_id" {
+ value = module.label8t_context.id
+}
+
+output "label8t_context_context" {
+ value = module.label8t_context.context
+}
+
+output "label8t_context_tags" {
+ value = module.label8t_context.tags
+}
+
+output "label8t_id" {
+ value = module.label8t.id
+}
+
+output "label8t_context" {
+ value = module.label8t.context
+}
+
+output "label8t_tags" {
+ value = module.label8t.tags
+}
diff --git a/examples/complete/label8u.tf b/examples/complete/label8u.tf
new file mode 100644
index 0000000..499535b
--- /dev/null
+++ b/examples/complete/label8u.tf
@@ -0,0 +1,50 @@
+module "label8u" {
+ source = "../../"
+ enabled = true
+ namespace = "eg"
+ environment = "demo"
+ name = "blue"
+ attributes = ["cluster"]
+ delimiter = "-"
+ label_key_case = "upper"
+ label_value_case = "upper"
+
+ tags = {
+ "kubernetes.io/cluster/" = "shared"
+ }
+}
+
+module "label8u_context" {
+ source = "../../"
+
+ context = module.label8u.context
+}
+
+output "label8u_context_id" {
+ value = module.label8u_context.id
+}
+
+output "label8u_context_context" {
+ value = module.label8u_context.context
+}
+
+// debug
+output "label8u_context_normalized_context" {
+ value = module.label8u_context.normalized_context
+}
+
+output "label8u_context_tags" {
+ value = module.label8u_context.tags
+}
+
+output "label8u_id" {
+ value = module.label8u.id
+}
+
+output "label8u_context" {
+ value = module.label8u.context
+}
+
+output "label8u_tags" {
+ value = module.label8u.tags
+}
diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf
index f94b61f..450c502 100644
--- a/examples/complete/versions.tf
+++ b/examples/complete/versions.tf
@@ -1,3 +1,3 @@
terraform {
- required_version = ">= 0.12.0"
+ required_version = ">= 0.13.0"
}
diff --git a/exports/context.tf b/exports/context.tf
index f5f2797..ff90b1c 100644
--- a/exports/context.tf
+++ b/exports/context.tf
@@ -20,7 +20,7 @@
module "this" {
source = "cloudposse/label/null"
- version = "0.22.1" // requires Terraform >= 0.12.26
+ version = "0.23.0" // requires Terraform >= 0.13.0
enabled = var.enabled
namespace = var.namespace
@@ -54,6 +54,8 @@ variable "context" {
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
+ label_key_case = string
+ label_value_case = string
})
default = {
enabled = true
@@ -68,6 +70,8 @@ variable "context" {
regex_replace_chars = null
label_order = []
id_length_limit = null
+ label_key_case = null
+ label_value_case = null
}
description = <<-EOT
Single object for setting entire context at once.
@@ -76,6 +80,16 @@ variable "context" {
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged.
EOT
+
+ validation {
+ condition = var.context["label_key_case"] == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = var.context["label_value_case"] == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
}
variable "enabled" {
@@ -165,4 +179,33 @@ variable "id_length_limit" {
EOT
}
+variable "label_key_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+ Possible values: `lower`, `title`, `upper`.
+ Default value: `title`.
+ EOT
+
+ validation {
+ condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+}
+
+variable "label_value_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of output label values (also used in `tags` and `id`).
+ Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+ Default value: `lower`.
+ EOT
+
+ validation {
+ condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/main.tf b/main.tf
index c9c73b8..cd5a6f3 100644
--- a/main.tf
+++ b/main.tf
@@ -5,15 +5,14 @@ locals {
regex_replace_chars = "/[^-a-zA-Z0-9]/"
delimiter = "-"
replacement = ""
- # The `sentinel` should match the `regex_replace_chars`, so it will be replaced with the `replacement` value
- sentinel = "\t"
- id_length_limit = 0
- id_hash_length = 5
+ id_length_limit = 0
+ id_hash_length = 5
+ label_value_case = "lower"
+ label_key_case = "title"
}
- # So far, we have decided not to allow overriding replacement, sentinel, or id_hash_length
+ # So far, we have decided not to allow overriding replacement or id_hash_length
replacement = local.defaults.replacement
- sentinel = local.defaults.sentinel
id_hash_length = local.defaults.id_hash_length
# The values provided by variables supersede the values inherited from the context object,
@@ -35,24 +34,44 @@ locals {
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 ? var.context.label_key_case : var.label_key_case
+ label_value_case = var.label_value_case == null ? var.context.label_value_case : var.label_value_case
}
enabled = local.input.enabled
regex_replace_chars = coalesce(local.input.regex_replace_chars, local.defaults.regex_replace_chars)
- name = lower(replace(coalesce(local.input.name, local.sentinel), local.regex_replace_chars, local.replacement))
- namespace = lower(replace(coalesce(local.input.namespace, local.sentinel), local.regex_replace_chars, local.replacement))
- environment = lower(replace(coalesce(local.input.environment, local.sentinel), local.regex_replace_chars, local.replacement))
- stage = lower(replace(coalesce(local.input.stage, local.sentinel), local.regex_replace_chars, local.replacement))
- 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
+ # string_label_names are names of inputs that are strings (not list of strings) used as labels
+ string_label_names = ["name", "namespace", "environment", "stage"]
+ 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))
+ ]))
- additional_tag_map = merge(var.context.additional_tag_map, var.additional_tag_map)
+ name = local.formatted_labels["name"]
+ namespace = local.formatted_labels["namespace"]
+ environment = local.formatted_labels["environment"]
+ stage = local.formatted_labels["stage"]
- attributes = local.input.attributes
+ 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
+
+ additional_tag_map = merge(var.context.additional_tag_map, var.additional_tag_map)
tags = merge(local.generated_tags, local.input.tags)
@@ -73,26 +92,34 @@ locals {
attributes = local.id_context.attributes
}
- generated_tags = { for l in keys(local.tags_context) : title(l) => local.tags_context[l] if length(local.tags_context[l]) > 0 }
+ generated_tags = {
+ for l in keys(local.tags_context) :
+ 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 = {
name = local.name
namespace = local.namespace
environment = local.environment
stage = local.stage
- attributes = lower(replace(join(local.delimiter, local.attributes), local.regex_replace_chars, local.replacement))
+ 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 = lower(join(local.delimiter, local.labels))
+ 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}"
- id_hash = md5(local.id_full)
+ # Support usages that disallow numeric characters. Would prefer tr 0-9 q-z but Terraform does not support it.
+ 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
@@ -112,6 +139,8 @@ locals {
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
}
}
diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go
index 5fc9341..322bdb3 100644
--- a/test/src/examples_complete_test.go
+++ b/test/src/examples_complete_test.go
@@ -177,4 +177,116 @@ func TestExamplesComplete(t *testing.T) {
label7 := terraform.OutputMap(t, terraformOptions, "label7")
assert.Equal(t, "eg-demo-blue-cluster-nodegroup", label7["id"], "var.attributes should be appended after context.attributes")
+ // Verify that apply with `label_key_case=title`, `label_value_case=lower`, `delimiter=""` returns expected value of id, context id
+ label8dndID := terraform.Output(t, terraformOptions, "label8dnd_id")
+ label8dndContextID := terraform.Output(t, terraformOptions, "label8dnd_context_id")
+ assert.Equal(t, "egdemobluecluster", label8dndID)
+ assert.Equal(t, label8dndID, label8dndContextID, "ID and context ID should be equal")
+
+ // Verify that apply with `label_key_case=title`, `label_value_case=lower`, `delimiter="x"` returns expected value of id, context id
+ label8dcdID := terraform.Output(t, terraformOptions, "label8dcd_id")
+ label8dcdContextID := terraform.Output(t, terraformOptions, "label8dcd_context_id")
+ assert.Equal(t, "egxdemoxbluexcluster", label8dcdID)
+ assert.Equal(t, label8dcdID, label8dcdContextID, "ID and context ID should be equal")
+
+ // Verify that apply with `label_key_case=title` and `label_value_case=lower` returns expected values of id, tags, context tags
+ label8dExpectedTags := map[string]string{
+ "Attributes": "cluster",
+ "Environment": "demo",
+ "Name": "eg-demo-blue-cluster",
+ "Namespace": "eg",
+ "kubernetes.io/cluster/": "shared",
+ }
+
+ label8dID := terraform.Output(t, terraformOptions, "label8d_id")
+ label8dContextID := terraform.Output(t, terraformOptions, "label8d_context_id")
+ assert.Equal(t, "eg-demo-blue-cluster", label8dID)
+ assert.Equal(t, label8dID, label8dContextID, "ID and context ID should be equal")
+
+ label8dTags := terraform.OutputMap(t, terraformOptions, "label8d_tags")
+ label8dContextTags := terraform.OutputMap(t, terraformOptions, "label8d_context_tags")
+
+ assert.Exactly(t, label8dExpectedTags, label8dTags, "generated tags are different from expected")
+ assert.Exactly(t, label8dTags, label8dContextTags, "tags and context tags should be equal")
+
+ // Verify that apply with `label_key_case=lower` and `label_value_case=lower` returns expected values of id, tags, context tags
+ label8lExpectedTags := map[string]string{
+ "attributes": "cluster",
+ "environment": "demo",
+ "name": "eg-demo-blue-cluster",
+ "namespace": "eg",
+ "kubernetes.io/cluster/": "shared",
+ "upperTEST": "testUPPER",
+ }
+
+ label8lID := terraform.Output(t, terraformOptions, "label8l_id")
+ label8lContextID := terraform.Output(t, terraformOptions, "label8l_context_id")
+ assert.Equal(t, "eg-demo-blue-cluster", label8lID)
+ assert.Equal(t, label8lID, label8lContextID, "ID and context ID should be equal")
+
+ label8lTags := terraform.OutputMap(t, terraformOptions, "label8l_tags")
+ label8lContextTags := terraform.OutputMap(t, terraformOptions, "label8l_context_tags")
+
+ assert.Exactly(t, label8lExpectedTags, label8lTags, "generated tags are different from expected")
+ assert.Exactly(t, label8lTags, label8lContextTags, "tags and context tags should be equal")
+
+ // Verify that apply with `label_key_case=title` and `label_value_case=title` returns expected values of id, tags, context tags
+ label8tExpectedTags := map[string]string{
+ "Attributes": "Eks-Cluster",
+ "Environment": "Demo",
+ "Name": "Eg-Demo-Blue-Eks-Cluster",
+ "Namespace": "Eg",
+ "kubernetes.io/cluster/": "shared",
+ }
+
+ label8tID := terraform.Output(t, terraformOptions, "label8t_id")
+ label8tContextID := terraform.Output(t, terraformOptions, "label8t_context_id")
+ assert.Equal(t, "Eg-Demo-Blue-Eks-Cluster", label8tID)
+ assert.Equal(t, label8tID, label8tContextID, "ID and context ID should be equal")
+
+ label8tTags := terraform.OutputMap(t, terraformOptions, "label8t_tags")
+ label8tContextTags := terraform.OutputMap(t, terraformOptions, "label8t_context_tags")
+
+ assert.Exactly(t, label8tExpectedTags, label8tTags, "generated tags are different from expected")
+ assert.Exactly(t, label8tTags, label8tContextTags, "tags and context tags should be equal")
+
+ // Verify that apply with `label_key_case=upper` and `label_value_case=upper` returns expected values of id, tags, context tags
+ label8uExpectedTags := map[string]string{
+ "ATTRIBUTES": "CLUSTER",
+ "ENVIRONMENT": "DEMO",
+ "NAME": "EG-DEMO-BLUE-CLUSTER",
+ "NAMESPACE": "EG",
+ "kubernetes.io/cluster/": "shared",
+ }
+
+ label8uID := terraform.Output(t, terraformOptions, "label8u_id")
+ label8uContextID := terraform.Output(t, terraformOptions, "label8u_context_id")
+ assert.Equal(t, "EG-DEMO-BLUE-CLUSTER", label8uID)
+ assert.Equal(t, label8uID, label8uContextID, "ID and context ID should be equal")
+
+ label8uTags := terraform.OutputMap(t, terraformOptions, "label8u_tags")
+ label8uContextTags := terraform.OutputMap(t, terraformOptions, "label8u_context_tags")
+
+ assert.Exactly(t, label8uExpectedTags, label8uTags, "generated tags are different from expected")
+ assert.Exactly(t, label8uTags, label8uContextTags, "tags and context tags should be equal")
+
+ // Verify that apply with `label_key_case=title` and `label_value_case=none` returns expected values of id, tags, context tags
+ label8nExpectedTags := map[string]string{
+ "Attributes": "eks-ClusteR",
+ "Environment": "demo",
+ "Name": "EG-demo-blue-eks-ClusteR",
+ "Namespace": "EG",
+ "kubernetes.io/cluster/": "shared",
+ }
+
+ label8nID := terraform.Output(t, terraformOptions, "label8n_id")
+ label8nContextID := terraform.Output(t, terraformOptions, "label8n_context_id")
+ assert.Equal(t, "EG-demo-blue-eks-ClusteR", label8nID)
+ assert.Equal(t, label8nID, label8nContextID, "ID and context ID should be equal")
+
+ label8nTags := terraform.OutputMap(t, terraformOptions, "label8n_tags")
+ label8nContextTags := terraform.OutputMap(t, terraformOptions, "label8n_context_tags")
+
+ assert.Exactly(t, label8nExpectedTags, label8nTags, "generated tags are different from expected")
+ assert.Exactly(t, label8nTags, label8nContextTags, "tags and context tags should be equal")
}
diff --git a/test/src/go.sum b/test/src/go.sum
index d69a78e..b05e040 100644
--- a/test/src/go.sum
+++ b/test/src/go.sum
@@ -289,6 +289,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
diff --git a/variables.tf b/variables.tf
index 438c84b..9ede816 100644
--- a/variables.tf
+++ b/variables.tf
@@ -12,6 +12,8 @@ variable "context" {
regex_replace_chars = string
label_order = list(string)
id_length_limit = number
+ label_key_case = string
+ label_value_case = string
})
default = {
enabled = true
@@ -26,6 +28,8 @@ variable "context" {
regex_replace_chars = null
label_order = []
id_length_limit = null
+ label_key_case = null
+ label_value_case = null
}
description = <<-EOT
Single object for setting entire context at once.
@@ -34,6 +38,16 @@ variable "context" {
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged.
EOT
+
+ validation {
+ condition = var.context["label_key_case"] == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = var.context["label_value_case"] == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
}
variable "enabled" {
@@ -122,3 +136,33 @@ variable "id_length_limit" {
Does not affect `id_full`.
EOT
}
+
+variable "label_key_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+ Possible values: `lower`, `title`, `upper`.
+ Default value: `title`.
+ EOT
+
+ validation {
+ condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+}
+
+variable "label_value_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ The letter case of output label values (also used in `tags` and `id`).
+ Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+ Default value: `lower`.
+ EOT
+
+ validation {
+ condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
diff --git a/versions.tf b/versions.tf
index 51de421..450c502 100644
--- a/versions.tf
+++ b/versions.tf
@@ -1,3 +1,3 @@
terraform {
- required_version = ">= 0.12.26"
+ required_version = ">= 0.13.0"
}