diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index b8f1b8a5..adea23e0 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -71,6 +71,14 @@ jobs: id: minMax uses: clowdhaus/terraform-min-max@v1.0.3 + - name: Install hcledit (for terraform_wrapper_module_for_each hook) + shell: bash + run: | + curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tgz + sudo tar -xzf hcledit.tgz -C /usr/bin/ hcledit + rm -f hcledit.tgz 2> /dev/null + hcledit version + - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} uses: clowdhaus/terraform-composite-actions/pre-commit@v1.3.0 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a010fdd..954c5373 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,9 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.64.0 + rev: v1.71.0 hooks: - id: terraform_fmt + - id: terraform_wrapper_module_for_each - id: terraform_validate - id: terraform_docs args: @@ -23,7 +24,7 @@ repos: - '--args=--only=terraform_standard_module_structure' - '--args=--only=terraform_workspace_remote' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer diff --git a/README.md b/README.md index abaf7b9a..8fddc030 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,12 @@ module "ec2_instance" { } ``` +## Module wrappers + +Users of this Terraform module can create multiple similar resources by using [`for_each` meta-argument within `module` block](https://www.terraform.io/language/meta-arguments/for_each) which became available in Terraform 0.13. + +Users of Terragrunt can achieve similar results by using modules provided in the [wrappers](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/wrappers) directory, if they prefer to reduce amount of configuration files. + ## Examples - [Complete EC2 instance](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) diff --git a/wrappers/README.md b/wrappers/README.md new file mode 100644 index 00000000..f1a51a5e --- /dev/null +++ b/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ec2-instance/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ec2-instance.git?ref=master//wrappers" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ec2-instance/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/main.tf b/wrappers/main.tf new file mode 100644 index 00000000..9937625b --- /dev/null +++ b/wrappers/main.tf @@ -0,0 +1,57 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + name = try(each.value.name, var.defaults.name, "") + ami = try(each.value.ami, var.defaults.ami, "") + associate_public_ip_address = try(each.value.associate_public_ip_address, var.defaults.associate_public_ip_address, null) + availability_zone = try(each.value.availability_zone, var.defaults.availability_zone, null) + capacity_reservation_specification = try(each.value.capacity_reservation_specification, var.defaults.capacity_reservation_specification, null) + cpu_credits = try(each.value.cpu_credits, var.defaults.cpu_credits, null) + disable_api_termination = try(each.value.disable_api_termination, var.defaults.disable_api_termination, null) + ebs_block_device = try(each.value.ebs_block_device, var.defaults.ebs_block_device, []) + ebs_optimized = try(each.value.ebs_optimized, var.defaults.ebs_optimized, null) + enclave_options_enabled = try(each.value.enclave_options_enabled, var.defaults.enclave_options_enabled, null) + ephemeral_block_device = try(each.value.ephemeral_block_device, var.defaults.ephemeral_block_device, []) + get_password_data = try(each.value.get_password_data, var.defaults.get_password_data, null) + hibernation = try(each.value.hibernation, var.defaults.hibernation, null) + host_id = try(each.value.host_id, var.defaults.host_id, null) + iam_instance_profile = try(each.value.iam_instance_profile, var.defaults.iam_instance_profile, null) + instance_initiated_shutdown_behavior = try(each.value.instance_initiated_shutdown_behavior, var.defaults.instance_initiated_shutdown_behavior, null) + instance_type = try(each.value.instance_type, var.defaults.instance_type, "t3.micro") + ipv6_address_count = try(each.value.ipv6_address_count, var.defaults.ipv6_address_count, null) + ipv6_addresses = try(each.value.ipv6_addresses, var.defaults.ipv6_addresses, null) + key_name = try(each.value.key_name, var.defaults.key_name, null) + launch_template = try(each.value.launch_template, var.defaults.launch_template, null) + metadata_options = try(each.value.metadata_options, var.defaults.metadata_options, {}) + monitoring = try(each.value.monitoring, var.defaults.monitoring, false) + network_interface = try(each.value.network_interface, var.defaults.network_interface, []) + placement_group = try(each.value.placement_group, var.defaults.placement_group, null) + private_ip = try(each.value.private_ip, var.defaults.private_ip, null) + root_block_device = try(each.value.root_block_device, var.defaults.root_block_device, []) + secondary_private_ips = try(each.value.secondary_private_ips, var.defaults.secondary_private_ips, null) + source_dest_check = try(each.value.source_dest_check, var.defaults.source_dest_check, true) + subnet_id = try(each.value.subnet_id, var.defaults.subnet_id, null) + tags = try(each.value.tags, var.defaults.tags, {}) + tenancy = try(each.value.tenancy, var.defaults.tenancy, null) + user_data = try(each.value.user_data, var.defaults.user_data, null) + user_data_base64 = try(each.value.user_data_base64, var.defaults.user_data_base64, null) + volume_tags = try(each.value.volume_tags, var.defaults.volume_tags, {}) + enable_volume_tags = try(each.value.enable_volume_tags, var.defaults.enable_volume_tags, true) + vpc_security_group_ids = try(each.value.vpc_security_group_ids, var.defaults.vpc_security_group_ids, null) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + cpu_core_count = try(each.value.cpu_core_count, var.defaults.cpu_core_count, null) + cpu_threads_per_core = try(each.value.cpu_threads_per_core, var.defaults.cpu_threads_per_core, null) + create_spot_instance = try(each.value.create_spot_instance, var.defaults.create_spot_instance, false) + spot_price = try(each.value.spot_price, var.defaults.spot_price, null) + spot_wait_for_fulfillment = try(each.value.spot_wait_for_fulfillment, var.defaults.spot_wait_for_fulfillment, null) + spot_type = try(each.value.spot_type, var.defaults.spot_type, null) + spot_launch_group = try(each.value.spot_launch_group, var.defaults.spot_launch_group, null) + spot_block_duration_minutes = try(each.value.spot_block_duration_minutes, var.defaults.spot_block_duration_minutes, null) + spot_instance_interruption_behavior = try(each.value.spot_instance_interruption_behavior, var.defaults.spot_instance_interruption_behavior, null) + spot_valid_until = try(each.value.spot_valid_until, var.defaults.spot_valid_until, null) + spot_valid_from = try(each.value.spot_valid_from, var.defaults.spot_valid_from, null) + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) +} diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf new file mode 100644 index 00000000..5da7c09b --- /dev/null +++ b/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/variables.tf b/wrappers/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/versions.tf b/wrappers/versions.tf new file mode 100644 index 00000000..51cad108 --- /dev/null +++ b/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +}