diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 424b3710..b84d048d 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.98.0 + rev: v1.99.4 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each diff --git a/README.md b/README.md index 863555d1..c7d6bd04 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,10 @@ module "ec2_instance" { name = "single-instance" - instance_type = "t2.micro" - key_name = "user1" - monitoring = true - vpc_security_group_ids = ["sg-12345678"] - subnet_id = "subnet-eddcdzz4" + instance_type = "t2.micro" + key_name = "user1" + monitoring = true + subnet_id = "subnet-eddcdzz4" tags = { Terraform = "true" @@ -37,11 +36,10 @@ module "ec2_instance" { name = "instance-${each.key}" - instance_type = "t2.micro" - key_name = "user1" - monitoring = true - vpc_security_group_ids = ["sg-12345678"] - subnet_id = "subnet-eddcdzz4" + instance_type = "t2.micro" + key_name = "user1" + monitoring = true + subnet_id = "subnet-eddcdzz4" tags = { Terraform = "true" @@ -62,11 +60,10 @@ module "ec2_instance" { spot_price = "0.60" spot_type = "persistent" - instance_type = "t2.micro" - key_name = "user1" - monitoring = true - vpc_security_group_ids = ["sg-12345678"] - subnet_id = "subnet-eddcdzz4" + instance_type = "t2.micro" + key_name = "user1" + monitoring = true + subnet_id = "subnet-eddcdzz4" tags = { Terraform = "true" @@ -75,23 +72,16 @@ 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) - [EC2 instance w/ private network access via Session Manager](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/session-manager) -- [EC2 instance with EBS volume attachment](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/volume-attachment) ## Make an encrypted AMI for use This module does not support encrypted AMI's out of the box however it is easy enough for you to generate one for use -This example creates an encrypted image from the latest ubuntu 16.04 base image. +This example creates an encrypted image from the latest ubuntu 20.04 base image. ```hcl provider "aws" { @@ -139,22 +129,32 @@ data "aws_ami" "encrypted-ami" { The following combinations are supported to conditionally create resources: -- Disable resource creation (no resources created): - ```hcl - create = false -``` +module "ec2_instance" { + source = "terraform-aws-modules/ec2-instance/aws" -- Create spot instance: + # Disable creation of EC2 and all resources + create = false -```hcl + # Enable creation of spot instance create_spot_instance = true + + # Enable creation of EC2 IAM instance profile + create_iam_instance_profile = true + + # Disable creation of security group + create_security_group = false + + # Enable creation of elastic IP + create_eip = true + + # ... omitted +} ``` ## Notes - `network_interface` can't be specified together with `vpc_security_group_ids`, `associate_public_ip_address`, `subnet_id`. See [complete example](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) for details. -- Changes in `ebs_block_device` argument will be ignored. Use [aws_volume_attachment](https://www.terraform.io/docs/providers/aws/r/volume_attachment.html) resource to attach and detach volumes from AWS EC2 instances. See [this example](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/volume-attachment). - In regards to spot instances, you must grant the `AWSServiceRoleForEC2Spot` service-linked role access to any custom KMS keys, otherwise your spot request and instances will fail with `bad parameters`. You can see more details about why the request failed by using the awscli and `aws ec2 describe-spot-instance-requests` @@ -162,14 +162,14 @@ The following combinations are supported to conditionally create resources: | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66 | +| [terraform](#requirement\_terraform) | >= 1.10 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules @@ -179,46 +179,54 @@ No modules. | Name | Type | |------|------| +| [aws_ebs_volume.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume) | resource | +| [aws_ec2_tag.spot_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | | [aws_eip.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource | | [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_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_spot_instance_request.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/spot_instance_request) | resource | +| [aws_volume_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment) | resource | +| [aws_vpc_security_group_egress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | | [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | | [aws_ssm_parameter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | +| [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [ami](#input\_ami) | ID of AMI to use for the instance | `string` | `null` | no | -| [ami\_ssm\_parameter](#input\_ami\_ssm\_parameter) | SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see [reference](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html) | `string` | `"/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"` | no | +| [ami\_ssm\_parameter](#input\_ami\_ssm\_parameter) | SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see [reference](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html) | `string` | `"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"` | no | | [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 | +| [capacity\_reservation\_specification](#input\_capacity\_reservation\_specification) | Describes an instance's Capacity Reservation targeting option |
object({
capacity_reservation_preference = optional(string)
capacity_reservation_target = optional(object({
capacity_reservation_id = optional(string)
capacity_reservation_resource_group_arn = optional(string)
}))
})
| `null` | no | | [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no | -| [cpu\_options](#input\_cpu\_options) | Defines CPU options to apply to the instance at launch time. | `any` | `{}` | 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\_options](#input\_cpu\_options) | Defines CPU options to apply to the instance at launch time. |
object({
amd_sev_snp = optional(string)
core_count = optional(number)
threads_per_core = optional(number)
})
| `null` | no | | [create](#input\_create) | Whether to create an instance | `bool` | `true` | no | | [create\_eip](#input\_create\_eip) | Determines whether a public EIP will be created and associated with the instance. | `bool` | `false` | 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\_security\_group](#input\_create\_security\_group) | Determines whether a security group will be created | `bool` | `true` | 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\_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 | +| [ebs\_volumes](#input\_ebs\_volumes) | Additional EBS volumes to attach to the instance |
map(object({
encrypted = optional(bool)
final_snapshot = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
multi_attach_enabled = optional(bool)
outpost_arn = optional(string)
size = optional(number)
snapshot_id = optional(string)
tags = optional(map(string), {})
throughput = optional(number)
type = optional(string, "gp3")
# Attachment
device_name = optional(string) # Will fall back to use map key as device name
force_detach = optional(bool)
skip_destroy = optional(bool)
stop_instance_before_detaching = optional(bool)
}))
| `null` | no | | [eip\_domain](#input\_eip\_domain) | Indicates if this EIP is for use in VPC | `string` | `"vpc"` | no | | [eip\_tags](#input\_eip\_tags) | A map of additional tags to add to the eip | `map(string)` | `{}` | no | +| [enable\_primary\_ipv6](#input\_enable\_primary\_ipv6) | Whether to assign a primary IPv6 Global Unicast Address (GUA) to the instance when launched in a dual-stack or IPv6-only subnet | `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 | +| [ephemeral\_block\_device](#input\_ephemeral\_block\_device) | Customize Ephemeral (also known as Instance Store) volumes on the instance |
map(object({
device_name = string
no_device = optional(bool)
virtual_name = optional(string)
}))
| `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 | +| [host\_resource\_group\_arn](#input\_host\_resource\_group\_arn) | ARN of the host resource group in which to launch the instances. If you specify an ARN, omit the `tenancy` parameter or set it to `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 | | [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | | [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | @@ -229,25 +237,34 @@ No modules. | [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\_market\_options](#input\_instance\_market\_options) | The market (purchasing) option for the instance. If set, overrides the `create_spot_instance` variable |
object({
market_type = optional(string)
spot_options = optional(object({
instance_interruption_behavior = optional(string)
max_price = optional(string)
spot_instance_type = optional(string)
valid_until = optional(string)
}))
})
| `null` | no | | [instance\_tags](#input\_instance\_tags) | Additional tags for the instance | `map(string)` | `{}` | 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)` | `{}` | 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)` |
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "required"
}
| 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 |
object({
id = optional(string)
name = optional(string)
version = optional(string)
})
| `null` | no | +| [maintenance\_options](#input\_maintenance\_options) | The maintenance options for the instance |
object({
auto_recovery = optional(string)
})
| `null` | no | +| [metadata\_options](#input\_metadata\_options) | Customize the metadata options of the instance |
object({
http_endpoint = optional(string, "enabled")
http_protocol_ipv6 = optional(string)
http_put_response_hop_limit = optional(number, 1)
http_tokens = optional(string, "required")
instance_metadata_tags = optional(string)
})
|
{
"http_endpoint": "enabled",
"http_put_response_hop_limit": 1,
"http_tokens": "required"
}
| 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 | +| [network\_interface](#input\_network\_interface) | Customize network interfaces to be attached at instance boot time |
map(object({
delete_on_termination = optional(bool)
device_index = optional(number) # Will fall back to use map key as device index
network_card_index = optional(number)
network_interface_id = string
}))
| `null` | no | | [placement\_group](#input\_placement\_group) | The Placement Group to start the instance in | `string` | `null` | no | -| [private\_dns\_name\_options](#input\_private\_dns\_name\_options) | Customize the private DNS name options of the instance | `map(string)` | `{}` | no | +| [placement\_partition\_number](#input\_placement\_partition\_number) | Number of the partition the instance is in. Valid only if the `aws_placement_group` resource's `strategy` argument is set to `partition` | `number` | `null` | no | +| [private\_dns\_name\_options](#input\_private\_dns\_name\_options) | Customize the private DNS name options of the instance |
object({
enable_resource_name_dns_a_record = optional(bool)
enable_resource_name_dns_aaaa_record = optional(bool)
hostname_type = optional(string)
})
| `null` | no | | [private\_ip](#input\_private\_ip) | Private IP address to associate with the instance in a VPC | `string` | `null` | no | | [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 | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | +| [root\_block\_device](#input\_root\_block\_device) | Customize details about the root block device of the instance. See Block Devices below for details |
object({
delete_on_termination = optional(bool)
encrypted = optional(bool)
iops = optional(number)
kms_key_id = optional(string)
tags = optional(map(string), {})
throughput = optional(number)
size = optional(number)
type = optional(string)
})
| `null` | 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 | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group | `string` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | Egress rules to add to the security group |
map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(number)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(number)
}))
|
{
"ipv4_default": {
"cidr_ipv4": "0.0.0.0/0",
"description": "Allow all IPv4 traffic",
"ip_protocol": "-1"
},
"ipv6_default": {
"cidr_ipv6": "::/0",
"description": "Allow all IPv6 traffic",
"ip_protocol": "-1"
}
}
| no | +| [security\_group\_ingress\_rules](#input\_security\_group\_ingress\_rules) | Egress rules to add to the security group |
map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(number)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(number)
}))
| `null` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name` or `name`) is used as a prefix | `bool` | `true` | no | +| [security\_group\_vpc\_id](#input\_security\_group\_vpc\_id) | VPC ID to create the security group in. If not set, the security group will be created in the default VPC | `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` | `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 | | [spot\_price](#input\_spot\_price) | The maximum price to request on the spot market. Defaults to on-demand price | `string` | `null` | no | @@ -263,7 +280,7 @@ No modules. | [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 | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | A list of security group IDs to associate with | `list(string)` | `[]` | no | ## Outputs @@ -274,6 +291,7 @@ No modules. | [availability\_zone](#output\_availability\_zone) | The availability zone of the created 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 | +| [ebs\_volumes](#output\_ebs\_volumes) | Map of EBS volumes created and their attributes | | [ephemeral\_block\_device](#output\_ephemeral\_block\_device) | Ephemeral block device information | | [iam\_instance\_profile\_arn](#output\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | | [iam\_instance\_profile\_id](#output\_iam\_instance\_profile\_id) | Instance profile's ID | diff --git a/UPGRADE-3.0.md b/docs/UPGRADE-3.0.md similarity index 96% rename from UPGRADE-3.0.md rename to docs/UPGRADE-3.0.md index 46175fb0..7bdfea98 100644 --- a/UPGRADE-3.0.md +++ b/docs/UPGRADE-3.0.md @@ -3,7 +3,6 @@ If you have any questions regarding this upgrade process, please consult the `examples` directory: - [Complete](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) -- [Volume Attachment](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/volume-attachment) If you find a bug, please open an issue with supporting configuration to reproduce. diff --git a/docs/UPGRADE-6.0.md b/docs/UPGRADE-6.0.md new file mode 100644 index 00000000..72d96976 --- /dev/null +++ b/docs/UPGRADE-6.0.md @@ -0,0 +1,183 @@ +# Upgrade from v5.x to v6.x + +If you have any questions regarding this upgrade process, please consult the `examples` directory: + +- [Complete](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) + +If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- Terraform v1.10.0 is now minimum supported version +- AWS provider v6.0.0 is now minimum supported version +- The default value for `ami_ssm_parameter` was changed from `"/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"` to `"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"`. AL2 is approaching end of life. + +## Additional changes + +### Added + +- Support for creating a security group within the module; this is now the default behavior and can be disabled by setting `create_security_group = false`. +- Support for `region` parameter to specify the AWS region for the resources created if different from the provider region. +- Support for tagging spot instances + +### Modified + +- Variable definitions now contain detailed `object` types in place of the previously used `any` type. +- Inline `ebs_block_device` argument has been removed in favor of `ebs_volumes` which is a map of EBS volumes created through `aws_ebs_volume` and `aws_ebs_volume_attachment` resources. This provides the same API as before, but allows for more flexibility without generating diffs when adding or removing EBS volumes as well as unintended changes to the volumes. +- Correct tag precedence ordering (least specific to most specific) + +### Removed + +- The `volume-attachment` example has been removed since the module has been updated to use the corrected form of EBS volume creation and attachment (tl;dr - example is no longer useful). + +### Variable and output changes + +1. Removed variables: + + - `cpu_core_count` - removed from provider `v6.x` + - `cpu_threads_per_core` - removed from provider `v6.x` + +2. Renamed variables: + + - `ebs_block_device` -> `ebs_volumes` + +3. Added variables: + + - `region` + - `enable_primary_ipv6` + - `host_resource_group_arn` + - `instance_market_options` + - `placement_partition_number` + - `create_security_group` + - `security_group_name` + - `security_group_use_name_prefix` + - `security_group_description` + - `security_group_vpc_id` + - `security_group_tags` + - `security_group_egress_rules` + - `security_group_ingress_rules` + +4. Removed outputs: + + - None + +5. Renamed outputs: + + - None + +6. Added outputs: + + - `ebs_volumes` + +## Upgrade State Migrations + +### Before 5.x Example + +```hcl +module "ec2_upgrade" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "5.8.0" + + # Truncated for brevity, only relevant module API changes are shown ... + + root_block_device = [ + { + encrypted = true + volume_size = 50 + volume_type = "gp3" + throughput = 200 + tags = { + Name = "my-root-block" + } + }, + ] + + ebs_block_device = [ + { + device_name = "/dev/sdf" + encrypted = true + volume_size = 5 + volume_type = "gp3" + throughput = 200 + tags = { + MountPoint = "/mnt/data" + } + } + ] + + network_interface = [ + { + device_index = 0 + network_interface_id = aws_network_interface.this.id + delete_on_termination = false + } + ] + + tags = local.tags +} +``` + +### After 6.x Example + +```hcl +module "ec2_upgrade" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "6.0.0" + + # Truncated for brevity, only relevant module API changes are shown ... + + # There can only be one root block device, so the wrapping list is removed + root_block_device = { + encrypted = true + size = 50 # Was `volume_size` + type = "gp3" # Was `volume_type` + throughput = 200 + tags = { + Name = "my-root-block" + } + } + + # Now a map of EBS volumes is used instead of a list + ebs_volumes = { + # The device_name can be the key of the map, or set by `device_name` attribute + "/dev/sdf" = { + encrypted = true + size = 5 # Was `volume_size` + type = "gp3" # Was `volume_type`, `gp3` is now the default + throughput = 200 + tags = { + MountPoint = "/mnt/data" + } + } + } + + # Now a map of network interfaces is used instead of a list + network_interface = { + # The device_index can be the key of the map, or set by `device_index` attribute + 0 = { + network_interface_id = aws_network_interface.this.id + delete_on_termination = false + } + } + + tags = local.tags +} +``` + +To migrate from the `v5.x` version to `v6.x` version example shown above, the following state move commands can be performed to maintain the current resources without modification: + +> [!NOTE] +> State move commands should only be required on instances that have additional EBS volumes attached to them. + +```bash +terraform state rm 'module.ec2_complete.aws_instance.this[0]' +terraform import 'module.ec2_complete.aws_instance.this[0]' + +# Do the following for each additional EBS volume attached to the instance +terraform import 'module.ec2_complete.aws_ebs_volume.this["/dev/sdf"]' +terraform import 'module.ec2_complete.aws_volume_attachment.this["/dev/sdf"]' :: +``` + +> [!TIP] +> If you encounter a situation where Terraform wants to recreate the instance due to user data changes, you can set the `user_data_replace_on_change` variable to `false` to prevent this behavior. +> This is related to https://github.com/hashicorp/terraform-provider-aws/issues/5011 diff --git a/examples/complete/README.md b/examples/complete/README.md index 1658ff2a..f0b0d11d 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -19,21 +19,20 @@ Note that this example may create resources which can cost money. Run `terraform | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66 | +| [terraform](#requirement\_terraform) | >= 1.10 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [ec2\_complete](#module\_ec2\_complete) | ../../ | n/a | -| [ec2\_cpu\_options](#module\_ec2\_cpu\_options) | ../../ | n/a | | [ec2\_disabled](#module\_ec2\_disabled) | ../../ | n/a | | [ec2\_ignore\_ami\_changes](#module\_ec2\_ignore\_ami\_changes) | ../../ | n/a | | [ec2\_metadata\_options](#module\_ec2\_metadata\_options) | ../../ | n/a | @@ -44,8 +43,8 @@ Note that this example may create resources which can cost money. Run `terraform | [ec2\_t2\_unlimited](#module\_ec2\_t2\_unlimited) | ../../ | n/a | | [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 | ~> 5.0 | +| [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources @@ -57,7 +56,6 @@ 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_ami.amazon_linux_23](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 465e7902..d43bf633 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -52,38 +52,34 @@ module "ec2_complete" { # enclave_options_enabled = true user_data_base64 = base64encode(local.user_data) - user_data_replace_on_change = true + user_data_replace_on_change = false cpu_options = { core_count = 2 threads_per_core = 1 } enable_volume_tags = false - root_block_device = [ - { - encrypted = true - volume_type = "gp3" - throughput = 200 - volume_size = 50 - tags = { - Name = "my-root-block" - } - }, - ] - - ebs_block_device = [ - { - device_name = "/dev/sdf" - volume_type = "gp3" - volume_size = 5 - throughput = 200 - encrypted = true - kms_key_id = aws_kms_key.this.arn + root_block_device = { + encrypted = true + type = "gp3" + throughput = 200 + size = 50 + tags = { + Name = "my-root-block" + } + } + + ebs_volumes = { + "/dev/sdf" = { + size = 5 + throughput = 200 + encrypted = true + kms_key_id = aws_kms_key.this.arn tags = { MountPoint = "/mnt/data" } } - ] + } tags = local.tags } @@ -93,13 +89,12 @@ module "ec2_network_interface" { name = "${local.name}-network-interface" - network_interface = [ - { - device_index = 0 + network_interface = { + 0 = { network_interface_id = aws_network_interface.this.id delete_on_termination = false } - ] + } tags = local.tags } @@ -109,8 +104,7 @@ module "ec2_metadata_options" { name = "${local.name}-metadata-options" - subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] + subnet_id = element(module.vpc.private_subnets, 0) metadata_options = { http_endpoint = "enabled" @@ -130,7 +124,6 @@ module "ec2_t2_unlimited" { instance_type = "t2.micro" cpu_credits = "unlimited" subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] associate_public_ip_address = true maintenance_options = { @@ -148,7 +141,6 @@ module "ec2_t3_unlimited" { instance_type = "t3.micro" cpu_credits = "unlimited" 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 @@ -167,15 +159,14 @@ module "ec2_disabled" { module "ec2_ignore_ami_changes" { source = "../../" - name = local.name + name = "${local.name}-ignore-ami-changes" ignore_ami_changes = true - ami = data.aws_ami.amazon_linux.id - instance_type = "t2.micro" - availability_zone = element(module.vpc.azs, 0) - subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] + ami = data.aws_ami.amazon_linux.id + instance_type = "t2.micro" + availability_zone = element(module.vpc.azs, 0) + subnet_id = element(module.vpc.private_subnets, 0) tags = local.tags } @@ -190,29 +181,25 @@ locals { instance_type = "t3.micro" availability_zone = element(module.vpc.azs, 0) subnet_id = element(module.vpc.private_subnets, 0) - root_block_device = [ - { - encrypted = true - volume_type = "gp3" - throughput = 200 - volume_size = 50 - tags = { - Name = "my-root-block" - } + root_block_device = { + encrypted = true + type = "gp3" + throughput = 200 + size = 50 + tags = { + Name = "my-root-block" } - ] + } } two = { instance_type = "t3.small" availability_zone = element(module.vpc.azs, 1) subnet_id = element(module.vpc.private_subnets, 1) - root_block_device = [ - { - encrypted = true - volume_type = "gp2" - volume_size = 50 - } - ] + root_block_device = { + encrypted = true + type = "gp2" + size = 50 + } } three = { instance_type = "t3.medium" @@ -229,13 +216,12 @@ module "ec2_multiple" { name = "${local.name}-multi-${each.key}" - instance_type = each.value.instance_type - availability_zone = each.value.availability_zone - subnet_id = each.value.subnet_id - vpc_security_group_ids = [module.security_group.security_group_id] + instance_type = each.value.instance_type + availability_zone = each.value.availability_zone + subnet_id = each.value.subnet_id enable_volume_tags = false - root_block_device = lookup(each.value, "root_block_device", []) + root_block_device = try(each.value.root_block_device, null) tags = local.tags } @@ -256,10 +242,9 @@ module "ec2_spot_instance" { associate_public_ip_address = true # Spot request specific attributes - spot_price = "0.1" - spot_wait_for_fulfillment = true - spot_type = "persistent" - spot_instance_interruption_behavior = "terminate" + spot_price = "0.1" + spot_wait_for_fulfillment = true + spot_type = "persistent" # End spot request specific attributes user_data_base64 = base64encode(local.user_data) @@ -270,28 +255,24 @@ module "ec2_spot_instance" { } enable_volume_tags = false - root_block_device = [ - { - encrypted = true - volume_type = "gp3" - throughput = 200 - volume_size = 50 - tags = { - Name = "my-root-block" - } - }, - ] - - ebs_block_device = [ - { - device_name = "/dev/sdf" - volume_type = "gp3" - volume_size = 5 - throughput = 200 - encrypted = true + root_block_device = { + encrypted = true + type = "gp3" + throughput = 200 + size = 50 + tags = { + Name = "my-root-block" + } + } + + ebs_volumes = { + "/dev/sdf" = { + size = 5 + throughput = 200 + encrypted = true # kms_key_id = aws_kms_key.this.arn # you must grant the AWSServiceRoleForEC2Spot service-linked role access to any custom KMS keys } - ] + } tags = local.tags } @@ -305,10 +286,8 @@ module "ec2_open_capacity_reservation" { name = "${local.name}-open-capacity-reservation" - ami = data.aws_ami.amazon_linux.id - instance_type = "t3.micro" + instance_type = "m4.large" subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] associate_public_ip_address = false capacity_reservation_specification = { @@ -325,10 +304,8 @@ module "ec2_targeted_capacity_reservation" { name = "${local.name}-targeted-capacity-reservation" - ami = data.aws_ami.amazon_linux.id - instance_type = "t3.micro" + instance_type = "m4.large" subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] associate_public_ip_address = false capacity_reservation_specification = { @@ -341,7 +318,7 @@ module "ec2_targeted_capacity_reservation" { } resource "aws_ec2_capacity_reservation" "open" { - instance_type = "t3.micro" + instance_type = "m4.large" instance_platform = "Linux/UNIX" availability_zone = "${local.region}a" instance_count = 1 @@ -349,84 +326,20 @@ resource "aws_ec2_capacity_reservation" "open" { } resource "aws_ec2_capacity_reservation" "targeted" { - instance_type = "t3.micro" + instance_type = "m4.large" instance_platform = "Linux/UNIX" availability_zone = "${local.region}a" instance_count = 1 instance_match_criteria = "targeted" } -################################################################################ -# EC2 Module - CPU Options -################################################################################ - -module "ec2_cpu_options" { - source = "../../" - - name = "${local.name}-cpu-options" - - ami = data.aws_ami.amazon_linux_23.id - instance_type = "c6a.xlarge" # used to set core count below and test amd_sev_snp attribute - availability_zone = element(module.vpc.azs, 0) - subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [module.security_group.security_group_id] - placement_group = aws_placement_group.web.id - associate_public_ip_address = true - disable_api_stop = false - - create_iam_instance_profile = true - iam_role_description = "IAM role for EC2 instance" - iam_role_policies = { - AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess" - } - - user_data_base64 = base64encode(local.user_data) - user_data_replace_on_change = true - - cpu_options = { - core_count = 2 - threads_per_core = 1 - amd_sev_snp = "enabled" - } - enable_volume_tags = false - root_block_device = [ - { - encrypted = true - volume_type = "gp3" - throughput = 200 - volume_size = 50 - tags = { - Name = "my-root-block" - } - }, - ] - - ebs_block_device = [ - { - device_name = "/dev/sdf" - volume_type = "gp3" - volume_size = 5 - throughput = 200 - encrypted = true - kms_key_id = aws_kms_key.this.arn - tags = { - MountPoint = "/mnt/data" - } - } - ] - - instance_tags = { Persistence = "09:00-18:00" } - - tags = local.tags -} - ################################################################################ # Supporting Resources ################################################################################ module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr @@ -441,26 +354,12 @@ module "vpc" { data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"] - - filter { - name = "name" - values = ["amzn-ami-hvm-*-x86_64-gp2"] - } -} - -data "aws_ami" "amazon_linux_23" { - most_recent = true - owners = ["amazon"] - - filter { - name = "name" - values = ["al2023-ami-2023*-x86_64"] - } + name_regex = "^al2023-ami-2023.*-x86_64" } module "security_group" { source = "terraform-aws-modules/security-group/aws" - version = "~> 4.0" + version = "~> 5.0" name = local.name description = "Security group for example usage with EC2 instance" @@ -468,7 +367,6 @@ module "security_group" { ingress_cidr_blocks = ["0.0.0.0/0"] ingress_rules = ["http-80-tcp", "all-icmp"] - egress_rules = ["all-all"] tags = local.tags } @@ -482,5 +380,6 @@ resource "aws_kms_key" "this" { } resource "aws_network_interface" "this" { - subnet_id = element(module.vpc.private_subnets, 0) + subnet_id = element(module.vpc.private_subnets, 0) + security_groups = [module.security_group.security_group_id] } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index fd4d1167..f648e20c 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66" + version = ">= 6.0" } } } diff --git a/examples/session-manager/README.md b/examples/session-manager/README.md index 07552482..b778d379 100644 --- a/examples/session-manager/README.md +++ b/examples/session-manager/README.md @@ -29,23 +29,22 @@ Note that this example may create resources which can cost money. Run `terraform | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66 | +| [terraform](#requirement\_terraform) | >= 1.10 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [ec2](#module\_ec2) | ../../ | n/a | -| [security\_group\_instance](#module\_security\_group\_instance) | terraform-aws-modules/security-group/aws | ~> 5.0 | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | -| [vpc\_endpoints](#module\_vpc\_endpoints) | terraform-aws-modules/vpc/aws//modules/vpc-endpoints | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | +| [vpc\_endpoints](#module\_vpc\_endpoints) | terraform-aws-modules/vpc/aws//modules/vpc-endpoints | ~> 6.0 | ## Resources diff --git a/examples/session-manager/main.tf b/examples/session-manager/main.tf index b7896da3..e58cb4e2 100644 --- a/examples/session-manager/main.tf +++ b/examples/session-manager/main.tf @@ -27,8 +27,14 @@ module "ec2" { name = local.name - subnet_id = element(module.vpc.intra_subnets, 0) - vpc_security_group_ids = [module.security_group_instance.security_group_id] + subnet_id = element(module.vpc.intra_subnets, 0) + security_group_egress_rules = { + vpc-endpoints = { + description = "Allow outbound traffic to VPC endpoints" + cidr_ipv4 = module.vpc.vpc_cidr_block + from_port = 443 + } + } create_iam_instance_profile = true iam_role_description = "IAM role for EC2 instance" @@ -45,7 +51,7 @@ module "ec2" { module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr @@ -56,23 +62,9 @@ module "vpc" { tags = local.tags } -module "security_group_instance" { - source = "terraform-aws-modules/security-group/aws" - version = "~> 5.0" - - name = "${local.name}-ec2" - description = "Security Group for EC2 Instance Egress" - - vpc_id = module.vpc.vpc_id - - egress_rules = ["https-443-tcp"] - - tags = local.tags -} - module "vpc_endpoints" { source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints" - version = "~> 5.0" + version = "~> 6.0" vpc_id = module.vpc.vpc_id diff --git a/examples/session-manager/versions.tf b/examples/session-manager/versions.tf index fd4d1167..f648e20c 100644 --- a/examples/session-manager/versions.tf +++ b/examples/session-manager/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66" + version = ">= 6.0" } } } diff --git a/examples/volume-attachment/README.md b/examples/volume-attachment/README.md deleted file mode 100644 index 181dd3f9..00000000 --- a/examples/volume-attachment/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# EC2 instance with EBS volume attachment - -Configuration in this directory creates EC2 instances, EBS volume and attach it together. - -This example outputs instance id and EBS volume id. - -## Usage - -To run this example you need to execute: - -```bash -$ terraform init -$ terraform plan -$ terraform apply -``` - -Note that this example may create resources which can cost money. Run `terraform destroy` when you don't need these resources. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 4.66 | - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [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 | ~> 4.0 | - -## Resources - -| Name | Type | -|------|------| -| [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 - -No inputs. - -## Outputs - -| Name | Description | -|------|-------------| -| [ec2\_arn](#output\_ec2\_arn) | The ARN of the instance | -| [ec2\_availability\_zone](#output\_ec2\_availability\_zone) | The availability zone of the created spot instance | -| [ec2\_capacity\_reservation\_specification](#output\_ec2\_capacity\_reservation\_specification) | Capacity reservation specification of the instance | -| [ec2\_id](#output\_ec2\_id) | The ID of the instance | -| [ec2\_instance\_state](#output\_ec2\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` | -| [ec2\_primary\_network\_interface\_id](#output\_ec2\_primary\_network\_interface\_id) | The ID of the instance's primary network interface | -| [ec2\_private\_dns](#output\_ec2\_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 | -| [ec2\_public\_dns](#output\_ec2\_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 | -| [ec2\_public\_ip](#output\_ec2\_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 | -| [ec2\_tags\_all](#output\_ec2\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block | - diff --git a/examples/volume-attachment/main.tf b/examples/volume-attachment/main.tf deleted file mode 100644 index 877f2140..00000000 --- a/examples/volume-attachment/main.tf +++ /dev/null @@ -1,94 +0,0 @@ -provider "aws" { - region = local.region -} - -data "aws_availability_zones" "available" {} - -locals { - 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 = { - 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 = module.ec2.availability_zone - size = 1 - - tags = local.tags -} - -################################################################################ -# Supporting Resources -################################################################################ - -module "vpc" { - source = "terraform-aws-modules/vpc/aws" - version = "~> 4.0" - - name = local.name - cidr = local.vpc_cidr - - 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 -} - -data "aws_ami" "amazon_linux" { - most_recent = true - owners = ["amazon"] - - filter { - name = "name" - values = ["amzn-ami-hvm-*-x86_64-gp2"] - } -} - -module "security_group" { - source = "terraform-aws-modules/security-group/aws" - version = "~> 4.0" - - name = local.name - description = "Security group for example usage with EC2 instance" - vpc_id = module.vpc.vpc_id - - ingress_cidr_blocks = ["0.0.0.0/0"] - ingress_rules = ["http-80-tcp", "all-icmp"] - egress_rules = ["all-all"] - - tags = local.tags -} diff --git a/examples/volume-attachment/outputs.tf b/examples/volume-attachment/outputs.tf deleted file mode 100644 index a927767b..00000000 --- a/examples/volume-attachment/outputs.tf +++ /dev/null @@ -1,50 +0,0 @@ -# EC2 -output "ec2_id" { - description = "The ID of the instance" - value = module.ec2.id -} - -output "ec2_arn" { - description = "The ARN of the instance" - value = module.ec2.arn -} - -output "ec2_capacity_reservation_specification" { - description = "Capacity reservation specification of the instance" - value = module.ec2.capacity_reservation_specification -} - -output "ec2_instance_state" { - description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" - value = module.ec2.instance_state -} - -output "ec2_primary_network_interface_id" { - description = "The ID of the instance's primary network interface" - value = module.ec2.primary_network_interface_id -} - -output "ec2_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 = module.ec2.private_dns -} - -output "ec2_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 = module.ec2.public_dns -} - -output "ec2_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 = module.ec2.public_ip -} - -output "ec2_tags_all" { - description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" - value = module.ec2.tags_all -} - -output "ec2_availability_zone" { - description = "The availability zone of the created spot instance" - value = module.ec2.availability_zone -} diff --git a/examples/volume-attachment/variables.tf b/examples/volume-attachment/variables.tf deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/volume-attachment/versions.tf b/examples/volume-attachment/versions.tf deleted file mode 100644 index fd4d1167..00000000 --- a/examples/volume-attachment/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.66" - } - } -} diff --git a/main.tf b/main.tf index 01a520d5..b116b406 100644 --- a/main.tf +++ b/main.tf @@ -6,11 +6,33 @@ locals { is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a|4g){1}\\..*$/", "1") == "1" ? true : false ami = try(coalesce(var.ami, try(nonsensitive(data.aws_ssm_parameter.this[0].value), null)), null) + + instance_tags = merge( + var.tags, + var.instance_tags, + { "Name" = var.name }, + ) + + instance_id = try( + aws_instance.this[0].id, + aws_instance.ignore_ami[0].id, + aws_spot_instance_request.this[0].spot_instance_id, + null, + ) + + instance_availability_zone = try( + aws_instance.this[0].availability_zone, + aws_instance.ignore_ami[0].availability_zone, + aws_spot_instance_request.this[0].availability_zone, + null, + ) } data "aws_ssm_parameter" "this" { count = local.create && var.ami == null ? 1 : 0 + region = var.region + name = var.ami_ssm_parameter } @@ -21,175 +43,187 @@ data "aws_ssm_parameter" "this" { resource "aws_instance" "this" { count = local.create && !var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 - ami = local.ami - 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 + region = var.region + ami = local.ami 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 "cpu_options" { - for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] - - content { - core_count = try(cpu_options.value.core_count, null) - threads_per_core = try(cpu_options.value.threads_per_core, null) - amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) - } - } + availability_zone = var.availability_zone dynamic "capacity_reservation_specification" { - for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + for_each = var.capacity_reservation_specification != null ? [var.capacity_reservation_specification] : [] content { - capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + capacity_reservation_preference = capacity_reservation_specification.value.capacity_reservation_preference dynamic "capacity_reservation_target" { - for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + for_each = capacity_reservation_specification.value.capacity_reservation_target != null ? [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) + capacity_reservation_id = capacity_reservation_target.value.capacity_reservation_id + capacity_reservation_resource_group_arn = capacity_reservation_target.value.capacity_reservation_resource_group_arn } } } } - dynamic "root_block_device" { - for_each = var.root_block_device + dynamic "cpu_options" { + for_each = var.cpu_options != null ? [var.cpu_options] : [] 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) + amd_sev_snp = cpu_options.value.amd_sev_snp + core_count = cpu_options.value.core_count + threads_per_core = cpu_options.value.threads_per_core } } - dynamic "ebs_block_device" { - for_each = var.ebs_block_device + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } - 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) - } + disable_api_stop = var.disable_api_stop + disable_api_termination = var.disable_api_termination + + # `ebs_block_device` managed by separate resource + + ebs_optimized = var.ebs_optimized + + enclave_options { + enabled = var.enclave_options_enabled } + enable_primary_ipv6 = var.enable_primary_ipv6 + dynamic "ephemeral_block_device" { - for_each = var.ephemeral_block_device + for_each = var.ephemeral_block_device != null ? var.ephemeral_block_device : {} content { 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) + no_device = ephemeral_block_device.value.no_device + virtual_name = ephemeral_block_device.value.virtual_name } } - dynamic "metadata_options" { - for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + get_password_data = var.get_password_data + hibernation = var.hibernation + host_id = var.host_id + host_resource_group_arn = var.host_resource_group_arn + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + + dynamic "instance_market_options" { + for_each = var.instance_market_options != null ? [var.instance_market_options] : [] content { - http_endpoint = try(metadata_options.value.http_endpoint, "enabled") - http_tokens = try(metadata_options.value.http_tokens, "required") - 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) + market_type = instance_market_options.value.market_type + + dynamic "spot_options" { + for_each = instance_market_options.value.spot_options != null ? [instance_market_options.value.spot_options] : [] + + content { + instance_interruption_behavior = spot_options.value.instance_interruption_behavior + max_price = spot_options.value.max_price + spot_instance_type = spot_options.value.spot_instance_type + valid_until = spot_options.value.valid_until + } + } } } - dynamic "network_interface" { - for_each = var.network_interface + instance_type = var.instance_type + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + key_name = var.key_name + + dynamic "launch_template" { + for_each = var.launch_template != null ? [var.launch_template] : [] 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) + id = launch_template.value.id + name = launch_template.value.name + version = launch_template.value.version } } - dynamic "private_dns_name_options" { - for_each = length(var.private_dns_name_options) > 0 ? [var.private_dns_name_options] : [] + dynamic "maintenance_options" { + for_each = var.maintenance_options != null ? [var.maintenance_options] : [] content { - hostname_type = try(private_dns_name_options.value.hostname_type, null) - enable_resource_name_dns_a_record = try(private_dns_name_options.value.enable_resource_name_dns_a_record, null) - enable_resource_name_dns_aaaa_record = try(private_dns_name_options.value.enable_resource_name_dns_aaaa_record, null) + auto_recovery = maintenance_options.value.auto_recovery } } - dynamic "launch_template" { - for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + dynamic "metadata_options" { + for_each = var.metadata_options != null ? [var.metadata_options] : [] content { - id = lookup(var.launch_template, "id", null) - name = lookup(var.launch_template, "name", null) - version = lookup(var.launch_template, "version", null) + http_endpoint = metadata_options.value.http_endpoint + http_protocol_ipv6 = metadata_options.value.http_protocol_ipv6 + http_put_response_hop_limit = metadata_options.value.http_put_response_hop_limit + http_tokens = metadata_options.value.http_tokens + instance_metadata_tags = metadata_options.value.instance_metadata_tags } } - dynamic "maintenance_options" { - for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + monitoring = var.monitoring + + dynamic "network_interface" { + for_each = var.network_interface != null ? var.network_interface : {} content { - auto_recovery = try(maintenance_options.value.auto_recovery, null) + delete_on_termination = network_interface.value.delete_on_termination + device_index = coalesce(network_interface.value.device_index, network_interface.key) + network_card_index = network_interface.value.network_card_index + network_interface_id = network_interface.value.network_interface_id + } } - enclave_options { - enabled = var.enclave_options_enabled + placement_group = var.placement_group + placement_partition_number = var.placement_partition_number + + dynamic "private_dns_name_options" { + for_each = var.private_dns_name_options != null ? [var.private_dns_name_options] : [] + + content { + enable_resource_name_dns_aaaa_record = private_dns_name_options.value.enable_resource_name_dns_aaaa_record + enable_resource_name_dns_a_record = private_dns_name_options.value.enable_resource_name_dns_a_record + hostname_type = private_dns_name_options.value.hostname_type + } } - 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 + private_ip = var.private_ip - credit_specification { - cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + dynamic "root_block_device" { + for_each = var.root_block_device != null ? [var.root_block_device] : [] + + content { + delete_on_termination = root_block_device.value.delete_on_termination + encrypted = root_block_device.value.encrypted + iops = root_block_device.value.iops + kms_key_id = root_block_device.value.kms_key_id + tags = root_block_device.value.tags + throughput = root_block_device.value.throughput + volume_size = root_block_device.value.size + volume_type = root_block_device.value.type + } } + secondary_private_ips = var.secondary_private_ips + source_dest_check = var.network_interface != null ? null : var.source_dest_check + subnet_id = var.subnet_id + tags = local.instance_tags + tenancy = var.tenancy + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + volume_tags = var.enable_volume_tags ? merge(var.tags, var.volume_tags, { "Name" = var.name }) : null + vpc_security_group_ids = var.network_interface == null ? local.vpc_security_group_ids : 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.instance_tags, var.tags) - volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null } ################################################################################ @@ -199,176 +233,188 @@ resource "aws_instance" "this" { resource "aws_instance" "ignore_ami" { count = local.create && var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 - ami = local.ami - 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 + region = var.region + ami = local.ami 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 "cpu_options" { - for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] - - content { - core_count = try(cpu_options.value.core_count, null) - threads_per_core = try(cpu_options.value.threads_per_core, null) - amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) - } - } + availability_zone = var.availability_zone dynamic "capacity_reservation_specification" { - for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + for_each = var.capacity_reservation_specification != null ? [var.capacity_reservation_specification] : [] content { - capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + capacity_reservation_preference = capacity_reservation_specification.value.capacity_reservation_preference dynamic "capacity_reservation_target" { - for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + for_each = capacity_reservation_specification.value.capacity_reservation_target != null ? [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) + capacity_reservation_id = capacity_reservation_target.value.capacity_reservation_id + capacity_reservation_resource_group_arn = capacity_reservation_target.value.capacity_reservation_resource_group_arn } } } } - dynamic "root_block_device" { - for_each = var.root_block_device + dynamic "cpu_options" { + for_each = var.cpu_options != null ? [var.cpu_options] : [] 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) + amd_sev_snp = cpu_options.value.amd_sev_snp + core_count = cpu_options.value.core_count + threads_per_core = cpu_options.value.threads_per_core } } - dynamic "ebs_block_device" { - for_each = var.ebs_block_device + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } - 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) - } + disable_api_stop = var.disable_api_stop + disable_api_termination = var.disable_api_termination + + # `ebs_block_device` managed by separate resource + + ebs_optimized = var.ebs_optimized + + enclave_options { + enabled = var.enclave_options_enabled } + enable_primary_ipv6 = var.enable_primary_ipv6 + dynamic "ephemeral_block_device" { - for_each = var.ephemeral_block_device + for_each = var.ephemeral_block_device != null ? var.ephemeral_block_device : {} content { 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) + no_device = ephemeral_block_device.value.no_device + virtual_name = ephemeral_block_device.value.virtual_name } } - dynamic "metadata_options" { - for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + get_password_data = var.get_password_data + hibernation = var.hibernation + host_id = var.host_id + host_resource_group_arn = var.host_resource_group_arn + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + + dynamic "instance_market_options" { + for_each = var.instance_market_options != null ? [var.instance_market_options] : [] content { - http_endpoint = try(metadata_options.value.http_endpoint, "enabled") - http_tokens = try(metadata_options.value.http_tokens, "required") - 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) + market_type = instance_market_options.value.market_type + + dynamic "spot_options" { + for_each = instance_market_options.value.spot_options != null ? [instance_market_options.value.spot_options] : [] + + content { + instance_interruption_behavior = spot_options.value.instance_interruption_behavior + max_price = spot_options.value.max_price + spot_instance_type = spot_options.value.spot_instance_type + valid_until = spot_options.value.valid_until + } + } } } - dynamic "network_interface" { - for_each = var.network_interface + instance_type = var.instance_type + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + key_name = var.key_name + + dynamic "launch_template" { + for_each = var.launch_template != null ? [var.launch_template] : [] 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) + id = launch_template.value.id + name = launch_template.value.name + version = launch_template.value.version } } - dynamic "private_dns_name_options" { - for_each = length(var.private_dns_name_options) > 0 ? [var.private_dns_name_options] : [] + dynamic "maintenance_options" { + for_each = var.maintenance_options != null ? [var.maintenance_options] : [] content { - hostname_type = try(private_dns_name_options.value.hostname_type, null) - enable_resource_name_dns_a_record = try(private_dns_name_options.value.enable_resource_name_dns_a_record, null) - enable_resource_name_dns_aaaa_record = try(private_dns_name_options.value.enable_resource_name_dns_aaaa_record, null) + auto_recovery = maintenance_options.value.auto_recovery } } - dynamic "launch_template" { - for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + dynamic "metadata_options" { + for_each = var.metadata_options != null ? [var.metadata_options] : [] content { - id = lookup(var.launch_template, "id", null) - name = lookup(var.launch_template, "name", null) - version = lookup(var.launch_template, "version", null) + http_endpoint = metadata_options.value.http_endpoint + http_protocol_ipv6 = metadata_options.value.http_protocol_ipv6 + http_put_response_hop_limit = metadata_options.value.http_put_response_hop_limit + http_tokens = metadata_options.value.http_tokens + instance_metadata_tags = metadata_options.value.instance_metadata_tags } } - dynamic "maintenance_options" { - for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + monitoring = var.monitoring + + dynamic "network_interface" { + for_each = var.network_interface != null ? var.network_interface : {} content { - auto_recovery = try(maintenance_options.value.auto_recovery, null) + delete_on_termination = network_interface.value.delete_on_termination + device_index = coalesce(network_interface.value.device_index, network_interface.key) + network_card_index = network_interface.value.network_card_index + network_interface_id = network_interface.value.network_interface_id + } } - enclave_options { - enabled = var.enclave_options_enabled + placement_group = var.placement_group + placement_partition_number = var.placement_partition_number + + dynamic "private_dns_name_options" { + for_each = var.private_dns_name_options != null ? [var.private_dns_name_options] : [] + + content { + enable_resource_name_dns_aaaa_record = private_dns_name_options.value.enable_resource_name_dns_aaaa_record + enable_resource_name_dns_a_record = private_dns_name_options.value.enable_resource_name_dns_a_record + hostname_type = private_dns_name_options.value.hostname_type + } } - 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 + private_ip = var.private_ip - credit_specification { - cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + dynamic "root_block_device" { + for_each = var.root_block_device != null ? [var.root_block_device] : [] + + content { + delete_on_termination = root_block_device.value.delete_on_termination + encrypted = root_block_device.value.encrypted + iops = root_block_device.value.iops + kms_key_id = root_block_device.value.kms_key_id + tags = root_block_device.value.tags + throughput = root_block_device.value.throughput + volume_size = root_block_device.value.size + volume_type = root_block_device.value.type + } } + secondary_private_ips = var.secondary_private_ips + source_dest_check = var.network_interface != null ? null : var.source_dest_check + subnet_id = var.subnet_id + tags = local.instance_tags + tenancy = var.tenancy + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + volume_tags = var.enable_volume_tags ? merge(var.tags, var.volume_tags, { "Name" = var.name }) : null + vpc_security_group_ids = var.network_interface == null ? local.vpc_security_group_ids : 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.instance_tags, var.tags) - volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null - lifecycle { ignore_changes = [ ami @@ -383,165 +429,234 @@ resource "aws_instance" "ignore_ami" { resource "aws_spot_instance_request" "this" { count = local.create && var.create_spot_instance ? 1 : 0 - ami = local.ami - 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 + region = var.region # Spot request specific attributes + instance_interruption_behavior = var.spot_instance_interruption_behavior + launch_group = var.spot_launch_group spot_price = var.spot_price - wait_for_fulfillment = var.spot_wait_for_fulfillment spot_type = var.spot_type - launch_group = var.spot_launch_group - block_duration_minutes = var.spot_block_duration_minutes - instance_interruption_behavior = var.spot_instance_interruption_behavior - valid_until = var.spot_valid_until + wait_for_fulfillment = var.spot_wait_for_fulfillment valid_from = var.spot_valid_from + valid_until = var.spot_valid_until # End spot request specific attributes - dynamic "cpu_options" { - for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] - - content { - core_count = try(cpu_options.value.core_count, null) - threads_per_core = try(cpu_options.value.threads_per_core, null) - amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) - } - } + ami = local.ami + associate_public_ip_address = var.associate_public_ip_address + availability_zone = var.availability_zone dynamic "capacity_reservation_specification" { - for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + for_each = var.capacity_reservation_specification != null ? [var.capacity_reservation_specification] : [] content { - capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + capacity_reservation_preference = capacity_reservation_specification.value.capacity_reservation_preference dynamic "capacity_reservation_target" { - for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + for_each = capacity_reservation_specification.value.capacity_reservation_target != null ? [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) + capacity_reservation_id = capacity_reservation_target.value.capacity_reservation_id + capacity_reservation_resource_group_arn = capacity_reservation_target.value.capacity_reservation_resource_group_arn } } } } - dynamic "root_block_device" { - for_each = var.root_block_device + dynamic "cpu_options" { + for_each = var.cpu_options != null ? [var.cpu_options] : [] 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) + amd_sev_snp = cpu_options.value.amd_sev_snp + core_count = cpu_options.value.core_count + threads_per_core = cpu_options.value.threads_per_core } } - dynamic "ebs_block_device" { - for_each = var.ebs_block_device + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } - 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) - } + disable_api_stop = var.disable_api_stop + disable_api_termination = var.disable_api_termination + + # `ebs_block_device` managed by separate resource + + ebs_optimized = var.ebs_optimized + + enclave_options { + enabled = var.enclave_options_enabled } + enable_primary_ipv6 = var.enable_primary_ipv6 + dynamic "ephemeral_block_device" { - for_each = var.ephemeral_block_device + for_each = var.ephemeral_block_device != null ? var.ephemeral_block_device : {} content { 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) + no_device = ephemeral_block_device.value.no_device + virtual_name = ephemeral_block_device.value.virtual_name + } + } + + get_password_data = var.get_password_data + hibernation = var.hibernation + host_id = var.host_id + host_resource_group_arn = var.host_resource_group_arn + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + + instance_type = var.instance_type + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + key_name = var.key_name + + dynamic "launch_template" { + for_each = var.launch_template != null ? [var.launch_template] : [] + + content { + id = launch_template.value.id + name = launch_template.value.name + version = launch_template.value.version + } + } + + dynamic "maintenance_options" { + for_each = var.maintenance_options != null ? [var.maintenance_options] : [] + + content { + auto_recovery = maintenance_options.value.auto_recovery } } dynamic "metadata_options" { - for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + for_each = var.metadata_options != null ? [var.metadata_options] : [] content { - http_endpoint = try(metadata_options.value.http_endpoint, "enabled") - http_tokens = try(metadata_options.value.http_tokens, "required") - 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) + http_endpoint = metadata_options.value.http_endpoint + http_protocol_ipv6 = metadata_options.value.http_protocol_ipv6 + http_put_response_hop_limit = metadata_options.value.http_put_response_hop_limit + http_tokens = metadata_options.value.http_tokens + instance_metadata_tags = metadata_options.value.instance_metadata_tags } } + monitoring = var.monitoring + dynamic "network_interface" { - for_each = var.network_interface + for_each = var.network_interface != null ? 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) + delete_on_termination = network_interface.value.delete_on_termination + device_index = try(network_interface.value.device_index, network_interface.key) + network_card_index = network_interface.value.network_card_index + network_interface_id = network_interface.value.network_interface_id + } } - dynamic "launch_template" { - for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + placement_group = var.placement_group + placement_partition_number = var.placement_partition_number + + dynamic "private_dns_name_options" { + for_each = var.private_dns_name_options != null ? [var.private_dns_name_options] : [] content { - id = lookup(var.launch_template, "id", null) - name = lookup(var.launch_template, "name", null) - version = lookup(var.launch_template, "version", null) + enable_resource_name_dns_aaaa_record = private_dns_name_options.value.enable_resource_name_dns_aaaa_record + enable_resource_name_dns_a_record = private_dns_name_options.value.enable_resource_name_dns_a_record + hostname_type = private_dns_name_options.value.hostname_type } } - enclave_options { - enabled = var.enclave_options_enabled - } + private_ip = var.private_ip - source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check - disable_api_termination = var.disable_api_termination - instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior - placement_group = var.placement_group - tenancy = var.tenancy - host_id = var.host_id + dynamic "root_block_device" { + for_each = var.root_block_device != null ? [var.root_block_device] : [] - credit_specification { - cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + content { + delete_on_termination = root_block_device.value.delete_on_termination + encrypted = root_block_device.value.encrypted + iops = root_block_device.value.iops + kms_key_id = root_block_device.value.kms_key_id + tags = root_block_device.value.tags + throughput = root_block_device.value.throughput + volume_size = root_block_device.value.size + volume_type = root_block_device.value.type + } } + secondary_private_ips = var.secondary_private_ips + source_dest_check = var.network_interface != null ? null : var.source_dest_check + subnet_id = var.subnet_id + tags = local.instance_tags + tenancy = var.tenancy + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + volume_tags = var.enable_volume_tags ? merge(var.tags, var.volume_tags, { "Name" = var.name }) : null + vpc_security_group_ids = var.network_interface == null ? local.vpc_security_group_ids : null + timeouts { create = try(var.timeouts.create, null) delete = try(var.timeouts.delete, null) } - tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) - volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null + lifecycle { + ignore_changes = [ + ebs_block_device, + ] + } +} + +resource "aws_ec2_tag" "spot_instance" { + for_each = { for k, v in local.instance_tags : k => v if local.create && var.create_spot_instance } + + resource_id = aws_spot_instance_request.this[0].spot_instance_id + key = each.key + value = each.value +} + +################################################################################ +# EBS Volume(s) +################################################################################ + +resource "aws_ebs_volume" "this" { + for_each = var.create && var.ebs_volumes != null ? var.ebs_volumes : {} + + region = var.region + + availability_zone = local.instance_availability_zone + encrypted = each.value.encrypted + final_snapshot = each.value.final_snapshot + iops = each.value.iops + kms_key_id = each.value.kms_key_id + multi_attach_enabled = each.value.multi_attach_enabled + outpost_arn = each.value.outpost_arn + size = each.value.size + snapshot_id = each.value.snapshot_id + + tags = merge( + var.tags, + var.volume_tags, + { "Name" = "${var.name}-${each.key}" }, + each.value.tags, + ) + + throughput = each.value.throughput + type = each.value.type +} + +resource "aws_volume_attachment" "this" { + for_each = var.create && var.ebs_volumes != null ? var.ebs_volumes : {} + + region = var.region + + device_name = coalesce(each.value.device_name, each.key) + instance_id = local.instance_id + volume_id = aws_ebs_volume.this[each.key].id + force_detach = each.value.force_detach + skip_destroy = each.value.skip_destroy + stop_instance_before_detaching = each.value.stop_instance_before_detaching } ################################################################################ @@ -604,6 +719,94 @@ resource "aws_iam_instance_profile" "this" { } } +################################################################################ +# Security Group +################################################################################ + +locals { + create_security_group = var.create && var.create_security_group && var.network_interface == null + security_group_name = try(coalesce(var.security_group_name, var.name), "") + + vpc_security_group_ids = local.create_security_group ? concat(var.vpc_security_group_ids, [aws_security_group.this[0].id]) : var.vpc_security_group_ids +} + +data "aws_subnet" "this" { + count = local.create_security_group ? 1 : 0 + + region = var.region + + id = var.subnet_id +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + region = var.region + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = coalesce(var.security_group_vpc_id, data.aws_subnet.this[0].vpc_id) + + tags = merge( + var.tags, + { "Name" = local.security_group_name }, + var.security_group_tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_vpc_security_group_egress_rule" "this" { + for_each = local.create_security_group && var.security_group_egress_rules != null ? var.security_group_egress_rules : {} + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = try(coalesce(each.value.from_port, each.value.to_port), null) + ip_protocol = each.value.ip_protocol + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = "${var.name}-${each.key}" }, + each.value.tags, + ) + + to_port = try(coalesce(each.value.to_port, each.value.from_port), null) +} + +resource "aws_vpc_security_group_ingress_rule" "this" { + for_each = local.create_security_group && var.security_group_ingress_rules != null ? var.security_group_ingress_rules : {} + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = try(coalesce(each.value.from_port, each.value.to_port), null) + ip_protocol = each.value.ip_protocol + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = "${var.name}-${each.key}" }, + each.value.tags, + ) + + to_port = try(coalesce(each.value.to_port, each.value.from_port), null) +} + ################################################################################ # Elastic IP ################################################################################ @@ -611,12 +814,10 @@ resource "aws_iam_instance_profile" "this" { resource "aws_eip" "this" { count = local.create && var.create_eip && !var.create_spot_instance ? 1 : 0 - instance = try( - aws_instance.this[0].id, - aws_instance.ignore_ami[0].id, - ) + region = var.region - domain = var.eip_domain + domain = var.eip_domain + instance = local.instance_id tags = merge(var.tags, var.eip_tags) } diff --git a/outputs.tf b/outputs.tf index 3f57b650..3b6a9818 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,11 +1,6 @@ output "id" { description = "The ID of the instance" - value = try( - aws_instance.this[0].id, - aws_instance.ignore_ami[0].id, - aws_spot_instance_request.this[0].id, - null, - ) + value = local.instance_id } output "arn" { @@ -156,12 +151,16 @@ output "ami" { output "availability_zone" { description = "The availability zone of the created instance" - value = try( - aws_instance.this[0].availability_zone, - aws_instance.ignore_ami[0].availability_zone, - aws_spot_instance_request.this[0].availability_zone, - null, - ) + value = local.instance_availability_zone +} + +################################################################################ +# EBS Volume(s) +################################################################################ + +output "ebs_volumes" { + description = "Map of EBS volumes created and their attributes" + value = aws_ebs_volume.this } ################################################################################ @@ -201,6 +200,7 @@ output "iam_instance_profile_unique" { ################################################################################ # Block Devices ################################################################################ + output "root_block_device" { description = "Root block device information" value = try( diff --git a/variables.tf b/variables.tf index e59a9dc8..f354c2ce 100644 --- a/variables.tf +++ b/variables.tf @@ -10,18 +10,28 @@ variable "name" { default = "" } -variable "ami_ssm_parameter" { - description = "SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see [reference](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html)" +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" type = string - default = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" + default = null } +################################################################################ +# Instance +################################################################################ + variable "ami" { description = "ID of AMI to use for the instance" type = string default = null } +variable "ami_ssm_parameter" { + description = "SSM parameter name for the AMI ID. For Amazon Linux AMI SSM parameters see [reference](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html)" + type = string + default = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64" +} + 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 @@ -34,12 +44,6 @@ variable "associate_public_ip_address" { default = null } -variable "maintenance_options" { - description = "The maintenance options for the instance" - type = any - default = {} -} - variable "availability_zone" { description = "AZ to start the instance in" type = string @@ -48,8 +52,24 @@ variable "availability_zone" { variable "capacity_reservation_specification" { description = "Describes an instance's Capacity Reservation targeting option" - type = any - default = {} + type = object({ + capacity_reservation_preference = optional(string) + capacity_reservation_target = optional(object({ + capacity_reservation_id = optional(string) + capacity_reservation_resource_group_arn = optional(string) + })) + }) + default = null +} + +variable "cpu_options" { + description = "Defines CPU options to apply to the instance at launch time." + type = object({ + amd_sev_snp = optional(string) + core_count = optional(number) + threads_per_core = optional(number) + }) + default = null } variable "cpu_credits" { @@ -64,10 +84,10 @@ variable "disable_api_termination" { default = null } -variable "ebs_block_device" { - description = "Additional EBS block devices to attach to the instance" - type = list(any) - default = [] +variable "disable_api_stop" { + description = "If true, enables EC2 Instance Stop Protection" + type = bool + default = null } variable "ebs_optimized" { @@ -82,10 +102,20 @@ variable "enclave_options_enabled" { default = null } +variable "enable_primary_ipv6" { + description = "Whether to assign a primary IPv6 Global Unicast Address (GUA) to the instance when launched in a dual-stack or IPv6-only subnet" + type = bool + default = null +} + variable "ephemeral_block_device" { description = "Customize Ephemeral (also known as Instance Store) volumes on the instance" - type = list(map(string)) - default = [] + type = map(object({ + device_name = string + no_device = optional(bool) + virtual_name = optional(string) + })) + default = null } variable "get_password_data" { @@ -106,6 +136,12 @@ variable "host_id" { default = null } +variable "host_resource_group_arn" { + description = "ARN of the host resource group in which to launch the instances. If you specify an ARN, omit the `tenancy` parameter or set it to `host`" + type = string + default = null +} + variable "iam_instance_profile" { description = "IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile" type = string @@ -118,18 +154,26 @@ variable "instance_initiated_shutdown_behavior" { default = null } +variable "instance_market_options" { + description = "The market (purchasing) option for the instance. If set, overrides the `create_spot_instance` variable" + type = object({ + market_type = optional(string) + spot_options = optional(object({ + instance_interruption_behavior = optional(string) + max_price = optional(string) + spot_instance_type = optional(string) + valid_until = optional(string) + })) + }) + default = null +} + variable "instance_type" { description = "The type of instance to start" type = string default = "t3.micro" } -variable "instance_tags" { - description = "Additional tags for the instance" - type = map(string) - default = {} -} - variable "ipv6_address_count" { description = "A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet" type = number @@ -150,17 +194,35 @@ 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 = {} + type = object({ + id = optional(string) + name = optional(string) + version = optional(string) + }) + default = null +} + +variable "maintenance_options" { + description = "The maintenance options for the instance" + type = object({ + auto_recovery = optional(string) + }) + default = null } variable "metadata_options" { description = "Customize the metadata options of the instance" - type = map(string) + type = object({ + http_endpoint = optional(string, "enabled") + http_protocol_ipv6 = optional(string) + http_put_response_hop_limit = optional(number, 1) + http_tokens = optional(string, "required") + instance_metadata_tags = optional(string) + }) default = { - "http_endpoint" = "enabled" - "http_put_response_hop_limit" = 1 - "http_tokens" = "required" + http_endpoint = "enabled" + http_put_response_hop_limit = 1 + http_tokens = "required" } } @@ -172,14 +234,13 @@ variable "monitoring" { variable "network_interface" { description = "Customize network interfaces to be attached at instance boot time" - type = list(map(string)) - default = [] -} - -variable "private_dns_name_options" { - description = "Customize the private DNS name options of the instance" - type = map(string) - default = {} + type = map(object({ + delete_on_termination = optional(bool) + device_index = optional(number) # Will fall back to use map key as device index + network_card_index = optional(number) + network_interface_id = string + })) + default = null } variable "placement_group" { @@ -188,6 +249,22 @@ variable "placement_group" { default = null } +variable "placement_partition_number" { + description = "Number of the partition the instance is in. Valid only if the `aws_placement_group` resource's `strategy` argument is set to `partition`" + type = number + default = null +} + +variable "private_dns_name_options" { + description = "Customize the private DNS name options of the instance" + type = object({ + enable_resource_name_dns_a_record = optional(bool) + enable_resource_name_dns_aaaa_record = optional(bool) + hostname_type = optional(string) + }) + default = null +} + variable "private_ip" { description = "Private IP address to associate with the instance in a VPC" type = string @@ -196,8 +273,17 @@ variable "private_ip" { variable "root_block_device" { description = "Customize details about the root block device of the instance. See Block Devices below for details" - type = list(any) - default = [] + type = object({ + delete_on_termination = optional(bool) + encrypted = optional(bool) + iops = optional(number) + kms_key_id = optional(string) + tags = optional(map(string), {}) + throughput = optional(number) + size = optional(number) + type = optional(string) + }) + default = null } variable "secondary_private_ips" { @@ -224,6 +310,12 @@ variable "tags" { default = {} } +variable "instance_tags" { + description = "Additional tags for the instance" + type = map(string) + default = {} +} + variable "tenancy" { description = "The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host" type = string @@ -263,7 +355,7 @@ variable "enable_volume_tags" { variable "vpc_security_group_ids" { description = "A list of security group IDs to associate with" type = list(string) - default = null + default = [] } variable "timeouts" { @@ -272,63 +364,48 @@ variable "timeouts" { default = {} } -variable "cpu_options" { - description = "Defines CPU options to apply to the instance at launch time." - type = any - default = {} -} - -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 - 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)" - type = number - default = null -} +################################################################################ +# Spot Instance Request +################################################################################ -# Spot instance request variable "create_spot_instance" { description = "Depicts if the instance is a spot instance" type = bool default = false } -variable "spot_price" { - description = "The maximum price to request on the spot market. Defaults to on-demand price" +variable "spot_instance_interruption_behavior" { + description = "Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate`" type = string default = null } -variable "spot_wait_for_fulfillment" { - description = "If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached" - type = bool +variable "spot_launch_group" { + description = "A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually" + type = string default = null } -variable "spot_type" { - description = "If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent`" +variable "spot_price" { + description = "The maximum price to request on the spot market. Defaults to on-demand price" type = string default = null } -variable "spot_launch_group" { - description = "A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually" +variable "spot_type" { + description = "If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent`" type = string default = null } -variable "spot_block_duration_minutes" { - description = "The required duration for the Spot instances, in minutes. This value must be a multiple of 60 (60, 120, 180, 240, 300, or 360)" - type = number +variable "spot_wait_for_fulfillment" { + description = "If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached" + type = bool default = null } -variable "spot_instance_interruption_behavior" { - description = "Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate`" +variable "spot_valid_from" { + description = "The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)" type = string default = null } @@ -339,22 +416,31 @@ variable "spot_valid_until" { default = null } -variable "spot_valid_from" { - description = "The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)" - type = string - default = null -} - -variable "disable_api_stop" { - description = "If true, enables EC2 Instance Stop Protection" - type = bool - default = null +################################################################################ +# EBS Volume(s) +################################################################################ -} -variable "putin_khuylo" { - description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" - type = bool - default = true +variable "ebs_volumes" { + description = "Additional EBS volumes to attach to the instance" + type = map(object({ + encrypted = optional(bool) + final_snapshot = optional(bool) + iops = optional(number) + kms_key_id = optional(string) + multi_attach_enabled = optional(bool) + outpost_arn = optional(string) + size = optional(number) + snapshot_id = optional(string) + tags = optional(map(string), {}) + throughput = optional(number) + type = optional(string, "gp3") + # Attachment + device_name = optional(string) # Will fall back to use map key as device name + force_detach = optional(bool) + skip_destroy = optional(bool) + stop_instance_before_detaching = optional(bool) + })) + default = null } ################################################################################ @@ -409,6 +495,89 @@ variable "iam_role_tags" { default = {} } +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines whether a security group will be created" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name` or `name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description of the security group" + type = string + default = null +} + +variable "security_group_vpc_id" { + description = "VPC ID to create the security group in. If not set, the security group will be created in the default VPC" + type = string + default = null +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} + +variable "security_group_egress_rules" { + description = "Egress rules to add to the security group" + type = map(object({ + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(number) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(number) + })) + default = { + ipv4_default = { + cidr_ipv4 = "0.0.0.0/0" + description = "Allow all IPv4 traffic" + ip_protocol = "-1" + } + ipv6_default = { + cidr_ipv6 = "::/0" + description = "Allow all IPv6 traffic" + ip_protocol = "-1" + } + } +} + +variable "security_group_ingress_rules" { + description = "Egress rules to add to the security group" + type = map(object({ + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(number) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(number) + })) + default = null +} + ################################################################################ # Elastic IP ################################################################################ @@ -430,3 +599,9 @@ variable "eip_tags" { type = map(string) default = {} } + +variable "putin_khuylo" { + description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" + type = bool + default = true +} diff --git a/versions.tf b/versions.tf index fd4d1167..f648e20c 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66" + version = ">= 6.0" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 048da569..fe9fc307 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -4,30 +4,31 @@ module "wrapper" { for_each = var.items ami = try(each.value.ami, var.defaults.ami, null) - 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_ssm_parameter = try(each.value.ami_ssm_parameter, var.defaults.ami_ssm_parameter, "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64") 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, {}) - cpu_core_count = try(each.value.cpu_core_count, var.defaults.cpu_core_count, 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) - cpu_options = try(each.value.cpu_options, var.defaults.cpu_options, {}) - cpu_threads_per_core = try(each.value.cpu_threads_per_core, var.defaults.cpu_threads_per_core, null) + cpu_options = try(each.value.cpu_options, var.defaults.cpu_options, null) create = try(each.value.create, var.defaults.create, true) create_eip = try(each.value.create_eip, var.defaults.create_eip, false) create_iam_instance_profile = try(each.value.create_iam_instance_profile, var.defaults.create_iam_instance_profile, false) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) create_spot_instance = try(each.value.create_spot_instance, var.defaults.create_spot_instance, false) disable_api_stop = try(each.value.disable_api_stop, var.defaults.disable_api_stop, 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) + ebs_volumes = try(each.value.ebs_volumes, var.defaults.ebs_volumes, null) eip_domain = try(each.value.eip_domain, var.defaults.eip_domain, "vpc") eip_tags = try(each.value.eip_tags, var.defaults.eip_tags, {}) + enable_primary_ipv6 = try(each.value.enable_primary_ipv6, var.defaults.enable_primary_ipv6, null) enable_volume_tags = try(each.value.enable_volume_tags, var.defaults.enable_volume_tags, true) 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, []) + ephemeral_block_device = try(each.value.ephemeral_block_device, var.defaults.ephemeral_block_device, null) 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) + host_resource_group_arn = try(each.value.host_resource_group_arn, var.defaults.host_resource_group_arn, null) iam_instance_profile = try(each.value.iam_instance_profile, var.defaults.iam_instance_profile, null) iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) @@ -38,29 +39,49 @@ module "wrapper" { iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) ignore_ami_changes = try(each.value.ignore_ami_changes, var.defaults.ignore_ami_changes, false) instance_initiated_shutdown_behavior = try(each.value.instance_initiated_shutdown_behavior, var.defaults.instance_initiated_shutdown_behavior, null) + instance_market_options = try(each.value.instance_market_options, var.defaults.instance_market_options, null) instance_tags = try(each.value.instance_tags, var.defaults.instance_tags, {}) 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, {}) - maintenance_options = try(each.value.maintenance_options, var.defaults.maintenance_options, {}) + launch_template = try(each.value.launch_template, var.defaults.launch_template, null) + maintenance_options = try(each.value.maintenance_options, var.defaults.maintenance_options, null) metadata_options = try(each.value.metadata_options, var.defaults.metadata_options, { - "http_endpoint" = "enabled" - "http_put_response_hop_limit" = 1 - "http_tokens" = "required" + http_endpoint = "enabled" + http_put_response_hop_limit = 1 + http_tokens = "required" }) - monitoring = try(each.value.monitoring, var.defaults.monitoring, null) - name = try(each.value.name, var.defaults.name, "") - network_interface = try(each.value.network_interface, var.defaults.network_interface, []) - placement_group = try(each.value.placement_group, var.defaults.placement_group, null) - private_dns_name_options = try(each.value.private_dns_name_options, var.defaults.private_dns_name_options, {}) - private_ip = try(each.value.private_ip, var.defaults.private_ip, null) - putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) - 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) + monitoring = try(each.value.monitoring, var.defaults.monitoring, null) + name = try(each.value.name, var.defaults.name, "") + network_interface = try(each.value.network_interface, var.defaults.network_interface, null) + placement_group = try(each.value.placement_group, var.defaults.placement_group, null) + placement_partition_number = try(each.value.placement_partition_number, var.defaults.placement_partition_number, null) + private_dns_name_options = try(each.value.private_dns_name_options, var.defaults.private_dns_name_options, null) + private_ip = try(each.value.private_ip, var.defaults.private_ip, null) + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) + region = try(each.value.region, var.defaults.region, null) + root_block_device = try(each.value.root_block_device, var.defaults.root_block_device, null) + secondary_private_ips = try(each.value.secondary_private_ips, var.defaults.secondary_private_ips, null) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, { + ipv4_default = { + cidr_ipv4 = "0.0.0.0/0" + description = "Allow all IPv4 traffic" + ip_protocol = "-1" + } + ipv6_default = { + cidr_ipv6 = "::/0" + description = "Allow all IPv6 traffic" + ip_protocol = "-1" + } + }) + security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, null) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + security_group_vpc_id = try(each.value.security_group_vpc_id, var.defaults.security_group_vpc_id, null) source_dest_check = try(each.value.source_dest_check, var.defaults.source_dest_check, 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_launch_group = try(each.value.spot_launch_group, var.defaults.spot_launch_group, null) spot_price = try(each.value.spot_price, var.defaults.spot_price, null) @@ -76,5 +97,5 @@ module "wrapper" { 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, {}) - vpc_security_group_ids = try(each.value.vpc_security_group_ids, var.defaults.vpc_security_group_ids, null) + vpc_security_group_ids = try(each.value.vpc_security_group_ids, var.defaults.vpc_security_group_ids, []) } diff --git a/wrappers/versions.tf b/wrappers/versions.tf index fd4d1167..f648e20c 100644 --- a/wrappers/versions.tf +++ b/wrappers/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.10" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66" + version = ">= 6.0" } } }