Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws_instance): add instance market options block #202

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0a889b4
feat(aws_instance): add instance market options block
haidargit Aug 4, 2024
647a05e
Update variables.tf
haidargit Aug 16, 2024
1595bb1
feat(aws_instance): add instance market options block
haidargit Aug 16, 2024
799c212
feat(aws_instance): add instance market options block
haidargit Aug 17, 2024
e1c34b3
feat(aws_instance): add instance market options block
haidargit Aug 18, 2024
c812ddc
feat(aws_instance): add instance market options block
haidargit Aug 18, 2024
508bc80
feat(aws_instance): add instance market options block
haidargit Aug 18, 2024
0b68992
feat(aws_instance): add instance market options block
haidargit Aug 18, 2024
59321a1
feat(aws_instance): add instance market options block
haidargit Aug 18, 2024
b989d10
feat(aws_instance): add instance market options block
haidargit Aug 18, 2024
f53df70
Update outputs.tf
haidargit Aug 19, 2024
f8dfb38
Update variables.tf
haidargit Aug 19, 2024
39b44a7
feat(aws_instance): add instance market options block
haidargit Aug 19, 2024
4857378
feat(aws_instance): add instance market options block
haidargit Sep 1, 2024
e03e799
feat(aws_instance): add instance market options block
haidargit Sep 21, 2024
ced18c0
feat(aws_instance): add instance market options block
haidargit Sep 21, 2024
5e3ea7d
feat(aws_instance): add instance market options block
haidargit Sep 21, 2024
1b2145c
feat(aws_instance): add instance market options block
haidargit Sep 22, 2024
4b1fd2c
Update variables.tf
haidargit Sep 22, 2024
b0a3945
Update variables.tf
haidargit Sep 22, 2024
d66b95a
Update outputs.tf
haidargit Sep 22, 2024
f2b4e07
Update outputs.tf
haidargit Sep 22, 2024
c184687
feat(aws_instance): add instance market options block
haidargit Oct 4, 2024
d8e33c1
feat(aws_instance): add instance market options block
haidargit Oct 5, 2024
79b68a8
feat(aws_instance): add instance market options block
haidargit Oct 5, 2024
c34444c
feat(aws_instance): add instance market options block
haidargit Oct 5, 2024
2b35658
feat(aws_instance): add instance market options block
haidargit Oct 5, 2024
a8830b0
feat(aws_instance): add instance market options block
haidargit Oct 5, 2024
225880e
Merge branch 'main' into feature/spot-instance-enablement
joe-niland Oct 15, 2024
b6ae4ad
feat(aws_instance): add instance market options block
haidargit Oct 21, 2024
1c0f892
Merge branch 'main' into feature/spot-instance-enablement
joe-niland Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ Available targets:
| <a name="input_external_network_interfaces"></a> [external\_network\_interfaces](#input\_external\_network\_interfaces) | The external interface definitions to attach to the instances. This depends on the instance type | <pre>list(object({<br/> delete_on_termination = bool<br/> device_index = number<br/> network_card_index = number<br/> network_interface_id = string<br/> }))</pre> | `null` | no |
| <a name="input_id_length_limit"></a> [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).<br/>Set to `0` for unlimited length.<br/>Set to `null` for keep the existing setting, which defaults to `0`.<br/>Does not affect `id_full`. | `number` | `null` | no |
| <a name="input_instance_initiated_shutdown_behavior"></a> [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 |
| <a name="input_instance_market_options"></a> [instance\_market\_options](#input\_instance\_market\_options) | Describes the market (purchasing) option for the instances.<br/>See [docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#spot-options) for more information. | <pre>object({<br/> market_type = string<br/> spot_options = optional(object({<br/> instance_interruption_behavior = optional(string)<br/> max_price = optional(number)<br/> spot_instance_type = optional(string)<br/> valid_until = optional(string)<br/> }))<br/> })</pre> | `null` | no |
| <a name="input_instance_profile"></a> [instance\_profile](#input\_instance\_profile) | A pre-defined profile to attach to the instance (default is to build our own) | `string` | `""` | no |
| <a name="input_instance_profile_enabled"></a> [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 |
| <a name="input_instance_type"></a> [instance\_type](#input\_instance\_type) | The type of the instance | `string` | `"t2.micro"` | no |
Expand Down Expand Up @@ -310,6 +311,7 @@ Available targets:
| <a name="output_arn"></a> [arn](#output\_arn) | ARN of the instance |
| <a name="output_ebs_ids"></a> [ebs\_ids](#output\_ebs\_ids) | IDs of EBSs |
| <a name="output_id"></a> [id](#output\_id) | Disambiguated ID of the instance |
| <a name="output_instance_lifecycle"></a> [instance\_lifecycle](#output\_instance\_lifecycle) | Indicates whether this is a Spot Instance or a Scheduled Instance |
| <a name="output_instance_profile"></a> [instance\_profile](#output\_instance\_profile) | Name of the instance's profile (either built or supplied) |
| <a name="output_name"></a> [name](#output\_name) | Instance name |
| <a name="output_primary_network_interface_id"></a> [primary\_network\_interface\_id](#output\_primary\_network\_interface\_id) | ID of the instance's primary network interface |
Expand All @@ -323,6 +325,7 @@ Available targets:
| <a name="output_security_group_id"></a> [security\_group\_id](#output\_security\_group\_id) | EC2 instance Security Group ID |
| <a name="output_security_group_ids"></a> [security\_group\_ids](#output\_security\_group\_ids) | IDs on the AWS Security Groups associated with the instance |
| <a name="output_security_group_name"></a> [security\_group\_name](#output\_security\_group\_name) | EC2 instance Security Group name |
| <a name="output_spot_instance_request_id"></a> [spot\_instance\_request\_id](#output\_spot\_instance\_request\_id) | ID of the Spot Instance request |
| <a name="output_ssh_key_pair"></a> [ssh\_key\_pair](#output\_ssh\_key\_pair) | Name of the SSH key pair provisioned on the instance |
<!-- markdownlint-restore -->

Expand Down
36 changes: 20 additions & 16 deletions docs/terraform.md

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions examples/complete/fixtures.us-east-2.spot.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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 = {
market_type = "spot"
spot_options = {
instance_interruption_behavior = "terminate"
max_price = null
spot_instance_type = "one-time"
valid_until = null
}
}
1 change: 1 addition & 0 deletions examples/complete/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ 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 = var.instance_market_options

depends_on = [aws_iam_instance_profile.test]

Expand Down
10 changes: 10 additions & 0 deletions examples/complete/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 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 = try(one(module.ec2_instance[*].spot_instance_request_id), null)
description = "ID of the Spot Instance request"
}
23 changes: 22 additions & 1 deletion examples/complete/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,25 @@ 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\"."
}
}
}

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 = null
validation {
condition = contains(["spot", "capacity-block"], var.instance_market_options.market_type)
haidargit marked this conversation as resolved.
Show resolved Hide resolved
error_message = "The value of market_type must be one of the following: \"spot\" and \"capacity-block\"."
}
}
2 changes: 1 addition & 1 deletion examples/complete/versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.0"
version = ">= 4.7.0"
}
null = {
source = "hashicorp/null"
Expand Down
2 changes: 1 addition & 1 deletion examples/external-eni/versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.0"
version = ">= 4.7.0"
}
null = {
source = "hashicorp/null"
Expand Down
18 changes: 18 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,24 @@ resource "aws_instance" "default" {
cpu_credits = var.burstable_mode
}

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)
}
}
}
}

tags = module.this.tags

volume_tags = var.volume_tags_enabled ? module.this.tags : {}
Expand Down
10 changes: 10 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,13 @@ output "security_group_name" {
value = module.security_group.name
description = "EC2 instance Security Group name"
}

output "instance_lifecycle" {
value = try(one(aws_instance.default[*].instance_lifecycle), null)
description = "Indicates whether this is a Spot Instance or a Scheduled Instance"
}

output "spot_instance_request_id" {
value = try(one(aws_instance.default[*].spot_instance_request_id), null)
description = "ID of the Spot Instance request"
}
77 changes: 77 additions & 0 deletions test/src/examples_complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,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-'")
}
21 changes: 21 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ variable "burstable_mode" {
default = null
}

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 = null
haidargit marked this conversation as resolved.
Show resolved Hide resolved
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" {
type = string
description = "The ID of the VPC that the instance security group belongs to"
Expand Down