diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..b7548c3 --- /dev/null +++ b/main.tf @@ -0,0 +1,583 @@ +data "aws_partition" "current" {} + +locals { + create = var.create && var.putin_khuylo + + is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a|4g){1}\\..*$/", "1") == "1" ? true : false +} + +data "aws_ssm_parameter" "this" { + count = local.create ? 1 : 0 + + name = var.ami_ssm_parameter +} + +################################################################################ +# Instance +################################################################################ + +resource "aws_instance" "this" { + count = local.create && !var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + + content { + core_count = try(cpu_options.value.core_count, null) + threads_per_core = try(cpu_options.value.threads_per_core, null) + amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = ephemeral_block_device.value.device_name + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + dynamic "maintenance_options" { + for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + + content { + auto_recovery = try(maintenance_options.value.auto_recovery, null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + disable_api_stop = var.disable_api_stop + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null +} + +################################################################################ +# Instance - Ignore AMI Changes +################################################################################ + +resource "aws_instance" "ignore_ami" { + count = local.create && var.ignore_ami_changes && !var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + + content { + core_count = try(cpu_options.value.core_count, null) + threads_per_core = try(cpu_options.value.threads_per_core, null) + amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = ephemeral_block_device.value.device_name + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + dynamic "maintenance_options" { + for_each = length(var.maintenance_options) > 0 ? [var.maintenance_options] : [] + + content { + auto_recovery = try(maintenance_options.value.auto_recovery, null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + disable_api_stop = var.disable_api_stop + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null + + lifecycle { + ignore_changes = [ + ami + ] + } +} + +################################################################################ +# Spot Instance +################################################################################ + +resource "aws_spot_instance_request" "this" { + count = local.create && var.create_spot_instance ? 1 : 0 + + ami = try(coalesce(var.ami, nonsensitive(data.aws_ssm_parameter.this[0].value)), null) + instance_type = var.instance_type + cpu_core_count = var.cpu_core_count + cpu_threads_per_core = var.cpu_threads_per_core + hibernation = var.hibernation + + user_data = var.user_data + user_data_base64 = var.user_data_base64 + user_data_replace_on_change = var.user_data_replace_on_change + + availability_zone = var.availability_zone + subnet_id = var.subnet_id + vpc_security_group_ids = var.vpc_security_group_ids + + key_name = var.key_name + monitoring = var.monitoring + get_password_data = var.get_password_data + iam_instance_profile = var.create_iam_instance_profile ? aws_iam_instance_profile.this[0].name : var.iam_instance_profile + + associate_public_ip_address = var.associate_public_ip_address + private_ip = var.private_ip + secondary_private_ips = var.secondary_private_ips + ipv6_address_count = var.ipv6_address_count + ipv6_addresses = var.ipv6_addresses + + ebs_optimized = var.ebs_optimized + + # Spot request specific attributes + spot_price = var.spot_price + wait_for_fulfillment = var.spot_wait_for_fulfillment + spot_type = var.spot_type + launch_group = var.spot_launch_group + block_duration_minutes = var.spot_block_duration_minutes + instance_interruption_behavior = var.spot_instance_interruption_behavior + valid_until = var.spot_valid_until + valid_from = var.spot_valid_from + # End spot request specific attributes + + dynamic "cpu_options" { + for_each = length(var.cpu_options) > 0 ? [var.cpu_options] : [] + + content { + core_count = try(cpu_options.value.core_count, null) + threads_per_core = try(cpu_options.value.threads_per_core, null) + amd_sev_snp = try(cpu_options.value.amd_sev_snp, null) + } + } + + dynamic "capacity_reservation_specification" { + for_each = length(var.capacity_reservation_specification) > 0 ? [var.capacity_reservation_specification] : [] + + content { + capacity_reservation_preference = try(capacity_reservation_specification.value.capacity_reservation_preference, null) + + dynamic "capacity_reservation_target" { + for_each = try([capacity_reservation_specification.value.capacity_reservation_target], []) + content { + capacity_reservation_id = try(capacity_reservation_target.value.capacity_reservation_id, null) + capacity_reservation_resource_group_arn = try(capacity_reservation_target.value.capacity_reservation_resource_group_arn, null) + } + } + } + } + + dynamic "root_block_device" { + for_each = var.root_block_device + + content { + delete_on_termination = try(root_block_device.value.delete_on_termination, null) + encrypted = try(root_block_device.value.encrypted, null) + iops = try(root_block_device.value.iops, null) + kms_key_id = lookup(root_block_device.value, "kms_key_id", null) + volume_size = try(root_block_device.value.volume_size, null) + volume_type = try(root_block_device.value.volume_type, null) + throughput = try(root_block_device.value.throughput, null) + tags = try(root_block_device.value.tags, null) + } + } + + dynamic "ebs_block_device" { + for_each = var.ebs_block_device + + content { + delete_on_termination = try(ebs_block_device.value.delete_on_termination, null) + device_name = ebs_block_device.value.device_name + encrypted = try(ebs_block_device.value.encrypted, null) + iops = try(ebs_block_device.value.iops, null) + kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null) + snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null) + volume_size = try(ebs_block_device.value.volume_size, null) + volume_type = try(ebs_block_device.value.volume_type, null) + throughput = try(ebs_block_device.value.throughput, null) + tags = try(ebs_block_device.value.tags, null) + } + } + + dynamic "ephemeral_block_device" { + for_each = var.ephemeral_block_device + + content { + device_name = ephemeral_block_device.value.device_name + no_device = try(ephemeral_block_device.value.no_device, null) + virtual_name = try(ephemeral_block_device.value.virtual_name, null) + } + } + + dynamic "metadata_options" { + for_each = length(var.metadata_options) > 0 ? [var.metadata_options] : [] + + content { + http_endpoint = try(metadata_options.value.http_endpoint, "enabled") + http_tokens = try(metadata_options.value.http_tokens, "optional") + http_put_response_hop_limit = try(metadata_options.value.http_put_response_hop_limit, 1) + instance_metadata_tags = try(metadata_options.value.instance_metadata_tags, null) + } + } + + dynamic "network_interface" { + for_each = var.network_interface + + content { + device_index = network_interface.value.device_index + network_interface_id = lookup(network_interface.value, "network_interface_id", null) + delete_on_termination = try(network_interface.value.delete_on_termination, false) + } + } + + dynamic "launch_template" { + for_each = length(var.launch_template) > 0 ? [var.launch_template] : [] + + content { + id = lookup(var.launch_template, "id", null) + name = lookup(var.launch_template, "name", null) + version = lookup(var.launch_template, "version", null) + } + } + + enclave_options { + enabled = var.enclave_options_enabled + } + + source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check + disable_api_termination = var.disable_api_termination + instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior + placement_group = var.placement_group + tenancy = var.tenancy + host_id = var.host_id + + credit_specification { + cpu_credits = local.is_t_instance_type ? var.cpu_credits : null + } + + timeouts { + create = try(var.timeouts.create, null) + delete = try(var.timeouts.delete, null) + } + + tags = merge({ "Name" = var.name }, var.instance_tags, var.tags) + volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null +} + +################################################################################ +# IAM Role / Instance Profile +################################################################################ + +locals { + iam_role_name = try(coalesce(var.iam_role_name, var.name), "") +} + +data "aws_iam_policy_document" "assume_role_policy" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + statement { + sid = "EC2AssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "this" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.assume_role_policy[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "this" { + for_each = { for k, v in var.iam_role_policies : k => v if var.create && var.create_iam_instance_profile } + + policy_arn = each.value + role = aws_iam_role.this[0].name +} + +resource "aws_iam_instance_profile" "this" { + count = var.create && var.create_iam_instance_profile ? 1 : 0 + + role = aws_iam_role.this[0].name + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + + tags = merge(var.tags, var.iam_role_tags) + + lifecycle { + create_before_destroy = true + } +}