diff --git a/.github/workflows/release-branch.yml b/.github/workflows/release-branch.yml index 3f8fe62..b30901e 100644 --- a/.github/workflows/release-branch.yml +++ b/.github/workflows/release-branch.yml @@ -10,6 +10,7 @@ on: - 'docs/**' - 'examples/**' - 'test/**' + - 'README.*' permissions: contents: write diff --git a/.github/workflows/release-published.yml b/.github/workflows/release-published.yml index f86352b..b31232b 100644 --- a/.github/workflows/release-published.yml +++ b/.github/workflows/release-published.yml @@ -11,4 +11,4 @@ permissions: jobs: terraform-module: - uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/release.yml@main + uses: cloudposse/github-actions-workflows-terraform-module/.github/workflows/release-published.yml@main diff --git a/README.md b/README.md index 05ddbb6..8a1a4c7 100644 --- a/README.md +++ b/README.md @@ -147,22 +147,22 @@ Available targets: | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 4.27.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.9.0 | | [local](#requirement\_local) | >= 1.2 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.27.0 | +| [aws](#provider\_aws) | >= 4.9.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [dns](#module\_dns) | cloudposse/route53-alias/aws | 0.13.0 | -| [logs](#module\_logs) | cloudposse/s3-log-storage/aws | 0.26.0 | +| [logs](#module\_logs) | cloudposse/s3-log-storage/aws | 1.4.2 | | [origin\_label](#module\_origin\_label) | cloudposse/label/null | 0.25.0 | | [this](#module\_this) | cloudposse/label/null | 0.25.0 | diff --git a/docs/terraform.md b/docs/terraform.md index e80df26..529251c 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -3,22 +3,22 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 4.27.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.9.0 | | [local](#requirement\_local) | >= 1.2 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.27.0 | +| [aws](#provider\_aws) | >= 4.9.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [dns](#module\_dns) | cloudposse/route53-alias/aws | 0.13.0 | -| [logs](#module\_logs) | cloudposse/s3-log-storage/aws | 0.26.0 | +| [logs](#module\_logs) | cloudposse/s3-log-storage/aws | 1.4.2 | | [origin\_label](#module\_origin\_label) | cloudposse/label/null | 0.25.0 | | [this](#module\_this) | cloudposse/label/null | 0.25.0 | diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index b29221a..65e830b 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.0" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.27.0" + version = ">= 5.0" } local = { source = "hashicorp/local" diff --git a/examples/wordpress/context.tf b/examples/wordpress/context.tf index bae0cf1..5e0ef88 100644 --- a/examples/wordpress/context.tf +++ b/examples/wordpress/context.tf @@ -8,6 +8,8 @@ # Cloud Posse's standard configuration inputs suitable for passing # to Cloud Posse modules. # +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# # Modules should access the whole context as `module.this.context` # to get the input variables with nulls for defaults, # for example `context = module.this.context`, @@ -19,10 +21,12 @@ # module "this" { - source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2" + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 enabled = var.enabled namespace = var.namespace + tenant = var.tenant environment = var.environment stage = var.stage name = var.name @@ -33,6 +37,10 @@ module "this" { label_order = var.label_order regex_replace_chars = var.regex_replace_chars id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags context = var.context } @@ -40,23 +48,11 @@ module "this" { # Copy contents of cloudposse/terraform-null-label/variables.tf here variable "context" { - type = 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 - }) + type = any default = { enabled = true namespace = null + tenant = null environment = null stage = null name = null @@ -67,6 +63,17 @@ variable "context" { regex_replace_chars = null label_order = [] id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] } description = <<-EOT Single object for setting entire context at once. @@ -75,6 +82,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 = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } } variable "enabled" { @@ -86,32 +103,42 @@ variable "enabled" { variable "namespace" { type = string default = null - description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'" + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" } variable "environment" { type = string default = null - description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'" + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" } variable "stage" { type = string default = null - description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'" + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" } variable "name" { type = string default = null - description = "Solution name, e.g. 'app' or 'jenkins'" + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT } variable "delimiter" { type = string default = null description = <<-EOT - Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`. + Delimiter to be used between ID elements. Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. EOT } @@ -119,36 +146,64 @@ variable "delimiter" { variable "attributes" { type = list(string) default = [] - description = "Additional attributes (e.g. `1`)" + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT } variable "tags" { type = map(string) default = {} - description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`" + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT } variable "additional_tag_map" { type = map(string) default = {} - description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`." + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT } variable "label_order" { type = list(string) default = null description = <<-EOT - The naming order of the id output and Name tag. + The order in which the labels (ID elements) appear in the `id`. Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 5 elements, but at least one must be present. - EOT + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT } variable "regex_replace_chars" { type = string default = null description = <<-EOT - Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`. + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. EOT } @@ -157,11 +212,68 @@ variable "id_length_limit" { type = number default = null description = <<-EOT - Limit `id` to this many characters. + Limit `id` to this many characters (minimum 6). Set to `0` for unlimited length. - Set to `null` for default, which is `0`. + Set to `null` for keep the existing setting, which defaults to `0`. Does not affect `id_full`. EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + 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 + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + 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`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT } #### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/wordpress/versions.tf b/examples/wordpress/versions.tf new file mode 100644 index 0000000..ddfcb0e --- /dev/null +++ b/examples/wordpress/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} diff --git a/main.tf b/main.tf index 0cc2f00..8c11095 100644 --- a/main.tf +++ b/main.tf @@ -15,16 +15,30 @@ resource "aws_cloudfront_origin_access_identity" "default" { module "logs" { source = "cloudposse/s3-log-storage/aws" - version = "0.26.0" + version = "1.4.2" enabled = module.this.enabled && var.logging_enabled && length(var.log_bucket_fqdn) == 0 attributes = compact(concat(module.this.attributes, ["origin", "logs"])) + allow_ssl_requests_only = true lifecycle_prefix = var.log_prefix + s3_object_ownership = "BucketOwnerPreferred" standard_transition_days = var.log_standard_transition_days glacier_transition_days = var.log_glacier_transition_days expiration_days = var.log_expiration_days force_destroy = var.log_force_destroy + # See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html + acl = null + grants = [ + { + # Canonical ID for the awslogsdelivery account + id = "c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0" + permissions = ["FULL_CONTROL"] + type = "CanonicalUser" + uri = null + }, + ] + context = module.this.context } @@ -242,6 +256,8 @@ resource "aws_cloudfront_distribution" "default" { } tags = module.this.tags + + depends_on = [module.logs] } module "dns" { diff --git a/versions.tf b/versions.tf index b29221a..20ef6fb 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.0" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.27.0" + version = ">= 4.9.0" } local = { source = "hashicorp/local"