From 80c9fd60bfa76f33c1df267f6e4d8d6644134dcf Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 28 Apr 2023 08:28:53 -0400 Subject: [PATCH 1/4] feat!: Raise minimum required Terraform version to 1.0+ --- .pre-commit-config.yaml | 2 +- README.md | 6 +- examples/complete/README.md | 9 +-- examples/complete/main.tf | 40 ++++++----- examples/complete/versions.tf | 4 +- examples/volume-attachment/README.md | 9 +-- examples/volume-attachment/main.tf | 91 ++++++++++++++------------ examples/volume-attachment/versions.tf | 4 +- main.tf | 16 +++++ versions.tf | 4 +- wrappers/outputs.tf | 2 +- 11 files changed, 108 insertions(+), 79 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75deea30..e940bf7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.77.0 + rev: v1.77.3 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each diff --git a/README.md b/README.md index cdd8766f..316d0b24 100644 --- a/README.md +++ b/README.md @@ -167,14 +167,14 @@ The following combinations are supported to conditionally create resources: | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.20.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.20 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.20.0 | +| [aws](#provider\_aws) | >= 4.20 | ## Modules diff --git a/examples/complete/README.md b/examples/complete/README.md index 99e48366..b4b077ed 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -19,14 +19,14 @@ Note that this example may create resources which can cost money. Run `terraform | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.7 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.20 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.7 | +| [aws](#provider\_aws) | >= 4.20 | ## Modules @@ -43,7 +43,7 @@ Note that this example may create resources which can cost money. Run `terraform | [ec2\_t3\_unlimited](#module\_ec2\_t3\_unlimited) | ../../ | n/a | | [ec2\_targeted\_capacity\_reservation](#module\_ec2\_targeted\_capacity\_reservation) | ../../ | n/a | | [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | ## Resources @@ -55,6 +55,7 @@ Note that this example may create resources which can cost money. Run `terraform | [aws_network_interface.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_interface) | resource | | [aws_placement_group.web](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/placement_group) | resource | | [aws_ami.amazon_linux](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | ## Inputs diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 4c638c9d..f47bdd5b 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -2,18 +2,24 @@ provider "aws" { region = local.region } +data "aws_availability_zones" "available" {} + locals { - name = "example-ec2-complete" + name = "ex-${basename(path.cwd)}" region = "eu-west-1" + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + user_data = <<-EOT - #!/bin/bash - echo "Hello Terraform!" + #!/bin/bash + echo "Hello Terraform!" EOT tags = { - Owner = "user" - Environment = "dev" + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ec2-instance" } } @@ -21,12 +27,6 @@ locals { # EC2 Module ################################################################################ -module "ec2_disabled" { - source = "../../" - - create = false -} - module "ec2_complete" { source = "../../" @@ -150,6 +150,13 @@ module "ec2_t3_unlimited" { tags = local.tags } + +module "ec2_disabled" { + source = "../../" + + create = false +} + ################################################################################ # EC2 Module - multiple instances with `for_each` ################################################################################ @@ -330,15 +337,14 @@ resource "aws_ec2_capacity_reservation" "targeted" { module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" + version = "~> 4.0" name = local.name - cidr = "10.99.0.0/18" + cidr = local.vpc_cidr - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] - public_subnets = ["10.99.0.0/24", "10.99.1.0/24", "10.99.2.0/24"] - private_subnets = ["10.99.3.0/24", "10.99.4.0/24", "10.99.5.0/24"] - database_subnets = ["10.99.7.0/24", "10.99.8.0/24", "10.99.9.0/24"] + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] tags = local.tags } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 36060f73..eddf9d5b 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.7" + version = ">= 4.20" } } } diff --git a/examples/volume-attachment/README.md b/examples/volume-attachment/README.md index af4ffc6e..adede89a 100644 --- a/examples/volume-attachment/README.md +++ b/examples/volume-attachment/README.md @@ -21,14 +21,14 @@ Note that this example may create resources which can cost money. Run `terraform | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 3.72 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.20 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.72 | +| [aws](#provider\_aws) | >= 4.20 | ## Modules @@ -36,7 +36,7 @@ Note that this example may create resources which can cost money. Run `terraform |------|--------|---------| | [ec2](#module\_ec2) | ../../ | n/a | | [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | ## Resources @@ -45,6 +45,7 @@ Note that this example may create resources which can cost money. Run `terraform | [aws_ebs_volume.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume) | resource | | [aws_volume_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment) | resource | | [aws_ami.amazon_linux](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | ## Inputs diff --git a/examples/volume-attachment/main.tf b/examples/volume-attachment/main.tf index 526773fe..f201cad2 100644 --- a/examples/volume-attachment/main.tf +++ b/examples/volume-attachment/main.tf @@ -2,31 +2,68 @@ provider "aws" { region = local.region } +data "aws_availability_zones" "available" {} + locals { - availability_zone = "${local.region}a" - name = "example-ec2-volume-attachment" - region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + region = "eu-west-1" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + tags = { - Owner = "user" - Environment = "dev" + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ec2-instance" } } +################################################################################ +# EC2 Module +################################################################################ + +module "ec2" { + source = "../../" + + name = local.name + + ami = data.aws_ami.amazon_linux.id + instance_type = "c5.large" + availability_zone = element(local.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = true + + tags = local.tags +} + +resource "aws_volume_attachment" "this" { + device_name = "/dev/sdh" + volume_id = aws_ebs_volume.this.id + instance_id = module.ec2.id +} + +resource "aws_ebs_volume" "this" { + availability_zone = element(local.azs, 0) + size = 1 + + tags = local.tags +} + ################################################################################ # Supporting Resources ################################################################################ module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" + version = "~> 4.0" name = local.name - cidr = "10.99.0.0/18" + cidr = local.vpc_cidr - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] - public_subnets = ["10.99.0.0/24", "10.99.1.0/24", "10.99.2.0/24"] - private_subnets = ["10.99.3.0/24", "10.99.4.0/24", "10.99.5.0/24"] - database_subnets = ["10.99.7.0/24", "10.99.8.0/24", "10.99.9.0/24"] + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] tags = local.tags } @@ -55,35 +92,3 @@ module "security_group" { tags = local.tags } - -################################################################################ -# EC2 Module -################################################################################ - -module "ec2" { - source = "../../" - - name = local.name - - ami = data.aws_ami.amazon_linux.id - instance_type = "c5.large" - availability_zone = local.availability_zone - subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] - associate_public_ip_address = true - - tags = local.tags -} - -resource "aws_volume_attachment" "this" { - device_name = "/dev/sdh" - volume_id = aws_ebs_volume.this.id - instance_id = module.ec2.id -} - -resource "aws_ebs_volume" "this" { - availability_zone = local.availability_zone - size = 1 - - tags = local.tags -} diff --git a/examples/volume-attachment/versions.tf b/examples/volume-attachment/versions.tf index 22e8d726..eddf9d5b 100644 --- a/examples/volume-attachment/versions.tf +++ b/examples/volume-attachment/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.72" + version = ">= 4.20" } } } diff --git a/main.tf b/main.tf index a13e6e00..4d81e6f5 100644 --- a/main.tf +++ b/main.tf @@ -48,11 +48,13 @@ resource "aws_instance" "this" { dynamic "capacity_reservation_specification" { for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + content { capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) dynamic "capacity_reservation_target" { for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + content { capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) @@ -63,6 +65,7 @@ resource "aws_instance" "this" { dynamic "root_block_device" { for_each = var.root_block_device + content { delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null) encrypted = lookup(root_block_device.value, "encrypted", null) @@ -77,6 +80,7 @@ resource "aws_instance" "this" { dynamic "ebs_block_device" { for_each = var.ebs_block_device + content { delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null) device_name = ebs_block_device.value.device_name @@ -92,6 +96,7 @@ resource "aws_instance" "this" { dynamic "ephemeral_block_device" { for_each = var.ephemeral_block_device + content { device_name = ephemeral_block_device.value.device_name no_device = lookup(ephemeral_block_device.value, "no_device", null) @@ -101,6 +106,7 @@ resource "aws_instance" "this" { dynamic "metadata_options" { for_each = var.metadata_options != null ? [var.metadata_options] : [] + content { http_endpoint = lookup(metadata_options.value, "http_endpoint", "enabled") http_tokens = lookup(metadata_options.value, "http_tokens", "optional") @@ -111,6 +117,7 @@ resource "aws_instance" "this" { dynamic "network_interface" { for_each = var.network_interface + content { device_index = network_interface.value.device_index network_interface_id = lookup(network_interface.value, "network_interface_id", null) @@ -120,6 +127,7 @@ resource "aws_instance" "this" { dynamic "launch_template" { for_each = var.launch_template != null ? [var.launch_template] : [] + content { id = lookup(var.launch_template, "id", null) name = lookup(var.launch_template, "name", null) @@ -129,6 +137,7 @@ resource "aws_instance" "this" { dynamic "maintenance_options" { for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + content { auto_recovery = try(maintenance_options.value.auto_recovery, null) } @@ -207,6 +216,7 @@ resource "aws_spot_instance_request" "this" { dynamic "capacity_reservation_specification" { for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + content { capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) @@ -222,6 +232,7 @@ resource "aws_spot_instance_request" "this" { dynamic "root_block_device" { for_each = var.root_block_device + content { delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null) encrypted = lookup(root_block_device.value, "encrypted", null) @@ -236,6 +247,7 @@ resource "aws_spot_instance_request" "this" { dynamic "ebs_block_device" { for_each = var.ebs_block_device + content { delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null) device_name = ebs_block_device.value.device_name @@ -251,6 +263,7 @@ resource "aws_spot_instance_request" "this" { dynamic "ephemeral_block_device" { for_each = var.ephemeral_block_device + content { device_name = ephemeral_block_device.value.device_name no_device = lookup(ephemeral_block_device.value, "no_device", null) @@ -260,6 +273,7 @@ resource "aws_spot_instance_request" "this" { dynamic "metadata_options" { for_each = var.metadata_options != null ? [var.metadata_options] : [] + content { http_endpoint = lookup(metadata_options.value, "http_endpoint", "enabled") http_tokens = lookup(metadata_options.value, "http_tokens", "optional") @@ -269,6 +283,7 @@ resource "aws_spot_instance_request" "this" { dynamic "network_interface" { for_each = var.network_interface + content { device_index = network_interface.value.device_index network_interface_id = lookup(network_interface.value, "network_interface_id", null) @@ -278,6 +293,7 @@ resource "aws_spot_instance_request" "this" { dynamic "launch_template" { for_each = var.launch_template != null ? [var.launch_template] : [] + content { id = lookup(var.launch_template, "id", null) name = lookup(var.launch_template, "name", null) diff --git a/versions.tf b/versions.tf index 0836352d..eddf9d5b 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.20.0" + version = ">= 4.20" } } } diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf index 5da7c09b..ec6da5f4 100644 --- a/wrappers/outputs.tf +++ b/wrappers/outputs.tf @@ -1,5 +1,5 @@ output "wrapper" { description = "Map of outputs of a wrapper." value = module.wrapper - # sensitive = false # No sensitive module output found + # sensitive = false # No sensitive module output found } From 660d13ca8bb8e472e8516319cd3e6c57f941a790 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 28 Apr 2023 10:15:23 -0400 Subject: [PATCH 2/4] feat: Add the ability to ignore AMI changes on instance --- README.md | 40 ++++--- main.tf | 272 ++++++++++++++++++++++++++++++++++++++--------- outputs.tf | 112 +++++++++++++++---- variables.tf | 38 ++++--- wrappers/main.tf | 85 ++++++++------- 5 files changed, 397 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index ef4eeadf..33d8151c 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,9 @@ Terraform module which creates an EC2 instance on AWS. ```hcl module "ec2_instance" { source = "terraform-aws-modules/ec2-instance/aws" - version = "~> 3.0" name = "single-instance" - ami = "ami-ebd02392" instance_type = "t2.micro" key_name = "user1" monitoring = true @@ -34,13 +32,11 @@ module "ec2_instance" { ```hcl module "ec2_instance" { source = "terraform-aws-modules/ec2-instance/aws" - version = "~> 3.0" for_each = toset(["one", "two", "three"]) name = "instance-${each.key}" - ami = "ami-ebd02392" instance_type = "t2.micro" key_name = "user1" monitoring = true @@ -59,7 +55,6 @@ module "ec2_instance" { ```hcl module "ec2_instance" { source = "terraform-aws-modules/ec2-instance/aws" - version = "~> 3.0" name = "spot-instance" @@ -67,7 +62,6 @@ module "ec2_instance" { spot_price = "0.60" spot_type = "persistent" - ami = "ami-ebd02392" instance_type = "t2.micro" key_name = "user1" monitoring = true @@ -187,6 +181,7 @@ No modules. | [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_instance.ignore_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [aws_spot_instance_request.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/spot_instance_request) | resource | | [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -202,20 +197,20 @@ No modules. | [associate\_public\_ip\_address](#input\_associate\_public\_ip\_address) | Whether to associate a public IP address with an instance in a VPC | `bool` | `null` | no | | [availability\_zone](#input\_availability\_zone) | AZ to start the instance in | `string` | `null` | no | | [capacity\_reservation\_specification](#input\_capacity\_reservation\_specification) | Describes an instance's Capacity Reservation targeting option | `any` | `{}` | no | -| [cpu\_core\_count](#input\_cpu\_core\_count) | Sets the number of CPU cores for an instance. | `number` | `null` | no | +| [cpu\_core\_count](#input\_cpu\_core\_count) | Sets the number of CPU cores for an instance | `number` | `null` | no | | [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no | -| [cpu\_threads\_per\_core](#input\_cpu\_threads\_per\_core) | Sets the number of CPU threads per core for an instance (has no effect unless cpu\_core\_count is also set). | `number` | `null` | no | +| [cpu\_threads\_per\_core](#input\_cpu\_threads\_per\_core) | Sets the number of CPU threads per core for an instance (has no effect unless cpu\_core\_count is also set) | `number` | `null` | no | | [create](#input\_create) | Whether to create an instance | `bool` | `true` | no | | [create\_iam\_instance\_profile](#input\_create\_iam\_instance\_profile) | Determines whether an IAM instance profile is created or to use an existing IAM instance profile | `bool` | `false` | no | | [create\_spot\_instance](#input\_create\_spot\_instance) | Depicts if the instance is a spot instance | `bool` | `false` | no | -| [disable\_api\_stop](#input\_disable\_api\_stop) | If true, enables EC2 Instance Stop Protection. | `bool` | `null` | no | +| [disable\_api\_stop](#input\_disable\_api\_stop) | If true, enables EC2 Instance Stop Protection | `bool` | `null` | no | | [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no | | [ebs\_block\_device](#input\_ebs\_block\_device) | Additional EBS block devices to attach to the instance | `list(any)` | `[]` | no | | [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no | | [enable\_volume\_tags](#input\_enable\_volume\_tags) | Whether to enable volume tags (if enabled it conflicts with root\_block\_device tags) | `bool` | `true` | no | | [enclave\_options\_enabled](#input\_enclave\_options\_enabled) | Whether Nitro Enclaves will be enabled on the instance. Defaults to `false` | `bool` | `null` | no | | [ephemeral\_block\_device](#input\_ephemeral\_block\_device) | Customize Ephemeral (also known as Instance Store) volumes on the instance | `list(map(string))` | `[]` | no | -| [get\_password\_data](#input\_get\_password\_data) | If true, wait for password data to become available and retrieve it. | `bool` | `null` | no | +| [get\_password\_data](#input\_get\_password\_data) | If true, wait for password data to become available and retrieve it | `bool` | `null` | no | | [hibernation](#input\_hibernation) | If true, the launched EC2 instance will support hibernation | `bool` | `null` | no | | [host\_id](#input\_host\_id) | ID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host | `string` | `null` | no | | [iam\_instance\_profile](#input\_iam\_instance\_profile) | IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile | `string` | `null` | no | @@ -226,15 +221,16 @@ No modules. | [iam\_role\_policies](#input\_iam\_role\_policies) | Policies attached to the IAM role | `map(string)` | `{}` | no | | [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role/profile created | `map(string)` | `{}` | no | | [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name` or `name`) is used as a prefix | `bool` | `true` | no | +| [ignore\_ami\_changes](#input\_ignore\_ami\_changes) | Whether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instance | `bool` | `false` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Shutdown behavior for the instance. Amazon defaults this to stop for EBS-backed instances and terminate for instance-store instances. Cannot be set on instance-store instance | `string` | `null` | no | | [instance\_type](#input\_instance\_type) | The type of instance to start | `string` | `"t3.micro"` | no | | [ipv6\_address\_count](#input\_ipv6\_address\_count) | A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet | `number` | `null` | no | | [ipv6\_addresses](#input\_ipv6\_addresses) | Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface | `list(string)` | `null` | no | | [key\_name](#input\_key\_name) | Key name of the Key Pair to use for the instance; which can be managed using the `aws_key_pair` resource | `string` | `null` | no | -| [launch\_template](#input\_launch\_template) | Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template | `map(string)` | `null` | no | +| [launch\_template](#input\_launch\_template) | Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template | `map(string)` | `{}` | no | | [maintenance\_options](#input\_maintenance\_options) | The maintenance options for the instance | `any` | `{}` | no | -| [metadata\_options](#input\_metadata\_options) | Customize the metadata options of the instance | `map(string)` | `{}` | no | -| [monitoring](#input\_monitoring) | If true, the launched EC2 instance will have detailed monitoring enabled | `bool` | `false` | no | +| [metadata\_options](#input\_metadata\_options) | Customize the metadata options of the instance | `map(string)` |
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "optional"
}
| no | +| [monitoring](#input\_monitoring) | If true, the launched EC2 instance will have detailed monitoring enabled | `bool` | `null` | no | | [name](#input\_name) | Name to be used on EC2 instance created | `string` | `""` | no | | [network\_interface](#input\_network\_interface) | Customize network interfaces to be attached at instance boot time | `list(map(string))` | `[]` | no | | [placement\_group](#input\_placement\_group) | The Placement Group to start the instance in | `string` | `null` | no | @@ -242,7 +238,7 @@ No modules. | [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no | | [root\_block\_device](#input\_root\_block\_device) | Customize details about the root block device of the instance. See Block Devices below for details | `list(any)` | `[]` | no | | [secondary\_private\_ips](#input\_secondary\_private\_ips) | A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface block` | `list(string)` | `null` | no | -| [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs. | `bool` | `true` | no | +| [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `null` | no | | [spot\_block\_duration\_minutes](#input\_spot\_block\_duration\_minutes) | The required duration for the Spot instances, in minutes. This value must be a multiple of 60 (60, 120, 180, 240, 300, or 360) | `number` | `null` | no | | [spot\_instance\_interruption\_behavior](#input\_spot\_instance\_interruption\_behavior) | Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate` | `string` | `null` | no | | [spot\_launch\_group](#input\_spot\_launch\_group) | A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually | `string` | `null` | no | @@ -253,11 +249,11 @@ No modules. | [spot\_wait\_for\_fulfillment](#input\_spot\_wait\_for\_fulfillment) | If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached | `bool` | `null` | no | | [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no | | [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | -| [tenancy](#input\_tenancy) | The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host. | `string` | `null` | no | +| [tenancy](#input\_tenancy) | The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host | `string` | `null` | no | | [timeouts](#input\_timeouts) | Define maximum timeout for creating, updating, and deleting EC2 instance resources | `map(string)` | `{}` | no | -| [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user\_data\_base64 instead. | `string` | `null` | no | -| [user\_data\_base64](#input\_user\_data\_base64) | Can be used instead of user\_data to pass base64-encoded binary data directly. Use this instead of user\_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption. | `string` | `null` | no | -| [user\_data\_replace\_on\_change](#input\_user\_data\_replace\_on\_change) | When used in combination with user\_data or user\_data\_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set. | `bool` | `false` | no | +| [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user\_data\_base64 instead | `string` | `null` | no | +| [user\_data\_base64](#input\_user\_data\_base64) | Can be used instead of user\_data to pass base64-encoded binary data directly. Use this instead of user\_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption | `string` | `null` | no | +| [user\_data\_replace\_on\_change](#input\_user\_data\_replace\_on\_change) | When used in combination with user\_data or user\_data\_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set | `bool` | `null` | no | | [volume\_tags](#input\_volume\_tags) | A mapping of tags to assign to the devices created by the instance at launch time | `map(string)` | `{}` | no | | [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | A list of security group IDs to associate with | `list(string)` | `null` | no | @@ -265,7 +261,7 @@ No modules. | Name | Description | |------|-------------| -| [ami](#output\_ami) | AMI ID that was used to create the instance. | +| [ami](#output\_ami) | AMI ID that was used to create the instance | | [arn](#output\_arn) | The ARN of the instance | | [capacity\_reservation\_specification](#output\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | | [ebs\_block\_device](#output\_ebs\_block\_device) | EBS block device information | @@ -277,13 +273,13 @@ No modules. | [iam\_role\_name](#output\_iam\_role\_name) | The name of the IAM role | | [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | | [id](#output\_id) | The ID of the instance | -| [instance\_state](#output\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | -| [ipv6\_addresses](#output\_ipv6\_addresses) | The IPv6 address assigned to the instance, if applicable. | +| [instance\_state](#output\_instance\_state) | The state of the instance | +| [ipv6\_addresses](#output\_ipv6\_addresses) | The IPv6 address assigned to the instance, if applicable | | [outpost\_arn](#output\_outpost\_arn) | The ARN of the Outpost the instance is assigned to | | [password\_data](#output\_password\_data) | Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true | | [primary\_network\_interface\_id](#output\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | | [private\_dns](#output\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC | -| [private\_ip](#output\_private\_ip) | The private IP address assigned to the instance. | +| [private\_ip](#output\_private\_ip) | The private IP address assigned to the instance | | [public\_dns](#output\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC | | [public\_ip](#output\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached | | [root\_block\_device](#output\_root\_block\_device) | Root block device information | diff --git a/main.tf b/main.tf index d1e5390c..9c889c8c 100644 --- a/main.tf +++ b/main.tf @@ -3,7 +3,7 @@ data "aws_partition" "current" {} locals { create = var.create && var.putin_khuylo - is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a){1}\\..*$/", "1") == "1" ? true : false + is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a|4g){1}\\..*$/", "1") == "1" ? true : false } data "aws_ssm_parameter" "this" { @@ -17,7 +17,7 @@ data "aws_ssm_parameter" "this" { ################################################################################ resource "aws_instance" "this" { - count = local.create && !var.create_spot_instance ? 1 : 0 + count = local.create && !var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) instance_type = var.instance_type @@ -67,14 +67,14 @@ resource "aws_instance" "this" { for_each = var.root_block_device content { - delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null) - encrypted = lookup(root_block_device.value, "encrypted", null) - iops = lookup(root_block_device.value, "iops", null) + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) kms_key_id = lookup(root_block_device.value, "kms_key_id", null) - volume_size = lookup(root_block_device.value, "volume_size", null) - volume_type = lookup(root_block_device.value, "volume_type", null) - throughput = lookup(root_block_device.value, "throughput", null) - tags = lookup(root_block_device.value, "tags", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) } } @@ -82,16 +82,16 @@ resource "aws_instance" "this" { for_each = var.ebs_block_device content { - delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null) + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) device_name = ebs_block_device.value.device_name - encrypted = lookup(ebs_block_device.value, "encrypted", null) - iops = lookup(ebs_block_device.value, "iops", null) + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) - volume_size = lookup(ebs_block_device.value, "volume_size", null) - volume_type = lookup(ebs_block_device.value, "volume_type", null) - throughput = lookup(ebs_block_device.value, "throughput", null) - tags = lookup(ebs_block_device.value, "tags", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) } } @@ -99,20 +99,20 @@ resource "aws_instance" "this" { for_each = var.ephemeral_block_device content { - device_name = ephemeral_block_device.value.device_name - no_device = lookup(ephemeral_block_device.value, "no_device", null) - virtual_name = lookup(ephemeral_block_device.value, "virtual_name", null) + device_name = try(ephemeral_block_device.value.device_name, each.key) + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) } } dynamic "metadata_options" { - for_each = var.metadata_options != null ? [var.metadata_options] : [] + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] content { - http_endpoint = lookup(metadata_options.value, "http_endpoint", "enabled") - http_tokens = lookup(metadata_options.value, "http_tokens", "optional") - http_put_response_hop_limit = lookup(metadata_options.value, "http_put_response_hop_limit", "1") - instance_metadata_tags = lookup(metadata_options.value, "instance_metadata_tags", null) + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) } } @@ -122,12 +122,12 @@ resource "aws_instance" "this" { content { device_index = network_interface.value.device_index network_interface_id = lookup(network_interface.value, "network_interface_id", null) - delete_on_termination = lookup(network_interface.value, "delete_on_termination", false) + delete_on_termination = try(network_interface.value.delete_on_termination, false) } } dynamic "launch_template" { - for_each = var.launch_template != null ? [var.launch_template] : [] + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] content { id = lookup(var.launch_template, "id", null) @@ -161,15 +161,179 @@ resource "aws_instance" "this" { } timeouts { - create = lookup(var.timeouts, "create", null) - update = lookup(var.timeouts, "update", null) - delete = lookup(var.timeouts, "delete", null) + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) } tags = merge({ "Name" = var.name }, var.tags) volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null } +################################################################################ +# Instance - Ignore AMI Changes +################################################################################ + +resource "aws_instance" "ignore_ami" { + count = local.create && var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = try(ephemeral_block_device.value.device_name, each.key) + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + dynamic "maintenance_options" { + for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + + content { + auto_recovery = try(maintenance_options.value.auto_recovery, null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + disable_api_stop = var.disable_api_stop + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null + + lifecycle { + ignore_changes = [ + ami + ] + } +} + ################################################################################ # Spot Instance ################################################################################ @@ -235,14 +399,14 @@ resource "aws_spot_instance_request" "this" { for_each = var.root_block_device content { - delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null) - encrypted = lookup(root_block_device.value, "encrypted", null) - iops = lookup(root_block_device.value, "iops", null) + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) kms_key_id = lookup(root_block_device.value, "kms_key_id", null) - volume_size = lookup(root_block_device.value, "volume_size", null) - volume_type = lookup(root_block_device.value, "volume_type", null) - throughput = lookup(root_block_device.value, "throughput", null) - tags = lookup(root_block_device.value, "tags", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) } } @@ -250,15 +414,16 @@ resource "aws_spot_instance_request" "this" { for_each = var.ebs_block_device content { - delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null) + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) device_name = ebs_block_device.value.device_name - encrypted = lookup(ebs_block_device.value, "encrypted", null) - iops = lookup(ebs_block_device.value, "iops", null) + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) - volume_size = lookup(ebs_block_device.value, "volume_size", null) - volume_type = lookup(ebs_block_device.value, "volume_type", null) - throughput = lookup(ebs_block_device.value, "throughput", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) } } @@ -266,19 +431,20 @@ resource "aws_spot_instance_request" "this" { for_each = var.ephemeral_block_device content { - device_name = ephemeral_block_device.value.device_name - no_device = lookup(ephemeral_block_device.value, "no_device", null) - virtual_name = lookup(ephemeral_block_device.value, "virtual_name", null) + device_name = try(ephemeral_block_device.value.device_name, each.key) + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) } } dynamic "metadata_options" { - for_each = var.metadata_options != null ? [var.metadata_options] : [] + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] content { - http_endpoint = lookup(metadata_options.value, "http_endpoint", "enabled") - http_tokens = lookup(metadata_options.value, "http_tokens", "optional") - http_put_response_hop_limit = lookup(metadata_options.value, "http_put_response_hop_limit", "1") + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) } } @@ -288,12 +454,12 @@ resource "aws_spot_instance_request" "this" { content { device_index = network_interface.value.device_index network_interface_id = lookup(network_interface.value, "network_interface_id", null) - delete_on_termination = lookup(network_interface.value, "delete_on_termination", false) + delete_on_termination = try(network_interface.value.delete_on_termination, false) } } dynamic "launch_template" { - for_each = var.launch_template != null ? [var.launch_template] : [] + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] content { id = lookup(var.launch_template, "id", null) @@ -318,8 +484,8 @@ resource "aws_spot_instance_request" "this" { } timeouts { - create = lookup(var.timeouts, "create", null) - delete = lookup(var.timeouts, "delete", null) + create = try(var.timeouts.create, null) + delete = try(var.timeouts.delete, null) } tags = merge({ "Name" = var.name }, var.tags) diff --git a/outputs.tf b/outputs.tf index 1e5c90aa..04610766 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,86 +1,156 @@ output "id" { description = "The ID of the instance" - value = try(aws_instance.this[0].id, aws_spot_instance_request.this[0].id, "") + value = try( + aws_instance.this[0].id, + aws_instance.ignore_ami[0].id, + aws_spot_instance_request.this[0].id, + null, + ) } output "arn" { description = "The ARN of the instance" - value = try(aws_instance.this[0].arn, aws_spot_instance_request.this[0].arn, "") + value = try( + aws_instance.this[0].arn, + aws_instance.ignore_ami[0].arn, + aws_spot_instance_request.this[0].arn, + null, + ) } output "capacity_reservation_specification" { description = "Capacity reservation specification of the instance" - value = try(aws_instance.this[0].capacity_reservation_specification, aws_spot_instance_request.this[0].capacity_reservation_specification, "") + value = try( + aws_instance.this[0].capacity_reservation_specification, + aws_instance.ignore_ami[0].capacity_reservation_specification, + aws_spot_instance_request.this[0].capacity_reservation_specification, + null, + ) } output "instance_state" { - description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" - value = try(aws_instance.this[0].instance_state, aws_spot_instance_request.this[0].instance_state, "") + description = "The state of the instance" + value = try( + aws_instance.this[0].instance_state, + aws_instance.ignore_ami[0].instance_state, + aws_spot_instance_request.this[0].instance_state, + null, + ) } output "outpost_arn" { description = "The ARN of the Outpost the instance is assigned to" - value = try(aws_instance.this[0].outpost_arn, aws_spot_instance_request.this[0].outpost_arn, "") + value = try( + aws_instance.this[0].outpost_arn, + aws_instance.ignore_ami[0].outpost_arn, + aws_spot_instance_request.this[0].outpost_arn, + null, + ) } output "password_data" { description = "Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true" - value = try(aws_instance.this[0].password_data, aws_spot_instance_request.this[0].password_data, "") + value = try( + aws_instance.this[0].password_data, + aws_instance.ignore_ami[0].password_data, + aws_spot_instance_request.this[0].password_data, + null, + ) } output "primary_network_interface_id" { description = "The ID of the instance's primary network interface" - value = try(aws_instance.this[0].primary_network_interface_id, aws_spot_instance_request.this[0].primary_network_interface_id, "") + value = try( + aws_instance.this[0].primary_network_interface_id, + aws_instance.ignore_ami[0].primary_network_interface_id, + aws_spot_instance_request.this[0].primary_network_interface_id, + null, + ) } output "private_dns" { description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC" - value = try(aws_instance.this[0].private_dns, aws_spot_instance_request.this[0].private_dns, "") + value = try( + aws_instance.this[0].private_dns, + aws_instance.ignore_ami[0].private_dns, + aws_spot_instance_request.this[0].private_dns, + null, + ) } output "public_dns" { description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC" - value = try(aws_instance.this[0].public_dns, aws_spot_instance_request.this[0].public_dns, "") + value = try( + aws_instance.this[0].public_dns, + aws_instance.ignore_ami[0].public_dns, + aws_spot_instance_request.this[0].public_dns, + null, + ) } output "public_ip" { description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached" - value = try(aws_instance.this[0].public_ip, aws_spot_instance_request.this[0].public_ip, "") + value = try( + aws_instance.this[0].public_ip, + aws_instance.ignore_ami[0].public_ip, + aws_spot_instance_request.this[0].public_ip, + null, + ) } output "private_ip" { - description = "The private IP address assigned to the instance." - value = try(aws_instance.this[0].private_ip, aws_spot_instance_request.this[0].private_ip, "") + description = "The private IP address assigned to the instance" + value = try( + aws_instance.this[0].private_ip, + aws_instance.ignore_ami[0].private_ip, + aws_spot_instance_request.this[0].private_ip, + null, + ) } output "ipv6_addresses" { - description = "The IPv6 address assigned to the instance, if applicable." - value = try(aws_instance.this[0].ipv6_addresses, []) + description = "The IPv6 address assigned to the instance, if applicable" + value = try( + aws_instance.this[0].ipv6_addresses, + aws_instance.ignore_ami[0].ipv6_addresses, + aws_spot_instance_request.this[0].ipv6_addresses, + [], + ) } output "tags_all" { description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" - value = try(aws_instance.this[0].tags_all, aws_spot_instance_request.this[0].tags_all, {}) + value = try( + aws_instance.this[0].tags_all, + aws_instance.ignore_ami[0].tags_all, + aws_spot_instance_request.this[0].tags_all, + {}, + ) } output "spot_bid_status" { description = "The current bid status of the Spot Instance Request" - value = try(aws_spot_instance_request.this[0].spot_bid_status, "") + value = try(aws_spot_instance_request.this[0].spot_bid_status, null) } output "spot_request_state" { description = "The current request state of the Spot Instance Request" - value = try(aws_spot_instance_request.this[0].spot_request_state, "") + value = try(aws_spot_instance_request.this[0].spot_request_state, null) } output "spot_instance_id" { description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request" - value = try(aws_spot_instance_request.this[0].spot_instance_id, "") + value = try(aws_spot_instance_request.this[0].spot_instance_id, null) } output "ami" { - description = "AMI ID that was used to create the instance." - value = try(aws_instance.this[0].ami, aws_spot_instance_request.this[0].ami, "") + description = "AMI ID that was used to create the instance" + value = try( + aws_instance.this[0].ami, + aws_instance.ignore_ami[0].ami, + aws_spot_instance_request.this[0].ami, + null, + ) } ################################################################################ diff --git a/variables.tf b/variables.tf index bc8d1595..b1b92dbf 100644 --- a/variables.tf +++ b/variables.tf @@ -22,6 +22,12 @@ variable "ami" { default = null } +variable "ignore_ami_changes" { + description = "Whether changes to the AMI ID changes should be ignored by Terraform. Note - changing this value will result in the replacement of the instance" + type = bool + default = false +} + variable "associate_public_ip_address" { description = "Whether to associate a public IP address with an instance in a VPC" type = bool @@ -83,7 +89,7 @@ variable "ephemeral_block_device" { } variable "get_password_data" { - description = "If true, wait for password data to become available and retrieve it." + description = "If true, wait for password data to become available and retrieve it" type = bool default = null } @@ -139,19 +145,23 @@ variable "key_name" { variable "launch_template" { description = "Specifies a Launch Template to configure the instance. Parameters configured on this resource will override the corresponding parameters in the Launch Template" type = map(string) - default = null + default = {} } variable "metadata_options" { description = "Customize the metadata options of the instance" type = map(string) - default = {} + default = { + "http_endpoint" = "enabled" + "http_put_response_hop_limit" = 1 + "http_tokens" = "optional" + } } variable "monitoring" { description = "If true, the launched EC2 instance will have detailed monitoring enabled" type = bool - default = false + default = null } variable "network_interface" { @@ -185,9 +195,9 @@ variable "secondary_private_ips" { } variable "source_dest_check" { - description = "Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs." + description = "Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs" type = bool - default = true + default = null } variable "subnet_id" { @@ -203,27 +213,27 @@ variable "tags" { } variable "tenancy" { - description = "The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host." + description = "The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host" type = string default = null } variable "user_data" { - description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user_data_base64 instead." + description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user_data_base64 instead" type = string default = null } variable "user_data_base64" { - description = "Can be used instead of user_data to pass base64-encoded binary data directly. Use this instead of user_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption." + description = "Can be used instead of user_data to pass base64-encoded binary data directly. Use this instead of user_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption" type = string default = null } variable "user_data_replace_on_change" { - description = "When used in combination with user_data or user_data_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set." + description = "When used in combination with user_data or user_data_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set" type = bool - default = false + default = null } variable "volume_tags" { @@ -251,13 +261,13 @@ variable "timeouts" { } variable "cpu_core_count" { - description = "Sets the number of CPU cores for an instance." # This option is only supported on creation of instance type that support CPU Options https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html#cpu-options-supported-instances-values + description = "Sets the number of CPU cores for an instance" # This option is only supported on creation of instance type that support CPU Options https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html#cpu-options-supported-instances-values type = number default = null } variable "cpu_threads_per_core" { - description = "Sets the number of CPU threads per core for an instance (has no effect unless cpu_core_count is also set)." + description = "Sets the number of CPU threads per core for an instance (has no effect unless cpu_core_count is also set)" type = number default = null } @@ -318,7 +328,7 @@ variable "spot_valid_from" { } variable "disable_api_stop" { - description = "If true, enables EC2 Instance Stop Protection." + description = "If true, enables EC2 Instance Stop Protection" type = bool default = null diff --git a/wrappers/main.tf b/wrappers/main.tf index c9700f24..12a4b59a 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -7,6 +7,7 @@ module "wrapper" { name = try(each.value.name, var.defaults.name, "") ami_ssm_parameter = try(each.value.ami_ssm_parameter, var.defaults.ami_ssm_parameter, "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2") ami = try(each.value.ami, var.defaults.ami, null) + ignore_ami_changes = try(each.value.ignore_ami_changes, var.defaults.ignore_ami_changes, false) associate_public_ip_address = try(each.value.associate_public_ip_address, var.defaults.associate_public_ip_address, null) maintenance_options = try(each.value.maintenance_options, var.defaults.maintenance_options, {}) availability_zone = try(each.value.availability_zone, var.defaults.availability_zone, null) @@ -26,44 +27,48 @@ module "wrapper" { 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) - user_data_replace_on_change = try(each.value.user_data_replace_on_change, var.defaults.user_data_replace_on_change, false) - 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) - disable_api_stop = try(each.value.disable_api_stop, var.defaults.disable_api_stop, null) - putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) - create_iam_instance_profile = try(each.value.create_iam_instance_profile, var.defaults.create_iam_instance_profile, false) - iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) - iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) - iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) - iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) - iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) - iam_role_policies = try(each.value.iam_role_policies, var.defaults.iam_role_policies, {}) - iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) + launch_template = try(each.value.launch_template, var.defaults.launch_template, {}) + metadata_options = try(each.value.metadata_options, var.defaults.metadata_options, { + "http_endpoint" = "enabled" + "http_put_response_hop_limit" = 1 + "http_tokens" = "optional" + }) + monitoring = try(each.value.monitoring, var.defaults.monitoring, null) + 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, null) + 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) + user_data_replace_on_change = try(each.value.user_data_replace_on_change, var.defaults.user_data_replace_on_change, 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) + disable_api_stop = try(each.value.disable_api_stop, var.defaults.disable_api_stop, null) + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) + create_iam_instance_profile = try(each.value.create_iam_instance_profile, var.defaults.create_iam_instance_profile, false) + iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) + iam_role_policies = try(each.value.iam_role_policies, var.defaults.iam_role_policies, {}) + iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) } From dec22369a2ec1e6ff39e31080525fe4d1337e778 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 28 Apr 2023 10:20:09 -0400 Subject: [PATCH 3/4] chore: Add examples README --- examples/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..f417c0ad --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. From 2669d46c779e57259064b714acae29e5af78faba Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 28 Apr 2023 10:46:00 -0400 Subject: [PATCH 4/4] fix: Remove `each.key` usage due to list variable type --- main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.tf b/main.tf index 9c889c8c..46b23b46 100644 --- a/main.tf +++ b/main.tf @@ -99,7 +99,7 @@ resource "aws_instance" "this" { for_each = var.ephemeral_block_device content { - device_name = try(ephemeral_block_device.value.device_name, each.key) + device_name = ephemeral_block_device.value.device_name no_device = try(ephemeral_block_device.value.no_device, null) virtual_name = try(ephemeral_block_device.value.virtual_name, null) } @@ -257,7 +257,7 @@ resource "aws_instance" "ignore_ami" { for_each = var.ephemeral_block_device content { - device_name = try(ephemeral_block_device.value.device_name, each.key) + device_name = ephemeral_block_device.value.device_name no_device = try(ephemeral_block_device.value.no_device, null) virtual_name = try(ephemeral_block_device.value.virtual_name, null) } @@ -431,7 +431,7 @@ resource "aws_spot_instance_request" "this" { for_each = var.ephemeral_block_device content { - device_name = try(ephemeral_block_device.value.device_name, each.key) + device_name = ephemeral_block_device.value.device_name no_device = try(ephemeral_block_device.value.no_device, null) virtual_name = try(ephemeral_block_device.value.virtual_name, null) }