diff --git a/.gitignore b/.gitignore index 4a1f3ec..57400d1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ terraform.rc go.mod go.sum + + +.amazonq/ \ No newline at end of file diff --git a/README.md b/README.md index 1e69b7c..7e49c5b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Please refer to the [examples](./examples/basic) on how to get started. | Name | Source | Version | |------|--------|---------| -| [ecs\_cluster](#module\_ecs\_cluster) | git::https://github.com/terraform-aws-modules/terraform-aws-ecs | 6b52c965734d95767d8e20d965afcd0db29dae5e | +| [ecs\_cluster](#module\_ecs\_cluster) | git::https://github.com/terraform-aws-modules/terraform-aws-ecs | be968fc4af733fae2ac41dfb3c34dce7712e028f | ## Resources @@ -86,6 +86,7 @@ Please refer to the [examples](./examples/basic) on how to get started. | [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign a public IP address to the ECS tasks. Set to true when using public subnets. | `bool` | `false` | no | | [cloudwatch\_log\_group\_name](#input\_cloudwatch\_log\_group\_name) | The name of the CloudWatch log group where agent logs will be sent. | `string` | `"/hcp/hcp-terraform-agent"` | no | | [cloudwatch\_log\_group\_retention](#input\_cloudwatch\_log\_group\_retention) | The number of days to retain logs in the CloudWatch log group. | `number` | `365` | no | +| [cpu\_architecture](#input\_cpu\_architecture) | The CPU architecture for the ECS task. Valid values are X86\_64 and ARM64. | `string` | `"X86_64"` | no | | [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Whether the CloudWatch log group should be created. | `bool` | `true` | no | | [create\_ecs\_cluster](#input\_create\_ecs\_cluster) | Whether to create a new ECS cluster for the agent. | `bool` | `true` | no | | [create\_tfe\_agent\_pool](#input\_create\_tfe\_agent\_pool) | Whether to omit agent pool/token creation | `bool` | `true` | no | diff --git a/examples/arm64/README.md b/examples/arm64/README.md new file mode 100644 index 0000000..8ac7636 --- /dev/null +++ b/examples/arm64/README.md @@ -0,0 +1,21 @@ +# ARM64 Example + +This example demonstrates how to deploy HCP Terraform agents on ARM64 architecture using AWS Fargate. + +## Key Features + +- Uses ARM64 CPU architecture for potentially better cost efficiency +- Deploys on Fargate Spot instances for additional cost savings +- Creates a new VPC with private subnets for secure agent deployment + +## Usage + +```bash +terraform init +terraform plan +terraform apply +``` + +## Configuration + +The example sets `cpu_architecture = "ARM64"` to deploy agents on ARM64-based Fargate instances instead of the default x86_64 architecture. diff --git a/examples/arm64/main.tf b/examples/arm64/main.tf new file mode 100644 index 0000000..4cce987 --- /dev/null +++ b/examples/arm64/main.tf @@ -0,0 +1,59 @@ +##################################################################################### +# Terraform module examples are meant to show an _example_ on how to use a module +# per use-case. The code below should not be copied directly but referenced in order +# to build your own root module that invokes this module +##################################################################################### + +data "aws_availability_zones" "available" {} + +locals { + region = "us-west-2" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + name = "ecs-${basename(path.cwd)}" + vpc_cidr = "10.0.0.0/16" + tags = { + Terraform = "true" + Environment = "dev" + ManagedBy = "aws-ia/terraform-aws-tf-cloud-agents" + } +} + +##################################################################################### +# MODULE INVOCATION +##################################################################################### + +module "agent_pool" { + source = "../../" + name = local.name + hcp_terraform_org_name = var.hcp_terraform_org_name + agent_image = "hashicorp/tfc-agent:latest" + cpu_architecture = "ARM64" + use_spot_instances = true + agent_cpu = 512 + agent_memory = 2048 + num_agents = 1 + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + task_policy_arns = ["arn:aws:iam::aws:policy/ReadOnlyAccess"] + tags = local.tags +} + +##################################################################################### +# VPC +##################################################################################### + +module "vpc" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc?ref=25322b6b6be69db6cca7f167d7b0e5327156a595" # v5.8.1 + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/examples/arm64/outputs.tf b/examples/arm64/outputs.tf new file mode 100644 index 0000000..1628ed3 --- /dev/null +++ b/examples/arm64/outputs.tf @@ -0,0 +1,49 @@ +output "agent_pool_name" { + description = "Name of the HCP Terraform or HCP Terraform Enterprise agent pool." + value = module.agent_pool.agent_pool_name +} + +output "agent_pool_id" { + description = "ID of the HCP Terraform or HCP Terraform Enterprise agent pool." + value = module.agent_pool.agent_pool_id +} + +output "ecs_service_arn" { + description = "ARN of the ECS service." + value = module.agent_pool.ecs_service_arn +} + +output "ecs_task_arn" { + description = "ARN of the ECS task definition." + value = module.agent_pool.ecs_task_arn +} + +output "ecs_task_revision" { + description = "Revision number of the ECS task definition." + value = module.agent_pool.ecs_task_revision +} + +output "log_stream_prefix" { + description = "Prefix for the CloudWatch log stream." + value = module.agent_pool.log_stream_prefix +} + +output "security_group_name" { + description = "Name of the VPC security group attached to the service." + value = module.agent_pool.security_group_name +} + +output "security_group_id" { + description = "ID of the VPC security group attached to the service." + value = module.agent_pool.security_group_id +} + +output "task_role_name" { + description = "Name of the IAM role attached to the task containers." + value = module.agent_pool.task_role_name +} + +output "task_role_arn" { + description = "ARN of the IAM role attached to the task containers." + value = module.agent_pool.task_role_arn +} diff --git a/examples/arm64/providers.tf b/examples/arm64/providers.tf new file mode 100644 index 0000000..538f05e --- /dev/null +++ b/examples/arm64/providers.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.47.0" + } + } +} + + +provider "aws" { + region = local.region +} + +provider "tfe" { + token = var.tfe_token +} \ No newline at end of file diff --git a/examples/arm64/variables.tf b/examples/arm64/variables.tf new file mode 100644 index 0000000..d2c7306 --- /dev/null +++ b/examples/arm64/variables.tf @@ -0,0 +1,9 @@ +variable "hcp_terraform_org_name" { + type = string + description = "The name of the HCP Terraform organization." +} + +variable "tfe_token" { + type = string + description = "Terraform token to be used to create the agent pool." +} \ No newline at end of file diff --git a/main.tf b/main.tf index cc16ec2..7e8b549 100644 --- a/main.tf +++ b/main.tf @@ -44,7 +44,7 @@ data "aws_iam_policy_document" "kms_key_policy" { "kms:*", ] resources = [ - "arn:aws:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:key/*" + "arn:aws:kms:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:key/*" ] } statement { @@ -53,7 +53,7 @@ data "aws_iam_policy_document" "kms_key_policy" { principals { type = "Service" identifiers = [ - "logs.${data.aws_region.current.name}.amazonaws.com", + "logs.${data.aws_region.current.id}.amazonaws.com", "ssm.amazonaws.com" ] } @@ -65,7 +65,7 @@ data "aws_iam_policy_document" "kms_key_policy" { "kms:Describe*" ] resources = [ - "arn:aws:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:key/*" + "arn:aws:kms:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:key/*" ] } } @@ -94,6 +94,7 @@ resource "aws_ecs_task_definition" "hcp_terraform_agent" { runtime_platform { operating_system_family = "LINUX" + cpu_architecture = var.cpu_architecture } container_definitions = jsonencode( @@ -107,7 +108,7 @@ resource "aws_ecs_task_definition" "hcp_terraform_agent" { options : { awslogs-create-group : "true", awslogs-group : var.create_cloudwatch_log_group ? aws_cloudwatch_log_group.cloudwatch[0].name : var.cloudwatch_log_group_name - awslogs-region : data.aws_region.current.name + awslogs-region : data.aws_region.current.id awslogs-stream-prefix : "hcp-tf-${var.hcp_terraform_org_name}-${var.name}" } } @@ -209,20 +210,16 @@ resource "aws_security_group_rule" "allow_egress" { module "ecs_cluster" { count = var.create_ecs_cluster ? 1 : 0 - source = "git::https://github.com/terraform-aws-modules/terraform-aws-ecs?ref=6b52c965734d95767d8e20d965afcd0db29dae5e" # v5.11.2 + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ecs?ref=be968fc4af733fae2ac41dfb3c34dce7712e028f" # v6.6.1 cluster_name = var.name - fargate_capacity_providers = { + default_capacity_provider_strategy = { FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = var.use_spot_instances ? 0 : 100 } FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = var.use_spot_instances ? 100 : 0 } } diff --git a/variables.tf b/variables.tf index bfe53d4..a6ae798 100644 --- a/variables.tf +++ b/variables.tf @@ -85,6 +85,16 @@ variable "agent_image" { default = "hashicorp/tfc-agent:latest" } +variable "cpu_architecture" { + type = string + description = "The CPU architecture for the ECS task. Valid values are X86_64 and ARM64." + default = "X86_64" + validation { + condition = contains(["X86_64", "ARM64"], var.cpu_architecture) + error_message = "Valid values: X86_64, ARM64" + } +} + variable "agent_single_execution" { type = bool description = "Whether to use single-execution mode."