From 3fae908945da82dc84105c728d744e729e725bf3 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 4 Aug 2024 18:36:40 +0700 Subject: [PATCH 01/29] feat(aws_instance): add instance market options block --- README.md | 4 + docs/terraform.md | 6 + examples/complete/versions.tf | 2 +- examples/external-eni/versions.tf | 2 +- examples/spot-instance/context.tf | 279 ++++++++++++++++++ .../spot-instance/fixtures.us-east-2.tfvars | 70 +++++ examples/spot-instance/main.tf | 96 ++++++ examples/spot-instance/outputs.tf | 124 ++++++++ examples/spot-instance/variables.tf | 84 ++++++ examples/spot-instance/versions.tf | 14 + main.tf | 20 ++ outputs.tf | 10 + variables.tf | 26 ++ 13 files changed, 735 insertions(+), 2 deletions(-) create mode 100644 examples/spot-instance/context.tf create mode 100644 examples/spot-instance/fixtures.us-east-2.tfvars create mode 100644 examples/spot-instance/main.tf create mode 100644 examples/spot-instance/outputs.tf create mode 100644 examples/spot-instance/variables.tf create mode 100644 examples/spot-instance/versions.tf diff --git a/README.md b/README.md index e334a1d..73d66e3 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ Available targets: | [force\_detach\_ebs](#input\_force\_detach\_ebs) | force the volume/s to detach from the instance. | `bool` | `false` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | +| [instance\_market\_options\_enabled](#input\_instance\_market\_options\_enabled) | Wheter to enable the purchasing option for the instances | `bool` | `false` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | | [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no | @@ -286,6 +287,7 @@ Available targets: | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `null` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | @@ -312,6 +314,7 @@ Available targets: | [arn](#output\_arn) | ARN of the instance | | [ebs\_ids](#output\_ebs\_ids) | IDs of EBSs | | [id](#output\_id) | Disambiguated ID of the instance | +| [instance\_lifecycle](#output\_instance\_lifecycle) | Indicates whether this is a Spot Instance or a Scheduled Instance | | [instance\_profile](#output\_instance\_profile) | Name of the instance's profile (either built or supplied) | | [name](#output\_name) | Instance name | | [primary\_network\_interface\_id](#output\_primary\_network\_interface\_id) | ID of the instance's primary network interface | @@ -325,6 +328,7 @@ Available targets: | [security\_group\_id](#output\_security\_group\_id) | EC2 instance Security Group ID | | [security\_group\_ids](#output\_security\_group\_ids) | IDs on the AWS Security Groups associated with the instance | | [security\_group\_name](#output\_security\_group\_name) | EC2 instance Security Group name | +| [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | the ID of the Spot Instance request | | [ssh\_key\_pair](#output\_ssh\_key\_pair) | Name of the SSH key pair provisioned on the instance | diff --git a/docs/terraform.md b/docs/terraform.md index 6f60f74..13837a7 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -71,6 +71,7 @@ | [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | | [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | | [disable\_alarm\_action](#input\_disable\_alarm\_action) | Disable the creation of Alarm Action | `bool` | `false` | no | +| [disable\_api\_stop](#input\_disable\_api\_stop) | Enable EC2 Instance Stop Protection | `bool` | `false` | no | | [disable\_api\_termination](#input\_disable\_api\_termination) | Enable EC2 Instance Termination Protection | `bool` | `false` | no | | [ebs\_device\_name](#input\_ebs\_device\_name) | Name of the EBS device to mount | `list(string)` |
[
"/dev/xvdb",
"/dev/xvdc",
"/dev/xvdd",
"/dev/xvde",
"/dev/xvdf",
"/dev/xvdg",
"/dev/xvdh",
"/dev/xvdi",
"/dev/xvdj",
"/dev/xvdk",
"/dev/xvdl",
"/dev/xvdm",
"/dev/xvdn",
"/dev/xvdo",
"/dev/xvdp",
"/dev/xvdq",
"/dev/xvdr",
"/dev/xvds",
"/dev/xvdt",
"/dev/xvdu",
"/dev/xvdv",
"/dev/xvdw",
"/dev/xvdx",
"/dev/xvdy",
"/dev/xvdz"
]
| no | | [ebs\_iops](#input\_ebs\_iops) | Amount of provisioned IOPS. This must be set with a volume\_type of `io1`, `io2` or `gp3` | `number` | `0` | no | @@ -87,6 +88,7 @@ | [external\_network\_interfaces](#input\_external\_network\_interfaces) | The external interface definitions to attach to the instances. This depends on the instance type |
list(object({
delete_on_termination = bool
device_index = number
network_card_index = number
network_interface_id = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | +| [instance\_market\_options\_enabled](#input\_instance\_market\_options\_enabled) | Wheter to enable the purchasing option for the instances | `bool` | `false` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | | [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no | @@ -97,6 +99,7 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if `spot_options` is specified. | `string` | `"spot"` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | @@ -125,6 +128,7 @@ | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `null` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | @@ -150,6 +154,7 @@ | [arn](#output\_arn) | ARN of the instance | | [ebs\_ids](#output\_ebs\_ids) | IDs of EBSs | | [id](#output\_id) | Disambiguated ID of the instance | +| [instance\_lifecycle](#output\_instance\_lifecycle) | Indicates whether this is a Spot Instance or a Scheduled Instance | | [instance\_profile](#output\_instance\_profile) | Name of the instance's profile (either built or supplied) | | [name](#output\_name) | Instance name | | [primary\_network\_interface\_id](#output\_primary\_network\_interface\_id) | ID of the instance's primary network interface | @@ -163,5 +168,6 @@ | [security\_group\_id](#output\_security\_group\_id) | EC2 instance Security Group ID | | [security\_group\_ids](#output\_security\_group\_ids) | IDs on the AWS Security Groups associated with the instance | | [security\_group\_name](#output\_security\_group\_name) | EC2 instance Security Group name | +| [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | the ID of the Spot Instance request | | [ssh\_key\_pair](#output\_ssh\_key\_pair) | Name of the SSH key pair provisioned on the instance | diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index b56bd24..9aa1a69 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 2.0" + version = ">= 4.7.0" } null = { source = "hashicorp/null" diff --git a/examples/external-eni/versions.tf b/examples/external-eni/versions.tf index b56bd24..9aa1a69 100644 --- a/examples/external-eni/versions.tf +++ b/examples/external-eni/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 2.0" + version = ">= 4.7.0" } null = { source = "hashicorp/null" diff --git a/examples/spot-instance/context.tf b/examples/spot-instance/context.tf new file mode 100644 index 0000000..5e0ef88 --- /dev/null +++ b/examples/spot-instance/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/spot-instance/fixtures.us-east-2.tfvars b/examples/spot-instance/fixtures.us-east-2.tfvars new file mode 100644 index 0000000..340072c --- /dev/null +++ b/examples/spot-instance/fixtures.us-east-2.tfvars @@ -0,0 +1,70 @@ +enabled = true + +region = "us-east-2" + +namespace = "eg" + +stage = "test" + +name = "ec2-instance" + +availability_zones = ["us-east-2a", "us-east-2b"] + +assign_eip_address = false + +associate_public_ip_address = true + +instance_type = "t3.micro" + +security_group_rules = [ + { + type = "egress" + from_port = 0 + to_port = 65535 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + }, + { + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + }, + { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + }, + { + type = "ingress" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + }, + { + type = "ingress" + from_port = 53 + to_port = 53 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] + }, +] + +ssh_public_key_path = "/secrets" + +metric_treat_missing_data = "notBreaching" + +instance_market_options_enabled = true + +market_type = "spot" + +spot_options_attributes = [{ + instance_interruption_behavior = "terminate" + max_price = 0.004700 + spot_instance_type = "one-time" + valid_until = null +}] diff --git a/examples/spot-instance/main.tf b/examples/spot-instance/main.tf new file mode 100644 index 0000000..e2067d8 --- /dev/null +++ b/examples/spot-instance/main.tf @@ -0,0 +1,96 @@ +provider "aws" { + region = var.region +} + +module "aws_key_pair" { + source = "cloudposse/key-pair/aws" + version = "0.18.3" + namespace = module.this.namespace + stage = module.this.stage + name = module.this.name + attributes = module.this.attributes + ssh_public_key_path = var.ssh_public_key_path + generate_ssh_key = true +} + +module "vpc" { + source = "cloudposse/vpc/aws" + version = "2.1.0" + + ipv4_primary_cidr_block = "172.16.0.0/16" + + context = module.this.context +} + +module "subnets" { + source = "cloudposse/dynamic-subnets/aws" + version = "2.3.0" + + availability_zones = var.availability_zones + vpc_id = module.vpc.vpc_id + igw_id = [module.vpc.igw_id] + ipv4_cidr_block = [module.vpc.vpc_cidr_block] + nat_gateway_enabled = false + nat_instance_enabled = false + + context = module.this.context +} + +module "instance_profile_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + attributes = distinct(compact(concat(module.this.attributes, ["profile"]))) + + context = module.this.context +} + +data "aws_iam_policy_document" "test" { + statement { + effect = "Allow" + + actions = [ + "sts:AssumeRole" + ] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "test" { + name = module.instance_profile_label.id + assume_role_policy = data.aws_iam_policy_document.test.json + tags = module.instance_profile_label.tags +} + +# https://github.com/hashicorp/terraform-guides/tree/master/infrastructure-as-code/terraform-0.13-examples/module-depends-on +resource "aws_iam_instance_profile" "test" { + name = module.instance_profile_label.id + role = aws_iam_role.test.name +} + +module "ec2_instance" { + source = "../../" + + ssh_key_pair = module.aws_key_pair.key_name + vpc_id = module.vpc.vpc_id + subnet = module.subnets.private_subnet_ids[0] + security_groups = [module.vpc.vpc_default_security_group_id] + assign_eip_address = var.assign_eip_address + associate_public_ip_address = var.associate_public_ip_address + instance_type = var.instance_type + security_group_rules = var.security_group_rules + instance_profile = aws_iam_instance_profile.test.name + tenancy = var.tenancy + metric_treat_missing_data = var.metric_treat_missing_data + instance_market_options_enabled = var.instance_market_options_enabled + market_type = var.market_type + spot_options_attributes = var.spot_options_attributes + + depends_on = [aws_iam_instance_profile.test] + + context = module.this.context +} diff --git a/examples/spot-instance/outputs.tf b/examples/spot-instance/outputs.tf new file mode 100644 index 0000000..1f376ea --- /dev/null +++ b/examples/spot-instance/outputs.tf @@ -0,0 +1,124 @@ +output "key_name" { + value = module.aws_key_pair.key_name + description = "Name of SSH key" +} + +output "public_key" { + value = module.aws_key_pair.public_key + description = "Content of the generated public key" +} + +output "public_key_filename" { + description = "Public Key Filename" + value = module.aws_key_pair.public_key_filename +} + +output "private_key_filename" { + description = "Private Key Filename" + value = module.aws_key_pair.private_key_filename +} + +output "public_subnet_cidrs" { + description = "Public subnet CIDRs" + value = module.subnets.public_subnet_cidrs +} + +output "private_subnet_cidrs" { + description = "Private subnet CIDRs" + value = module.subnets.private_subnet_cidrs +} + +output "vpc_cidr" { + description = "VPC CIDR" + value = module.vpc.vpc_cidr_block +} + +output "public_ip" { + description = "Public IP of instance (or EIP)" + value = module.ec2_instance.public_ip +} + +output "private_ip" { + description = "Private IP of instance" + value = module.ec2_instance.private_ip +} + +output "private_dns" { + description = "Private DNS of instance" + value = module.ec2_instance.private_dns +} + +output "public_dns" { + description = "Public DNS of instance (or DNS of EIP)" + value = module.ec2_instance.public_dns +} + +output "id" { + description = "Disambiguated ID of the instance" + value = module.ec2_instance.id +} + +output "arn" { + description = "ARN of the instance" + value = module.ec2_instance.arn +} + +output "name" { + description = "Instance name" + value = module.ec2_instance.name +} + +output "ssh_key_pair" { + description = "Name of the SSH key pair provisioned on the instance" + value = module.ec2_instance.ssh_key_pair +} + +output "security_group_ids" { + description = "IDs on the AWS Security Groups associated with the instance" + value = module.ec2_instance.security_group_ids +} + +output "role" { + description = "Name of AWS IAM Role associated with the instance" + value = module.ec2_instance.role +} + +output "additional_eni_ids" { + description = "Map of ENI to EIP" + value = module.ec2_instance.additional_eni_ids +} + +output "ebs_ids" { + description = "IDs of EBSs" + value = module.ec2_instance.ebs_ids +} + +output "primary_network_interface_id" { + description = "ID of the instance's primary network interface" + value = module.ec2_instance.primary_network_interface_id +} + +output "security_group_id" { + value = module.ec2_instance.security_group_id + description = "EC2 instance Security Group ID" +} + +output "security_group_arn" { + value = module.ec2_instance.security_group_arn + description = "EC2 instance Security Group ARN" +} + +output "security_group_name" { + value = module.ec2_instance.security_group_name + description = "EC2 instance Security Group name" +} + +output "instance_lifecycle" { + value = var.instance_market_options_enabled ? module.ec2_instance.instance_lifecycle : null + description = "Indicates whether this is a Spot Instance or a Scheduled Instance" +} + +output "spot_instance_request_id" { + value = var.instance_market_options_enabled && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null + description = "the ID of the Spot Instance request" +} diff --git a/examples/spot-instance/variables.tf b/examples/spot-instance/variables.tf new file mode 100644 index 0000000..774a8e0 --- /dev/null +++ b/examples/spot-instance/variables.tf @@ -0,0 +1,84 @@ +variable "region" { + type = string + description = "AWS region" +} + +variable "availability_zones" { + type = list(string) + description = "List of availability zones" +} + +variable "associate_public_ip_address" { + type = bool + description = "Associate a public IP address with the instance" +} + +variable "assign_eip_address" { + type = bool + description = "Assign an Elastic IP address to the instance" +} + +variable "instance_type" { + type = string + description = "The type of the instance" +} + +variable "ssh_public_key_path" { + type = string + description = "Path to SSH public key directory (e.g. `/secrets`)" +} + +variable "security_group_rules" { + type = list(any) + description = <<-EOT + A list of maps of Security Group rules. + The values of map is fully complated with `aws_security_group_rule` resource. + To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule . + EOT +} + +variable "tenancy" { + type = string + default = "default" + description = "Tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of 'dedicated' runs on single-tenant hardware. The 'host' tenancy is not supported for the import-instance command. Valid values are 'default', 'dedicated', and 'host'." + validation { + condition = contains(["default", "dedicated", "host"], lower(var.tenancy)) + error_message = "Tenancy field can only be one of default, dedicated, host" + } +} + +variable "metric_treat_missing_data" { + type = string + description = "Sets how this alarm is to handle missing data points. The following values are supported: `missing`, `ignore`, `breaching` and `notBreaching`. Defaults to `missing`." + default = "missing" + validation { + condition = contains(["missing", "ignore", "breaching", "notBreaching"], var.metric_treat_missing_data) + error_message = "The value of metric_treat_missing_data must be one of the following: \"missing\", \"ignore\", \"breaching\", and \"notBreaching\"." + } +} + +variable "instance_market_options_enabled" { + type = bool + description = "Wheter to enable the purchasing option for the instances" + default = false +} + +variable "market_type" { + type = string + description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if `spot_options` is specified." + default = "spot" +} + +variable "spot_options_attributes" { + type = list(object({ + instance_interruption_behavior = string + max_price = number + spot_instance_type = string + valid_until = string + })) + description = <<-EOT + Describes the market (purchasing) option for the instances. + To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . + EOT + default = null +} diff --git a/examples/spot-instance/versions.tf b/examples/spot-instance/versions.tf new file mode 100644 index 0000000..9aa1a69 --- /dev/null +++ b/examples/spot-instance/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.7.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/main.tf b/main.tf index 62c98ab..bc04591 100644 --- a/main.tf +++ b/main.tf @@ -168,6 +168,26 @@ resource "aws_instance" "default" { cpu_credits = var.burstable_mode } + instance_market_options { + market_type = var.market_type + + dynamic "spot_options" { + for_each = var.market_type == "spot" ? var.spot_options_attributes : [] + 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 + } + } + } + + lifecycle { + ignore_changes = [ + ami + ] + } + tags = module.this.tags volume_tags = var.volume_tags_enabled ? module.this.tags : {} diff --git a/outputs.tf b/outputs.tf index 151a35a..9ef111c 100644 --- a/outputs.tf +++ b/outputs.tf @@ -100,3 +100,13 @@ output "security_group_name" { value = module.security_group.name description = "EC2 instance Security Group name" } + +output "instance_lifecycle" { + value = var.instance_market_options_enabled ? aws_instance.default[*].instance_lifecycle : null + description = "Indicates whether this is a Spot Instance or a Scheduled Instance" +} + +output "spot_instance_request_id" { + value = var.instance_market_options_enabled && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null + description = "the ID of the Spot Instance request" +} diff --git a/variables.tf b/variables.tf index 3f81a7f..c9810c9 100644 --- a/variables.tf +++ b/variables.tf @@ -46,6 +46,32 @@ variable "burstable_mode" { default = null } +variable "instance_market_options_enabled" { + type = bool + description = "Wheter to enable the purchasing option for the instances" + default = false +} + +variable "market_type" { + type = string + description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if `spot_options` is specified." + default = "spot" +} + +variable "spot_options_attributes" { + type = list(object({ + instance_interruption_behavior = string + max_price = number + spot_instance_type = string + valid_until = string + })) + description = <<-EOT + Describes the market (purchasing) option for the instances. + To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . + EOT + default = null +} + variable "vpc_id" { type = string description = "The ID of the VPC that the instance security group belongs to" From a091c714678bb11bb1f6fb2905d934a63857bc1a Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:28:32 +0700 Subject: [PATCH 02/29] Update variables.tf Co-authored-by: Joe Niland --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index c9810c9..4d6fdab 100644 --- a/variables.tf +++ b/variables.tf @@ -54,7 +54,7 @@ variable "instance_market_options_enabled" { variable "market_type" { type = string - description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if `spot_options` is specified." + description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." default = "spot" } From 5d805437aabc7aad9df024577a0e2ba33da744fa Mon Sep 17 00:00:00 2001 From: haidar ali Date: Fri, 16 Aug 2024 21:38:34 +0700 Subject: [PATCH 03/29] feat(aws_instance): add instance market options block --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index 4d6fdab..65af8dd 100644 --- a/variables.tf +++ b/variables.tf @@ -69,7 +69,7 @@ variable "spot_options_attributes" { Describes the market (purchasing) option for the instances. To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . EOT - default = null + default = [] } variable "vpc_id" { From d3ef54ead30eb5045bca10e0f59bcc03d92025c3 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 17 Aug 2024 18:45:27 +0700 Subject: [PATCH 04/29] feat(aws_instance): add instance market options block --- examples/spot-instance/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/spot-instance/variables.tf b/examples/spot-instance/variables.tf index 774a8e0..279fae0 100644 --- a/examples/spot-instance/variables.tf +++ b/examples/spot-instance/variables.tf @@ -65,7 +65,7 @@ variable "instance_market_options_enabled" { variable "market_type" { type = string - description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if `spot_options` is specified." + description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." default = "spot" } @@ -80,5 +80,5 @@ variable "spot_options_attributes" { Describes the market (purchasing) option for the instances. To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . EOT - default = null + default = [] } From 83ed75a0bc68b2cc2379353662a05e003db9fe0e Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 18 Aug 2024 07:34:22 +0700 Subject: [PATCH 05/29] feat(aws_instance): add instance market options block --- main.tf | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/main.tf b/main.tf index bc04591..2a2cad0 100644 --- a/main.tf +++ b/main.tf @@ -191,6 +191,23 @@ resource "aws_instance" "default" { tags = module.this.tags volume_tags = var.volume_tags_enabled ? module.this.tags : {} + + dynamic "instance_market_options" { + for_each = var.instance_market_options_enabled ? [var.spot_options_attributes] : [] + content { + market_type = var.market_type + + dynamic "spot_options" { + for_each = var.market_type == "spot" ? var.spot_options_attributes : [] + 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 + } + } + } + } } resource "aws_eip" "default" { From 34d6120a280f23fce428685c0a468df723f08600 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 18 Aug 2024 08:07:52 +0700 Subject: [PATCH 06/29] feat(aws_instance): add instance market options block --- README.md | 2 +- docs/terraform.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 73d66e3..4df790c 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ Available targets: | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `null` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 13837a7..123f059 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -99,7 +99,7 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if `spot_options` is specified. | `string` | `"spot"` | no | +| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`. | `string` | `"spot"` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | @@ -128,7 +128,7 @@ | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `null` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | From 15929131f357ca6b19d7946100b08f74c27ad599 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 18 Aug 2024 08:22:36 +0700 Subject: [PATCH 07/29] feat(aws_instance): add instance market options block --- main.tf | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/main.tf b/main.tf index 2a2cad0..cea5941 100644 --- a/main.tf +++ b/main.tf @@ -192,22 +192,6 @@ resource "aws_instance" "default" { volume_tags = var.volume_tags_enabled ? module.this.tags : {} - dynamic "instance_market_options" { - for_each = var.instance_market_options_enabled ? [var.spot_options_attributes] : [] - content { - market_type = var.market_type - - dynamic "spot_options" { - for_each = var.market_type == "spot" ? var.spot_options_attributes : [] - 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 - } - } - } - } } resource "aws_eip" "default" { From b8b4a51e1be5d091936f6a7d95f3f2ee4c69f21e Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 18 Aug 2024 08:30:26 +0700 Subject: [PATCH 08/29] feat(aws_instance): add instance market options block --- examples/spot-instance/variables.tf | 2 +- main.tf | 1 - variables.tf | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/spot-instance/variables.tf b/examples/spot-instance/variables.tf index 279fae0..8d83fb4 100644 --- a/examples/spot-instance/variables.tf +++ b/examples/spot-instance/variables.tf @@ -71,7 +71,7 @@ variable "market_type" { variable "spot_options_attributes" { type = list(object({ - instance_interruption_behavior = string + instance_interruption_behavior = string max_price = number spot_instance_type = string valid_until = string diff --git a/main.tf b/main.tf index cea5941..bc04591 100644 --- a/main.tf +++ b/main.tf @@ -191,7 +191,6 @@ resource "aws_instance" "default" { tags = module.this.tags volume_tags = var.volume_tags_enabled ? module.this.tags : {} - } resource "aws_eip" "default" { diff --git a/variables.tf b/variables.tf index 65af8dd..f2ebdf6 100644 --- a/variables.tf +++ b/variables.tf @@ -60,7 +60,7 @@ variable "market_type" { variable "spot_options_attributes" { type = list(object({ - instance_interruption_behavior = string + instance_interruption_behavior = string max_price = number spot_instance_type = string valid_until = string From d2d7d82017b425c50941f7d7a750cfd8d3225995 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 18 Aug 2024 08:33:18 +0700 Subject: [PATCH 09/29] feat(aws_instance): add instance market options block --- README.md | 2 +- docs/terraform.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4df790c..cd55945 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ Available targets: | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 123f059..f55f007 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -128,7 +128,7 @@ | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | From b3491a9ce747c0a18a82953b91d905f271fe0cbc Mon Sep 17 00:00:00 2001 From: haidar ali Date: Mon, 19 Aug 2024 06:45:37 +0700 Subject: [PATCH 10/29] feat(aws_instance): add instance market options block --- .../fixtures.us-east-2.spot.tfvars} | 0 examples/complete/main.tf | 25 +- examples/complete/outputs.tf | 10 + examples/complete/variables.tf | 28 +- examples/spot-instance/context.tf | 279 ------------------ examples/spot-instance/main.tf | 96 ------ examples/spot-instance/outputs.tf | 124 -------- examples/spot-instance/variables.tf | 84 ------ examples/spot-instance/versions.tf | 14 - test/src/examples_complete_test.go | 11 +- 10 files changed, 61 insertions(+), 610 deletions(-) rename examples/{spot-instance/fixtures.us-east-2.tfvars => complete/fixtures.us-east-2.spot.tfvars} (100%) delete mode 100644 examples/spot-instance/context.tf delete mode 100644 examples/spot-instance/main.tf delete mode 100644 examples/spot-instance/outputs.tf delete mode 100644 examples/spot-instance/variables.tf delete mode 100644 examples/spot-instance/versions.tf diff --git a/examples/spot-instance/fixtures.us-east-2.tfvars b/examples/complete/fixtures.us-east-2.spot.tfvars similarity index 100% rename from examples/spot-instance/fixtures.us-east-2.tfvars rename to examples/complete/fixtures.us-east-2.spot.tfvars diff --git a/examples/complete/main.tf b/examples/complete/main.tf index da5f77a..e2067d8 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -75,17 +75,20 @@ resource "aws_iam_instance_profile" "test" { module "ec2_instance" { source = "../../" - ssh_key_pair = module.aws_key_pair.key_name - vpc_id = module.vpc.vpc_id - subnet = module.subnets.private_subnet_ids[0] - security_groups = [module.vpc.vpc_default_security_group_id] - assign_eip_address = var.assign_eip_address - associate_public_ip_address = var.associate_public_ip_address - instance_type = var.instance_type - security_group_rules = var.security_group_rules - instance_profile = aws_iam_instance_profile.test.name - tenancy = var.tenancy - metric_treat_missing_data = var.metric_treat_missing_data + ssh_key_pair = module.aws_key_pair.key_name + vpc_id = module.vpc.vpc_id + subnet = module.subnets.private_subnet_ids[0] + security_groups = [module.vpc.vpc_default_security_group_id] + assign_eip_address = var.assign_eip_address + associate_public_ip_address = var.associate_public_ip_address + instance_type = var.instance_type + security_group_rules = var.security_group_rules + instance_profile = aws_iam_instance_profile.test.name + tenancy = var.tenancy + metric_treat_missing_data = var.metric_treat_missing_data + instance_market_options_enabled = var.instance_market_options_enabled + market_type = var.market_type + spot_options_attributes = var.spot_options_attributes depends_on = [aws_iam_instance_profile.test] diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index c747dc2..1f376ea 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -112,3 +112,13 @@ output "security_group_name" { value = module.ec2_instance.security_group_name description = "EC2 instance Security Group name" } + +output "instance_lifecycle" { + value = var.instance_market_options_enabled ? module.ec2_instance.instance_lifecycle : null + description = "Indicates whether this is a Spot Instance or a Scheduled Instance" +} + +output "spot_instance_request_id" { + value = var.instance_market_options_enabled && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null + description = "the ID of the Spot Instance request" +} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 826c58b..8d83fb4 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -55,4 +55,30 @@ variable "metric_treat_missing_data" { condition = contains(["missing", "ignore", "breaching", "notBreaching"], var.metric_treat_missing_data) error_message = "The value of metric_treat_missing_data must be one of the following: \"missing\", \"ignore\", \"breaching\", and \"notBreaching\"." } -} \ No newline at end of file +} + +variable "instance_market_options_enabled" { + type = bool + description = "Wheter to enable the purchasing option for the instances" + default = false +} + +variable "market_type" { + type = string + description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." + default = "spot" +} + +variable "spot_options_attributes" { + type = list(object({ + instance_interruption_behavior = string + max_price = number + spot_instance_type = string + valid_until = string + })) + description = <<-EOT + Describes the market (purchasing) option for the instances. + To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . + EOT + default = [] +} diff --git a/examples/spot-instance/context.tf b/examples/spot-instance/context.tf deleted file mode 100644 index 5e0ef88..0000000 --- a/examples/spot-instance/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/spot-instance/main.tf b/examples/spot-instance/main.tf deleted file mode 100644 index e2067d8..0000000 --- a/examples/spot-instance/main.tf +++ /dev/null @@ -1,96 +0,0 @@ -provider "aws" { - region = var.region -} - -module "aws_key_pair" { - source = "cloudposse/key-pair/aws" - version = "0.18.3" - namespace = module.this.namespace - stage = module.this.stage - name = module.this.name - attributes = module.this.attributes - ssh_public_key_path = var.ssh_public_key_path - generate_ssh_key = true -} - -module "vpc" { - source = "cloudposse/vpc/aws" - version = "2.1.0" - - ipv4_primary_cidr_block = "172.16.0.0/16" - - context = module.this.context -} - -module "subnets" { - source = "cloudposse/dynamic-subnets/aws" - version = "2.3.0" - - availability_zones = var.availability_zones - vpc_id = module.vpc.vpc_id - igw_id = [module.vpc.igw_id] - ipv4_cidr_block = [module.vpc.vpc_cidr_block] - nat_gateway_enabled = false - nat_instance_enabled = false - - context = module.this.context -} - -module "instance_profile_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - attributes = distinct(compact(concat(module.this.attributes, ["profile"]))) - - context = module.this.context -} - -data "aws_iam_policy_document" "test" { - statement { - effect = "Allow" - - actions = [ - "sts:AssumeRole" - ] - - principals { - type = "Service" - identifiers = ["ec2.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "test" { - name = module.instance_profile_label.id - assume_role_policy = data.aws_iam_policy_document.test.json - tags = module.instance_profile_label.tags -} - -# https://github.com/hashicorp/terraform-guides/tree/master/infrastructure-as-code/terraform-0.13-examples/module-depends-on -resource "aws_iam_instance_profile" "test" { - name = module.instance_profile_label.id - role = aws_iam_role.test.name -} - -module "ec2_instance" { - source = "../../" - - ssh_key_pair = module.aws_key_pair.key_name - vpc_id = module.vpc.vpc_id - subnet = module.subnets.private_subnet_ids[0] - security_groups = [module.vpc.vpc_default_security_group_id] - assign_eip_address = var.assign_eip_address - associate_public_ip_address = var.associate_public_ip_address - instance_type = var.instance_type - security_group_rules = var.security_group_rules - instance_profile = aws_iam_instance_profile.test.name - tenancy = var.tenancy - metric_treat_missing_data = var.metric_treat_missing_data - instance_market_options_enabled = var.instance_market_options_enabled - market_type = var.market_type - spot_options_attributes = var.spot_options_attributes - - depends_on = [aws_iam_instance_profile.test] - - context = module.this.context -} diff --git a/examples/spot-instance/outputs.tf b/examples/spot-instance/outputs.tf deleted file mode 100644 index 1f376ea..0000000 --- a/examples/spot-instance/outputs.tf +++ /dev/null @@ -1,124 +0,0 @@ -output "key_name" { - value = module.aws_key_pair.key_name - description = "Name of SSH key" -} - -output "public_key" { - value = module.aws_key_pair.public_key - description = "Content of the generated public key" -} - -output "public_key_filename" { - description = "Public Key Filename" - value = module.aws_key_pair.public_key_filename -} - -output "private_key_filename" { - description = "Private Key Filename" - value = module.aws_key_pair.private_key_filename -} - -output "public_subnet_cidrs" { - description = "Public subnet CIDRs" - value = module.subnets.public_subnet_cidrs -} - -output "private_subnet_cidrs" { - description = "Private subnet CIDRs" - value = module.subnets.private_subnet_cidrs -} - -output "vpc_cidr" { - description = "VPC CIDR" - value = module.vpc.vpc_cidr_block -} - -output "public_ip" { - description = "Public IP of instance (or EIP)" - value = module.ec2_instance.public_ip -} - -output "private_ip" { - description = "Private IP of instance" - value = module.ec2_instance.private_ip -} - -output "private_dns" { - description = "Private DNS of instance" - value = module.ec2_instance.private_dns -} - -output "public_dns" { - description = "Public DNS of instance (or DNS of EIP)" - value = module.ec2_instance.public_dns -} - -output "id" { - description = "Disambiguated ID of the instance" - value = module.ec2_instance.id -} - -output "arn" { - description = "ARN of the instance" - value = module.ec2_instance.arn -} - -output "name" { - description = "Instance name" - value = module.ec2_instance.name -} - -output "ssh_key_pair" { - description = "Name of the SSH key pair provisioned on the instance" - value = module.ec2_instance.ssh_key_pair -} - -output "security_group_ids" { - description = "IDs on the AWS Security Groups associated with the instance" - value = module.ec2_instance.security_group_ids -} - -output "role" { - description = "Name of AWS IAM Role associated with the instance" - value = module.ec2_instance.role -} - -output "additional_eni_ids" { - description = "Map of ENI to EIP" - value = module.ec2_instance.additional_eni_ids -} - -output "ebs_ids" { - description = "IDs of EBSs" - value = module.ec2_instance.ebs_ids -} - -output "primary_network_interface_id" { - description = "ID of the instance's primary network interface" - value = module.ec2_instance.primary_network_interface_id -} - -output "security_group_id" { - value = module.ec2_instance.security_group_id - description = "EC2 instance Security Group ID" -} - -output "security_group_arn" { - value = module.ec2_instance.security_group_arn - description = "EC2 instance Security Group ARN" -} - -output "security_group_name" { - value = module.ec2_instance.security_group_name - description = "EC2 instance Security Group name" -} - -output "instance_lifecycle" { - value = var.instance_market_options_enabled ? module.ec2_instance.instance_lifecycle : null - description = "Indicates whether this is a Spot Instance or a Scheduled Instance" -} - -output "spot_instance_request_id" { - value = var.instance_market_options_enabled && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null - description = "the ID of the Spot Instance request" -} diff --git a/examples/spot-instance/variables.tf b/examples/spot-instance/variables.tf deleted file mode 100644 index 8d83fb4..0000000 --- a/examples/spot-instance/variables.tf +++ /dev/null @@ -1,84 +0,0 @@ -variable "region" { - type = string - description = "AWS region" -} - -variable "availability_zones" { - type = list(string) - description = "List of availability zones" -} - -variable "associate_public_ip_address" { - type = bool - description = "Associate a public IP address with the instance" -} - -variable "assign_eip_address" { - type = bool - description = "Assign an Elastic IP address to the instance" -} - -variable "instance_type" { - type = string - description = "The type of the instance" -} - -variable "ssh_public_key_path" { - type = string - description = "Path to SSH public key directory (e.g. `/secrets`)" -} - -variable "security_group_rules" { - type = list(any) - description = <<-EOT - A list of maps of Security Group rules. - The values of map is fully complated with `aws_security_group_rule` resource. - To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule . - EOT -} - -variable "tenancy" { - type = string - default = "default" - description = "Tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of 'dedicated' runs on single-tenant hardware. The 'host' tenancy is not supported for the import-instance command. Valid values are 'default', 'dedicated', and 'host'." - validation { - condition = contains(["default", "dedicated", "host"], lower(var.tenancy)) - error_message = "Tenancy field can only be one of default, dedicated, host" - } -} - -variable "metric_treat_missing_data" { - type = string - description = "Sets how this alarm is to handle missing data points. The following values are supported: `missing`, `ignore`, `breaching` and `notBreaching`. Defaults to `missing`." - default = "missing" - validation { - condition = contains(["missing", "ignore", "breaching", "notBreaching"], var.metric_treat_missing_data) - error_message = "The value of metric_treat_missing_data must be one of the following: \"missing\", \"ignore\", \"breaching\", and \"notBreaching\"." - } -} - -variable "instance_market_options_enabled" { - type = bool - description = "Wheter to enable the purchasing option for the instances" - default = false -} - -variable "market_type" { - type = string - description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." - default = "spot" -} - -variable "spot_options_attributes" { - type = list(object({ - instance_interruption_behavior = string - max_price = number - spot_instance_type = string - valid_until = string - })) - description = <<-EOT - Describes the market (purchasing) option for the instances. - To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . - EOT - default = [] -} diff --git a/examples/spot-instance/versions.tf b/examples/spot-instance/versions.tf deleted file mode 100644 index 9aa1a69..0000000 --- a/examples/spot-instance/versions.tf +++ /dev/null @@ -1,14 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.7.0" - } - null = { - source = "hashicorp/null" - version = ">= 2.0" - } - } -} diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go index 4db8941..8a09141 100644 --- a/test/src/examples_complete_test.go +++ b/test/src/examples_complete_test.go @@ -24,7 +24,7 @@ func TestExamplesComplete(t *testing.T) { TerraformDir: "../../examples/complete", Upgrade: true, // Variables to pass to our Terraform code using -var-file options - VarFiles: []string{"fixtures.us-east-2.tfvars"}, + VarFiles: []string{"fixtures.us-east-2.tfvars","fixtures.us-east-2.spot.tfvars"}, Vars: map[string]interface{}{ "attributes": attributes, }, @@ -81,6 +81,15 @@ func TestExamplesComplete(t *testing.T) { securityGroupARN := terraform.Output(t, terraformOptions, "security_group_arn") // Verify we're getting back the outputs we expect assert.Contains(t, securityGroupARN, "arn:aws:ec2", "SG ID should contains substring 'arn:aws:ec2'") + + // Run `terraform output` to get the value of an output variable + spotInstanceRequestID := terraform.Output(t, terraformOptions, "spot_instance_request_id") + // Verify we're getting back the outputs we expect + if spotInstanceRequestID != "" { + assert.Contains(t, spotInstanceRequestID, "sir-", "Spot instance request ID should contains substring 'sir-'") + } else { + t.Log("No spot_instance_request_id output found, skipping spot instance request assertions.") + } } func TestExternalEniComplete(t *testing.T) { From da7c2fc27dcfc81d9ff8081bb1516ce577420f74 Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:56:05 +0700 Subject: [PATCH 11/29] Update outputs.tf Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- outputs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/outputs.tf b/outputs.tf index 9ef111c..9a63f57 100644 --- a/outputs.tf +++ b/outputs.tf @@ -108,5 +108,5 @@ output "instance_lifecycle" { output "spot_instance_request_id" { value = var.instance_market_options_enabled && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null - description = "the ID of the Spot Instance request" + description = "ID of the Spot Instance request" } From 5063e1b3754ed9140e3759d964e995906db080cd Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:56:26 +0700 Subject: [PATCH 12/29] Update variables.tf Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index f2ebdf6..bfe7a21 100644 --- a/variables.tf +++ b/variables.tf @@ -48,7 +48,7 @@ variable "burstable_mode" { variable "instance_market_options_enabled" { type = bool - description = "Wheter to enable the purchasing option for the instances" + description = "Whether to enable the purchasing option for the instances" default = false } From a3258fa466208b584fb6c3cd828f4b585c0e45b5 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Mon, 19 Aug 2024 22:44:37 +0700 Subject: [PATCH 13/29] feat(aws_instance): add instance market options block --- README.md | 3 +-- docs/terraform.md | 3 +-- examples/complete/fixtures.us-east-2.spot.tfvars | 2 -- examples/complete/main.tf | 1 - examples/complete/outputs.tf | 4 ++-- examples/complete/variables.tf | 6 ------ outputs.tf | 4 ++-- variables.tf | 6 ------ 8 files changed, 6 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index cd55945..55268c6 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,6 @@ Available targets: | [force\_detach\_ebs](#input\_force\_detach\_ebs) | force the volume/s to detach from the instance. | `bool` | `false` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | -| [instance\_market\_options\_enabled](#input\_instance\_market\_options\_enabled) | Wheter to enable the purchasing option for the instances | `bool` | `false` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | | [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no | @@ -328,7 +327,7 @@ Available targets: | [security\_group\_id](#output\_security\_group\_id) | EC2 instance Security Group ID | | [security\_group\_ids](#output\_security\_group\_ids) | IDs on the AWS Security Groups associated with the instance | | [security\_group\_name](#output\_security\_group\_name) | EC2 instance Security Group name | -| [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | the ID of the Spot Instance request | +| [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | ID of the Spot Instance request | | [ssh\_key\_pair](#output\_ssh\_key\_pair) | Name of the SSH key pair provisioned on the instance | diff --git a/docs/terraform.md b/docs/terraform.md index f55f007..2da1113 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -88,7 +88,6 @@ | [external\_network\_interfaces](#input\_external\_network\_interfaces) | The external interface definitions to attach to the instances. This depends on the instance type |
list(object({
delete_on_termination = bool
device_index = number
network_card_index = number
network_interface_id = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | -| [instance\_market\_options\_enabled](#input\_instance\_market\_options\_enabled) | Wheter to enable the purchasing option for the instances | `bool` | `false` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | | [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no | @@ -168,6 +167,6 @@ | [security\_group\_id](#output\_security\_group\_id) | EC2 instance Security Group ID | | [security\_group\_ids](#output\_security\_group\_ids) | IDs on the AWS Security Groups associated with the instance | | [security\_group\_name](#output\_security\_group\_name) | EC2 instance Security Group name | -| [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | the ID of the Spot Instance request | +| [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | ID of the Spot Instance request | | [ssh\_key\_pair](#output\_ssh\_key\_pair) | Name of the SSH key pair provisioned on the instance | diff --git a/examples/complete/fixtures.us-east-2.spot.tfvars b/examples/complete/fixtures.us-east-2.spot.tfvars index 340072c..31a47d6 100644 --- a/examples/complete/fixtures.us-east-2.spot.tfvars +++ b/examples/complete/fixtures.us-east-2.spot.tfvars @@ -58,8 +58,6 @@ ssh_public_key_path = "/secrets" metric_treat_missing_data = "notBreaching" -instance_market_options_enabled = true - market_type = "spot" spot_options_attributes = [{ diff --git a/examples/complete/main.tf b/examples/complete/main.tf index e2067d8..67f54e5 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -86,7 +86,6 @@ module "ec2_instance" { instance_profile = aws_iam_instance_profile.test.name tenancy = var.tenancy metric_treat_missing_data = var.metric_treat_missing_data - instance_market_options_enabled = var.instance_market_options_enabled market_type = var.market_type spot_options_attributes = var.spot_options_attributes diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 1f376ea..cbb7930 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -114,11 +114,11 @@ output "security_group_name" { } output "instance_lifecycle" { - value = var.instance_market_options_enabled ? module.ec2_instance.instance_lifecycle : null + value = var.spot_options_attributes != [] ? module.ec2_instance.instance_lifecycle : null description = "Indicates whether this is a Spot Instance or a Scheduled Instance" } output "spot_instance_request_id" { - value = var.instance_market_options_enabled && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null + value = var.spot_options_attributes != [] && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null description = "the ID of the Spot Instance request" } diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 8d83fb4..2a33b8c 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -57,12 +57,6 @@ variable "metric_treat_missing_data" { } } -variable "instance_market_options_enabled" { - type = bool - description = "Wheter to enable the purchasing option for the instances" - default = false -} - variable "market_type" { type = string description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." diff --git a/outputs.tf b/outputs.tf index 9a63f57..9946d82 100644 --- a/outputs.tf +++ b/outputs.tf @@ -102,11 +102,11 @@ output "security_group_name" { } output "instance_lifecycle" { - value = var.instance_market_options_enabled ? aws_instance.default[*].instance_lifecycle : null + value = var.spot_options_attributes != [] ? aws_instance.default[*].instance_lifecycle : null description = "Indicates whether this is a Spot Instance or a Scheduled Instance" } output "spot_instance_request_id" { - value = var.instance_market_options_enabled && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null + value = var.spot_options_attributes != [] && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null description = "ID of the Spot Instance request" } diff --git a/variables.tf b/variables.tf index bfe7a21..f3ad1a2 100644 --- a/variables.tf +++ b/variables.tf @@ -46,12 +46,6 @@ variable "burstable_mode" { default = null } -variable "instance_market_options_enabled" { - type = bool - description = "Whether to enable the purchasing option for the instances" - default = false -} - variable "market_type" { type = string description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." From 2f9f24b8afa18a7be7843c330d07f33184cadbf7 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 1 Sep 2024 11:09:13 +0700 Subject: [PATCH 14/29] feat(aws_instance): add instance market options block --- main.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/main.tf b/main.tf index bc04591..bb3f613 100644 --- a/main.tf +++ b/main.tf @@ -182,12 +182,6 @@ resource "aws_instance" "default" { } } - lifecycle { - ignore_changes = [ - ami - ] - } - tags = module.this.tags volume_tags = var.volume_tags_enabled ? module.this.tags : {} From ee7cfcebf5206e84d77214dfcb520cee4f088ef5 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 21 Sep 2024 08:58:05 +0700 Subject: [PATCH 15/29] feat(aws_instance): add instance market options block --- README.md | 3 ++- docs/terraform.md | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 55268c6..526375b 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,7 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`. | `string` | `"spot"` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | @@ -286,7 +287,7 @@ Available targets: | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 2da1113..afe981e 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -55,25 +55,25 @@ | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [additional\_ips\_count](#input\_additional\_ips\_count) | Count of additional EIPs | `number` | `0` | no | -| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | | [ami](#input\_ami) | The AMI to use for the instance. By default it is the AMI provided by Amazon with Ubuntu 16.04 | `string` | `""` | no | | [ami\_owner](#input\_ami\_owner) | Owner of the given AMI (ignored if `ami` unset, required if set) | `string` | `""` | no | | [applying\_period](#input\_applying\_period) | The period in seconds over which the specified statistic is applied | `number` | `60` | no | | [assign\_eip\_address](#input\_assign\_eip\_address) | Assign an Elastic IP address to the instance | `bool` | `true` | no | | [associate\_public\_ip\_address](#input\_associate\_public\_ip\_address) | Associate a public IP address with the instance | `bool` | `false` | no | -| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | | [availability\_zone](#input\_availability\_zone) | Availability Zone the instance is launched in. If not set, will be launched in the first AZ of the region | `string` | `""` | no | | [burstable\_mode](#input\_burstable\_mode) | Enable burstable mode for the instance. Can be standard or unlimited. Applicable only for T2/T3/T4g instance types. | `string` | `null` | no | | [comparison\_operator](#input\_comparison\_operator) | The arithmetic operation to use when comparing the specified Statistic and Threshold. Possible values are: GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanThreshold, LessThanOrEqualToThreshold. | `string` | `"GreaterThanOrEqualToThreshold"` | no | -| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | | [default\_alarm\_action](#input\_default\_alarm\_action) | Default alarm action | `string` | `"action/actions/AWS_EC2.InstanceId.Reboot/1.0"` | no | | [delete\_on\_termination](#input\_delete\_on\_termination) | Whether the volume should be destroyed on instance termination | `bool` | `true` | no | -| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | -| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | | [disable\_alarm\_action](#input\_disable\_alarm\_action) | Disable the creation of Alarm Action | `bool` | `false` | no | | [disable\_api\_stop](#input\_disable\_api\_stop) | Enable EC2 Instance Stop Protection | `bool` | `false` | no | | [disable\_api\_termination](#input\_disable\_api\_termination) | Enable EC2 Instance Termination Protection | `bool` | `false` | no | -| [ebs\_device\_name](#input\_ebs\_device\_name) | Name of the EBS device to mount | `list(string)` |
[
"/dev/xvdb",
"/dev/xvdc",
"/dev/xvdd",
"/dev/xvde",
"/dev/xvdf",
"/dev/xvdg",
"/dev/xvdh",
"/dev/xvdi",
"/dev/xvdj",
"/dev/xvdk",
"/dev/xvdl",
"/dev/xvdm",
"/dev/xvdn",
"/dev/xvdo",
"/dev/xvdp",
"/dev/xvdq",
"/dev/xvdr",
"/dev/xvds",
"/dev/xvdt",
"/dev/xvdu",
"/dev/xvdv",
"/dev/xvdw",
"/dev/xvdx",
"/dev/xvdy",
"/dev/xvdz"
]
| no | +| [ebs\_device\_name](#input\_ebs\_device\_name) | Name of the EBS device to mount | `list(string)` |
[
"/dev/xvdb",
"/dev/xvdc",
"/dev/xvdd",
"/dev/xvde",
"/dev/xvdf",
"/dev/xvdg",
"/dev/xvdh",
"/dev/xvdi",
"/dev/xvdj",
"/dev/xvdk",
"/dev/xvdl",
"/dev/xvdm",
"/dev/xvdn",
"/dev/xvdo",
"/dev/xvdp",
"/dev/xvdq",
"/dev/xvdr",
"/dev/xvds",
"/dev/xvdt",
"/dev/xvdu",
"/dev/xvdv",
"/dev/xvdw",
"/dev/xvdx",
"/dev/xvdy",
"/dev/xvdz"
]
| no | | [ebs\_iops](#input\_ebs\_iops) | Amount of provisioned IOPS. This must be set with a volume\_type of `io1`, `io2` or `gp3` | `number` | `0` | no | | [ebs\_optimized](#input\_ebs\_optimized) | Launched EC2 instance will be EBS-optimized | `bool` | `true` | no | | [ebs\_throughput](#input\_ebs\_throughput) | Amount of throughput. This must be set if volume\_type is set to `gp3` | `number` | `0` | no | @@ -85,8 +85,8 @@ | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [evaluation\_periods](#input\_evaluation\_periods) | The number of periods over which data is compared to the specified threshold. | `number` | `5` | no | | [external\_network\_interface\_enabled](#input\_external\_network\_interface\_enabled) | Wheter to attach an external ENI as the eth0 interface for the instance. Any change to the interface will force instance recreation. | `bool` | `false` | no | -| [external\_network\_interfaces](#input\_external\_network\_interfaces) | The external interface definitions to attach to the instances. This depends on the instance type |
list(object({
delete_on_termination = bool
device_index = number
network_card_index = number
network_interface_id = string
}))
| `null` | no | -| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [external\_network\_interfaces](#input\_external\_network\_interfaces) | The external interface definitions to attach to the instances. This depends on the instance type |
list(object({
delete_on_termination = bool
device_index = number
network_card_index = number
network_interface_id = string
}))
| `null` | no | +| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | @@ -94,10 +94,10 @@ | [ipv6\_address\_count](#input\_ipv6\_address\_count) | Number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet (-1 to use subnet default) | `number` | `0` | no | | [ipv6\_addresses](#input\_ipv6\_addresses) | List of IPv6 addresses from the range of the subnet to associate with the primary network interface | `list(string)` | `[]` | no | | [kms\_key\_id](#input\_kms\_key\_id) | KMS key ID used to encrypt EBS volume. When specifying kms\_key\_id, ebs\_volume\_encrypted needs to be set to true | `string` | `null` | no | -| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | -| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | -| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | -| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | | [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`. | `string` | `"spot"` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | @@ -108,11 +108,11 @@ | [metric\_threshold](#input\_metric\_threshold) | The value against which the specified statistic is compared | `number` | `1` | no | | [metric\_treat\_missing\_data](#input\_metric\_treat\_missing\_data) | Sets how this alarm is to handle missing data points. The following values are supported: `missing`, `ignore`, `breaching` and `notBreaching`. Defaults to `missing`. | `string` | `"missing"` | no | | [monitoring](#input\_monitoring) | Launched EC2 instance will have detailed monitoring enabled | `bool` | `true` | no | -| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [permissions\_boundary\_arn](#input\_permissions\_boundary\_arn) | Policy ARN to attach to instance role as a permissions boundary | `string` | `""` | no | | [private\_ip](#input\_private\_ip) | Private IP address to associate with the instance in the VPC | `string` | `null` | no | -| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | | [region](#input\_region) | AWS Region the instance is launched in | `string` | `""` | no | | [root\_block\_device\_encrypted](#input\_root\_block\_device\_encrypted) | Whether to encrypt the root block device | `bool` | `true` | no | | [root\_block\_device\_kms\_key\_id](#input\_root\_block\_device\_kms\_key\_id) | KMS key ID used to encrypt EBS volume. When specifying root\_block\_device\_kms\_key\_id, root\_block\_device\_encrypted needs to be set to true | `string` | `null` | no | @@ -123,11 +123,11 @@ | [secondary\_private\_ips](#input\_secondary\_private\_ips) | List of secondary private IP addresses to associate with the instance in the VPC | `list(string)` | `[]` | no | | [security\_group\_description](#input\_security\_group\_description) | The Security Group description. | `string` | `"EC2 Security Group"` | no | | [security\_group\_enabled](#input\_security\_group\_enabled) | Whether to create default Security Group for EC2. | `bool` | `true` | no | -| [security\_group\_rules](#input\_security\_group\_rules) | A list of maps of Security Group rules.
The values of map is fully complated with `aws_security_group_rule` resource.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule . | `list(any)` |
[
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": "Allow all outbound traffic",
"from_port": 0,
"protocol": "-1",
"to_port": 65535,
"type": "egress"
}
]
| no | +| [security\_group\_rules](#input\_security\_group\_rules) | A list of maps of Security Group rules.
The values of map is fully complated with `aws_security_group_rule` resource.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule . | `list(any)` |
[
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": "Allow all outbound traffic",
"from_port": 0,
"protocol": "-1",
"to_port": 65535,
"type": "egress"
}
]
| no | | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | +| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | @@ -135,7 +135,7 @@ | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [statistic\_level](#input\_statistic\_level) | The statistic to apply to the alarm's associated metric. Allowed values are: SampleCount, Average, Sum, Minimum, Maximum | `string` | `"Maximum"` | no | | [subnet](#input\_subnet) | VPC Subnet ID the instance is launched in | `string` | n/a | yes | -| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | | [tenancy](#input\_tenancy) | Tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of 'dedicated' runs on single-tenant hardware. The 'host' tenancy is not supported for the import-instance command. Valid values are 'default', 'dedicated', and 'host'. | `string` | `"default"` | no | | [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | | [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; use `user_data_base64` instead | `string` | `null` | no | From f946f21eee7bc4eacb43d744eac451051577ea00 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 21 Sep 2024 21:42:08 +0700 Subject: [PATCH 16/29] feat(aws_instance): add instance market options block --- examples/complete/fixtures.us-east-2.tfvars | 2 +- examples/complete/main.tf | 26 ++++++++++----------- main.tf | 20 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/complete/fixtures.us-east-2.tfvars b/examples/complete/fixtures.us-east-2.tfvars index 0e78013..f639a6e 100644 --- a/examples/complete/fixtures.us-east-2.tfvars +++ b/examples/complete/fixtures.us-east-2.tfvars @@ -56,4 +56,4 @@ security_group_rules = [ ssh_public_key_path = "/secrets" -metric_treat_missing_data = "notBreaching" \ No newline at end of file +metric_treat_missing_data = "notBreaching" diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 67f54e5..5e00bfe 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -75,19 +75,19 @@ resource "aws_iam_instance_profile" "test" { module "ec2_instance" { source = "../../" - ssh_key_pair = module.aws_key_pair.key_name - vpc_id = module.vpc.vpc_id - subnet = module.subnets.private_subnet_ids[0] - security_groups = [module.vpc.vpc_default_security_group_id] - assign_eip_address = var.assign_eip_address - associate_public_ip_address = var.associate_public_ip_address - instance_type = var.instance_type - security_group_rules = var.security_group_rules - instance_profile = aws_iam_instance_profile.test.name - tenancy = var.tenancy - metric_treat_missing_data = var.metric_treat_missing_data - market_type = var.market_type - spot_options_attributes = var.spot_options_attributes + ssh_key_pair = module.aws_key_pair.key_name + vpc_id = module.vpc.vpc_id + subnet = module.subnets.private_subnet_ids[0] + security_groups = [module.vpc.vpc_default_security_group_id] + assign_eip_address = var.assign_eip_address + associate_public_ip_address = var.associate_public_ip_address + instance_type = var.instance_type + security_group_rules = var.security_group_rules + instance_profile = aws_iam_instance_profile.test.name + tenancy = var.tenancy + metric_treat_missing_data = var.metric_treat_missing_data + market_type = var.market_type + spot_options_attributes = var.spot_options_attributes depends_on = [aws_iam_instance_profile.test] diff --git a/main.tf b/main.tf index bb3f613..7ebec92 100644 --- a/main.tf +++ b/main.tf @@ -169,17 +169,17 @@ resource "aws_instance" "default" { } instance_market_options { - market_type = var.market_type - - dynamic "spot_options" { - for_each = var.market_type == "spot" ? var.spot_options_attributes : [] - 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 - } + market_type = var.market_type + + dynamic "spot_options" { + for_each = var.market_type == "spot" ? var.spot_options_attributes : [] + 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 } + } } tags = module.this.tags From bfcff37dbf2d41e251193599fee520f1484a5587 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 22 Sep 2024 05:05:02 +0700 Subject: [PATCH 17/29] feat(aws_instance): add instance market options block --- examples/complete/fixtures.us-east-2.spot.tfvars | 2 +- examples/complete/outputs.tf | 6 +++--- outputs.tf | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/complete/fixtures.us-east-2.spot.tfvars b/examples/complete/fixtures.us-east-2.spot.tfvars index 31a47d6..586c14b 100644 --- a/examples/complete/fixtures.us-east-2.spot.tfvars +++ b/examples/complete/fixtures.us-east-2.spot.tfvars @@ -62,7 +62,7 @@ market_type = "spot" spot_options_attributes = [{ instance_interruption_behavior = "terminate" - max_price = 0.004700 + max_price = null spot_instance_type = "one-time" valid_until = null }] diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index cbb7930..fe090f0 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -114,11 +114,11 @@ output "security_group_name" { } output "instance_lifecycle" { - value = var.spot_options_attributes != [] ? module.ec2_instance.instance_lifecycle : null + value = length(var.spot_options_attributes[*]) != 0 ? module.ec2_instance.instance_lifecycle : null description = "Indicates whether this is a Spot Instance or a Scheduled Instance" } output "spot_instance_request_id" { - value = var.spot_options_attributes != [] && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null - description = "the ID of the Spot Instance request" + value = length(var.spot_options_attributes[*]) != 0 && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null + description = "ID of the Spot Instance request" } diff --git a/outputs.tf b/outputs.tf index 9946d82..485f6af 100644 --- a/outputs.tf +++ b/outputs.tf @@ -102,11 +102,11 @@ output "security_group_name" { } output "instance_lifecycle" { - value = var.spot_options_attributes != [] ? aws_instance.default[*].instance_lifecycle : null + value = length(var.spot_options_attributes[*]) != 0 ? aws_instance.default[*].instance_lifecycle : null description = "Indicates whether this is a Spot Instance or a Scheduled Instance" } output "spot_instance_request_id" { - value = var.spot_options_attributes != [] && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null + value = length(var.spot_options_attributes[*]) != 0 && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null description = "ID of the Spot Instance request" } From 385ef649059f23d45ab8d7734dd3f6713fc04ee2 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sun, 22 Sep 2024 12:14:33 +0700 Subject: [PATCH 18/29] feat(aws_instance): add instance market options block --- README.md | 2 +- docs/terraform.md | 2 +- examples/complete/variables.tf | 4 ++-- variables.tf | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 526375b..b53123f 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`. | `string` | `"spot"` | no | +| [market\_type](#input\_market\_type) | (Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | diff --git a/docs/terraform.md b/docs/terraform.md index afe981e..7265183 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -98,7 +98,7 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`. | `string` | `"spot"` | no | +| [market\_type](#input\_market\_type) | (Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 2a33b8c..0582f61 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -59,8 +59,8 @@ variable "metric_treat_missing_data" { variable "market_type" { type = string - description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." - default = "spot" + description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable." + default = null } variable "spot_options_attributes" { diff --git a/variables.tf b/variables.tf index f3ad1a2..3b8e471 100644 --- a/variables.tf +++ b/variables.tf @@ -48,8 +48,8 @@ variable "burstable_mode" { variable "market_type" { type = string - description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. Defaults to `spot`. Required if a non-empty value is provided for `spot_options_attributes`." - default = "spot" + description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable." + default = null } variable "spot_options_attributes" { From 2e1572a87f5e717385877ad089f5c50dda0e7208 Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:54:39 +0700 Subject: [PATCH 19/29] Update variables.tf Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index 3b8e471..ab6a4ca 100644 --- a/variables.tf +++ b/variables.tf @@ -48,7 +48,7 @@ variable "burstable_mode" { variable "market_type" { type = string - description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable." + description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable." default = null } From da21bfaa2270379962286c0b83e1226d7191d69d Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:55:23 +0700 Subject: [PATCH 20/29] Update variables.tf Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index ab6a4ca..3a7107d 100644 --- a/variables.tf +++ b/variables.tf @@ -61,7 +61,7 @@ variable "spot_options_attributes" { })) description = <<-EOT Describes the market (purchasing) option for the instances. - To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . + See [docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options) for more information. EOT default = [] } From 9927a069d79f29ed18e4693b5fa90b482dbcbbf5 Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:59:00 +0700 Subject: [PATCH 21/29] Update outputs.tf Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- outputs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/outputs.tf b/outputs.tf index 485f6af..65bed0b 100644 --- a/outputs.tf +++ b/outputs.tf @@ -102,7 +102,7 @@ output "security_group_name" { } output "instance_lifecycle" { - value = length(var.spot_options_attributes[*]) != 0 ? aws_instance.default[*].instance_lifecycle : null + value = try(one(aws_instance.default[*].instance_lifecycle), null) description = "Indicates whether this is a Spot Instance or a Scheduled Instance" } From fae5cd44ee1ec836815e8c307628fdfaf4efdbc6 Mon Sep 17 00:00:00 2001 From: Haidar <33404432+haidargit@users.noreply.github.com> Date: Sun, 22 Sep 2024 23:59:46 +0700 Subject: [PATCH 22/29] Update outputs.tf Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- outputs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/outputs.tf b/outputs.tf index 65bed0b..99482dc 100644 --- a/outputs.tf +++ b/outputs.tf @@ -107,6 +107,6 @@ output "instance_lifecycle" { } output "spot_instance_request_id" { - value = length(var.spot_options_attributes[*]) != 0 && var.market_type == "spot" ? aws_instance.default[*].spot_instance_request_id : null + value = try(one(aws_instance.default[*].spot_instance_request_id), null) description = "ID of the Spot Instance request" } From 5b0f411b38cb687fd0f913eace84942bb76b5b54 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Fri, 4 Oct 2024 21:56:45 +0700 Subject: [PATCH 23/29] feat(aws_instance): add instance market options block --- README.md | 4 +-- docs/terraform.md | 4 +-- .../complete/fixtures.us-east-2.spot.tfvars | 17 +++++++------ examples/complete/main.tf | 2 +- examples/complete/outputs.tf | 4 +-- examples/complete/variables.tf | 25 ++++++++++++------- main.tf | 24 ++++++++++-------- variables.tf | 23 +++++++++++------ 8 files changed, 61 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index b53123f..2fbd475 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ Available targets: | [force\_detach\_ebs](#input\_force\_detach\_ebs) | force the volume/s to detach from the instance. | `bool` | `false` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | +| [instance\_market\_options](#input\_instance\_market\_options) | Describes the market (purchasing) option for the instances.
See [docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options) for more information. |
object({
market_type = string
spot_options = optional(object({
instance_interruption_behavior = optional(string)
max_price = optional(number)
spot_instance_type = optional(string)
valid_until = optional(string)
}))
})
| `null` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | | [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no | @@ -258,7 +259,7 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | (Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | +| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | @@ -287,7 +288,6 @@ Available targets: | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 7265183..67c25f8 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -88,6 +88,7 @@ | [external\_network\_interfaces](#input\_external\_network\_interfaces) | The external interface definitions to attach to the instances. This depends on the instance type |
list(object({
delete_on_termination = bool
device_index = number
network_card_index = number
network_interface_id = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [instance\_initiated\_shutdown\_behavior](#input\_instance\_initiated\_shutdown\_behavior) | Specifies whether an instance stops or terminates when you initiate shutdown from the instance. Can be one of 'stop' or 'terminate'. | `string` | `null` | no | +| [instance\_market\_options](#input\_instance\_market\_options) | Describes the market (purchasing) option for the instances.
See [docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options) for more information. |
object({
market_type = string
spot_options = optional(object({
instance_interruption_behavior = optional(string)
max_price = optional(number)
spot_instance_type = optional(string)
valid_until = optional(string)
}))
})
| `null` | no | | [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no | | [instance\_profile\_enabled](#input\_instance\_profile\_enabled) | Whether an IAM instance profile is created to pass a role to an Amazon EC2 instance when the instance starts | `bool` | `true` | no | | [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no | @@ -98,7 +99,7 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | (Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | +| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | @@ -127,7 +128,6 @@ | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Whether to create a default Security Group with unique name beginning with the normalized prefix. | `bool` | `false` | no | | [security\_groups](#input\_security\_groups) | A list of Security Group IDs to associate with EC2 instance. | `list(string)` | `[]` | no | | [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs | `bool` | `true` | no | -| [spot\_options\_attributes](#input\_spot\_options\_attributes) | Describes the market (purchasing) option for the instances.
To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . |
list(object({
instance_interruption_behavior = string
max_price = number
spot_instance_type = string
valid_until = string
}))
| `[]` | no | | [ssh\_key\_pair](#input\_ssh\_key\_pair) | SSH key pair to be provisioned on the instance | `string` | `null` | no | | [ssm\_patch\_manager\_enabled](#input\_ssm\_patch\_manager\_enabled) | Whether to enable SSM Patch manager | `bool` | `false` | no | | [ssm\_patch\_manager\_iam\_policy\_arn](#input\_ssm\_patch\_manager\_iam\_policy\_arn) | IAM policy ARN to allow Patch Manager to manage the instance. If not provided, `arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore` will be used | `string` | `null` | no | diff --git a/examples/complete/fixtures.us-east-2.spot.tfvars b/examples/complete/fixtures.us-east-2.spot.tfvars index 586c14b..4f14b9f 100644 --- a/examples/complete/fixtures.us-east-2.spot.tfvars +++ b/examples/complete/fixtures.us-east-2.spot.tfvars @@ -58,11 +58,12 @@ ssh_public_key_path = "/secrets" metric_treat_missing_data = "notBreaching" -market_type = "spot" - -spot_options_attributes = [{ - instance_interruption_behavior = "terminate" - max_price = null - spot_instance_type = "one-time" - valid_until = null -}] +instance_market_options = { + market_type = "spot" + spot_options = { + instance_interruption_behavior = "terminate" + max_price = null + spot_instance_type = "one-time" + valid_until = null + } +} diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 5e00bfe..411a2ed 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -87,7 +87,7 @@ module "ec2_instance" { tenancy = var.tenancy metric_treat_missing_data = var.metric_treat_missing_data market_type = var.market_type - spot_options_attributes = var.spot_options_attributes + instance_market_options = var.instance_market_options depends_on = [aws_iam_instance_profile.test] diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index fe090f0..b0e6493 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -114,11 +114,11 @@ output "security_group_name" { } output "instance_lifecycle" { - value = length(var.spot_options_attributes[*]) != 0 ? module.ec2_instance.instance_lifecycle : null + value = try(one(module.ec2_instance[*].instance_lifecycle), null) description = "Indicates whether this is a Spot Instance or a Scheduled Instance" } output "spot_instance_request_id" { - value = length(var.spot_options_attributes[*]) != 0 && var.market_type == "spot" ? module.ec2_instance.spot_instance_request_id : null + value = try(one(module.ec2_instance[*].spot_instance_request_id), null) description = "ID of the Spot Instance request" } diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 0582f61..49ed605 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -63,16 +63,23 @@ variable "market_type" { default = null } -variable "spot_options_attributes" { - type = list(object({ - instance_interruption_behavior = string - max_price = number - spot_instance_type = string - valid_until = string - })) +variable "instance_market_options" { + type = object({ + market_type = string + spot_options = optional(object({ + instance_interruption_behavior = optional(string) + max_price = optional(number) + spot_instance_type = optional(string) + valid_until = optional(string) + })) + }) description = <<-EOT Describes the market (purchasing) option for the instances. - To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options . + See [docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options) for more information. EOT - default = [] + default = null + validation { + condition = contains(["spot", "capacity-block"], var.instance_market_options.market_type) + error_message = "The value of market_type must be one of the following: \"spot\" and \"capacity-block\"." + } } diff --git a/main.tf b/main.tf index 7ebec92..7fc5d32 100644 --- a/main.tf +++ b/main.tf @@ -168,16 +168,20 @@ resource "aws_instance" "default" { cpu_credits = var.burstable_mode } - instance_market_options { - market_type = var.market_type - - dynamic "spot_options" { - for_each = var.market_type == "spot" ? var.spot_options_attributes : [] - 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 "instance_market_options" { + for_each = var.instance_market_options != null ? [var.instance_market_options] : [] + content { + market_type = lookup(instance_market_options.value, "market_type", null) + + dynamic "spot_options" { + for_each = (instance_market_options.value.spot_options != null ? + [instance_market_options.value.spot_options] : []) + content { + instance_interruption_behavior = lookup(spot_options.value, "instance_interruption_behavior", null) + max_price = lookup(spot_options.value, "max_price", null) + spot_instance_type = lookup(spot_options.value, "spot_instance_type", null) + valid_until = lookup(spot_options.value, "valid_until", null) + } } } } diff --git a/variables.tf b/variables.tf index 3a7107d..d6cb320 100644 --- a/variables.tf +++ b/variables.tf @@ -52,18 +52,25 @@ variable "market_type" { default = null } -variable "spot_options_attributes" { - type = list(object({ - instance_interruption_behavior = string - max_price = number - spot_instance_type = string - valid_until = string - })) +variable "instance_market_options" { + type = object({ + market_type = string + spot_options = optional(object({ + instance_interruption_behavior = optional(string) + max_price = optional(number) + spot_instance_type = optional(string) + valid_until = optional(string) + })) + }) description = <<-EOT Describes the market (purchasing) option for the instances. See [docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options) for more information. EOT - default = [] + default = null + validation { + condition = contains(["spot", "capacity-block"], var.instance_market_options.market_type) + error_message = "The value of market_type must be one of the following: \"spot\" and \"capacity-block\"." + } } variable "vpc_id" { From 9291881f947f3b903ff6cbf63a1beeab9bc5f7ed Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 5 Oct 2024 13:47:52 +0700 Subject: [PATCH 24/29] feat(aws_instance): add instance market options block --- variables.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/variables.tf b/variables.tf index d6cb320..da28317 100644 --- a/variables.tf +++ b/variables.tf @@ -46,12 +46,6 @@ variable "burstable_mode" { default = null } -variable "market_type" { - type = string - description = "Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable." - default = null -} - variable "instance_market_options" { type = object({ market_type = string From b9c4c7026b07757b6cc7891a1c3baf6e71c65118 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 5 Oct 2024 13:54:14 +0700 Subject: [PATCH 25/29] feat(aws_instance): add instance market options block --- examples/complete/variables.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 49ed605..09259d9 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -57,12 +57,6 @@ variable "metric_treat_missing_data" { } } -variable "market_type" { - type = string - description = "(Optional) Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable." - default = null -} - variable "instance_market_options" { type = object({ market_type = string From 87c6f06a31f28ccdd8a2082cbc51ddcd686f35b1 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 5 Oct 2024 14:29:35 +0700 Subject: [PATCH 26/29] feat(aws_instance): add instance market options block --- README.md | 1 - docs/terraform.md | 1 - test/src/examples_complete_test.go | 79 +++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2fbd475..6528557 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,6 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 67c25f8..ea848e5 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -99,7 +99,6 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [market\_type](#input\_market\_type) | Type of market for the instance. Valid values are `spot` and `capacity-block`. If the value set is `spot`, the `spot_options_attributes` variable becomes adjustable. | `string` | `null` | no | | [metadata\_http\_endpoint\_enabled](#input\_metadata\_http\_endpoint\_enabled) | Whether the metadata service is available | `bool` | `true` | no | | [metadata\_http\_put\_response\_hop\_limit](#input\_metadata\_http\_put\_response\_hop\_limit) | The desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests. | `number` | `2` | no | | [metadata\_http\_tokens\_required](#input\_metadata\_http\_tokens\_required) | Whether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2. | `bool` | `true` | no | diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go index 8a09141..179895c 100644 --- a/test/src/examples_complete_test.go +++ b/test/src/examples_complete_test.go @@ -24,7 +24,7 @@ func TestExamplesComplete(t *testing.T) { TerraformDir: "../../examples/complete", Upgrade: true, // Variables to pass to our Terraform code using -var-file options - VarFiles: []string{"fixtures.us-east-2.tfvars","fixtures.us-east-2.spot.tfvars"}, + VarFiles: []string{"fixtures.us-east-2.tfvars"}, Vars: map[string]interface{}{ "attributes": attributes, }, @@ -158,3 +158,80 @@ func TestExternalEniComplete(t *testing.T) { // Verify we're getting back the outputs we expect assert.Contains(t, securityGroupARN, "arn:aws:ec2", "SG ID should contains substring 'arn:aws:ec2'") } + +func TestSpotInstanceComplete(t *testing.T) { + t.Parallel() + + rand.Seed(time.Now().UnixNano()) + + randId := strconv.Itoa(rand.Intn(100000)) + attributes := []string{randId} + + terraformOptions := &terraform.Options{ + // The path to where our Terraform code is located + TerraformDir: "../../examples/complete", + Upgrade: true, + // Variables to pass to our Terraform code using -var-file options + VarFiles: []string{"fixtures.us-east-2.spot.tfvars"}, + Vars: map[string]interface{}{ + "attributes": attributes, + }, + } + + // At the end of the test, run `terraform destroy` to clean up any resources that were created + defer terraform.Destroy(t, terraformOptions) + + // This will run `terraform init` and `terraform apply` and fail the test if there are any errors + terraform.InitAndApply(t, terraformOptions) + + // Run `terraform output` to get the value of an output variable + vpcCidr := terraform.Output(t, terraformOptions, "vpc_cidr") + // Verify we're getting back the outputs we expect + assert.Equal(t, "172.16.0.0/16", vpcCidr) + + // Run `terraform output` to get the value of an output variable + privateSubnetCidrs := terraform.OutputList(t, terraformOptions, "private_subnet_cidrs") + // Verify we're getting back the outputs we expect + assert.Equal(t, []string{"172.16.0.0/19", "172.16.32.0/19"}, privateSubnetCidrs) + + // Run `terraform output` to get the value of an output variable + publicSubnetCidrs := terraform.OutputList(t, terraformOptions, "public_subnet_cidrs") + // Verify we're getting back the outputs we expect + assert.Equal(t, []string{"172.16.96.0/19", "172.16.128.0/19"}, publicSubnetCidrs) + + // Run `terraform output` to get the value of an output variable + keyName := terraform.Output(t, terraformOptions, "key_name") + // Verify we're getting back the outputs we expect + assert.Equal(t, "eg-test-ec2-instance-"+randId, keyName) + + // Run `terraform output` to get the value of an output variable + publicDns := terraform.Output(t, terraformOptions, "public_dns") + // Verify we're getting back the outputs we expect + assert.Contains(t, publicDns, ".us-east-2.compute.amazonaws.com") + + // Run `terraform output` to get the value of an output variable + role := terraform.Output(t, terraformOptions, "role") + // Verify we're getting back the outputs we expect + assert.Equal(t, "eg-test-ec2-instance-"+randId+"-profile", role) + + // Run `terraform output` to get the value of an output variable + securityGroupName := terraform.Output(t, terraformOptions, "security_group_name") + expectedSecurityGroupName := "eg-test-ec2-instance-" + randId + // Verify we're getting back the outputs we expect + assert.Equal(t, expectedSecurityGroupName, securityGroupName) + + // Run `terraform output` to get the value of an output variable + securityGroupID := terraform.Output(t, terraformOptions, "security_group_id") + // Verify we're getting back the outputs we expect + assert.Contains(t, securityGroupID, "sg-", "SG ID should contains substring 'sg-'") + + // Run `terraform output` to get the value of an output variable + securityGroupARN := terraform.Output(t, terraformOptions, "security_group_arn") + // Verify we're getting back the outputs we expect + assert.Contains(t, securityGroupARN, "arn:aws:ec2", "SG ID should contains substring 'arn:aws:ec2'") + + // Run `terraform output` to get the value of an output variable + spotInstanceRequestID := terraform.Output(t, terraformOptions, "spot_instance_request_id") + // Verify we're getting back the outputs we expect + assert.Contains(t, spotInstanceRequestID, "sir-", "Spot instance request ID should contains substring 'sir-'") +} \ No newline at end of file From 1e608b5bf864aab9592c15a775405bdc8c2a84cd Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 5 Oct 2024 14:30:50 +0700 Subject: [PATCH 27/29] feat(aws_instance): add instance market options block --- test/src/examples_complete_test.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go index 179895c..1bbc11a 100644 --- a/test/src/examples_complete_test.go +++ b/test/src/examples_complete_test.go @@ -81,15 +81,6 @@ func TestExamplesComplete(t *testing.T) { securityGroupARN := terraform.Output(t, terraformOptions, "security_group_arn") // Verify we're getting back the outputs we expect assert.Contains(t, securityGroupARN, "arn:aws:ec2", "SG ID should contains substring 'arn:aws:ec2'") - - // Run `terraform output` to get the value of an output variable - spotInstanceRequestID := terraform.Output(t, terraformOptions, "spot_instance_request_id") - // Verify we're getting back the outputs we expect - if spotInstanceRequestID != "" { - assert.Contains(t, spotInstanceRequestID, "sir-", "Spot instance request ID should contains substring 'sir-'") - } else { - t.Log("No spot_instance_request_id output found, skipping spot instance request assertions.") - } } func TestExternalEniComplete(t *testing.T) { @@ -234,4 +225,4 @@ func TestSpotInstanceComplete(t *testing.T) { spotInstanceRequestID := terraform.Output(t, terraformOptions, "spot_instance_request_id") // Verify we're getting back the outputs we expect assert.Contains(t, spotInstanceRequestID, "sir-", "Spot instance request ID should contains substring 'sir-'") -} \ No newline at end of file +} From 1ccfbd01c6a1b931f2e36cc2d20e216c9bc3ded8 Mon Sep 17 00:00:00 2001 From: haidar ali Date: Sat, 5 Oct 2024 14:41:13 +0700 Subject: [PATCH 28/29] feat(aws_instance): add instance market options block --- examples/complete/fixtures.us-east-2.tfvars | 2 +- examples/complete/main.tf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/complete/fixtures.us-east-2.tfvars b/examples/complete/fixtures.us-east-2.tfvars index f639a6e..0e78013 100644 --- a/examples/complete/fixtures.us-east-2.tfvars +++ b/examples/complete/fixtures.us-east-2.tfvars @@ -56,4 +56,4 @@ security_group_rules = [ ssh_public_key_path = "/secrets" -metric_treat_missing_data = "notBreaching" +metric_treat_missing_data = "notBreaching" \ No newline at end of file diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 411a2ed..11b4965 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -86,7 +86,6 @@ module "ec2_instance" { instance_profile = aws_iam_instance_profile.test.name tenancy = var.tenancy metric_treat_missing_data = var.metric_treat_missing_data - market_type = var.market_type instance_market_options = var.instance_market_options depends_on = [aws_iam_instance_profile.test] From 8b67bbc10884385fc5e4e00c1e395e01a6cad34e Mon Sep 17 00:00:00 2001 From: haidar ali Date: Mon, 21 Oct 2024 08:40:14 +0700 Subject: [PATCH 29/29] feat(aws_instance): add instance market options block --- examples/complete/variables.tf | 2 +- variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 09259d9..2bbc692 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -73,7 +73,7 @@ variable "instance_market_options" { EOT default = null validation { - condition = contains(["spot", "capacity-block"], var.instance_market_options.market_type) + condition = var.instance_market_options != null ? contains(["spot", "capacity-block"], var.instance_market_options.market_type) : true error_message = "The value of market_type must be one of the following: \"spot\" and \"capacity-block\"." } } diff --git a/variables.tf b/variables.tf index da28317..c78fd69 100644 --- a/variables.tf +++ b/variables.tf @@ -62,7 +62,7 @@ variable "instance_market_options" { EOT default = null validation { - condition = contains(["spot", "capacity-block"], var.instance_market_options.market_type) + condition = var.instance_market_options != null ? contains(["spot", "capacity-block"], var.instance_market_options.market_type) : true error_message = "The value of market_type must be one of the following: \"spot\" and \"capacity-block\"." } }