From 91611c61056b53e0fb2800136fe7b7ec9de76499 Mon Sep 17 00:00:00 2001 From: Alex Thewsey Date: Tue, 23 Sep 2025 18:32:18 +0800 Subject: [PATCH] feat: WIP push infra within agent package(s) Initial draft to push agent infrastructure (ECR repository + image, IAM execution role) within the agent package itself, in preparation for expanding the repo with multiple example agents. --- cx-agent-backend/infra/main.tf | 66 +++++++++++++++++++++++++++++ cx-agent-backend/infra/outputs.tf | 14 ++++++ cx-agent-backend/infra/terraform.tf | 5 +++ cx-agent-backend/infra/variables.tf | 21 +++++++++ infra/main.tf | 40 +++++++++-------- 5 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 cx-agent-backend/infra/main.tf create mode 100644 cx-agent-backend/infra/outputs.tf create mode 100644 cx-agent-backend/infra/terraform.tf create mode 100644 cx-agent-backend/infra/variables.tf diff --git a/cx-agent-backend/infra/main.tf b/cx-agent-backend/infra/main.tf new file mode 100644 index 0000000..e995025 --- /dev/null +++ b/cx-agent-backend/infra/main.tf @@ -0,0 +1,66 @@ +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +locals { + image_src_path = "${path.module}/.." + image_src_hash = sha512( + join( + "", + # TODO: Find a way to exclude .venv, dist, and potentially other subfolders: + [for f in fileset(".", "${local.image_src_path}/**") : filesha512(f)] + ) + ) + + image_build_extra_args = "--platform linux/arm64" + image_build_push_cmd = <<-EOT + aws ecr get-login-password | finch login --username AWS \ + --password-stdin ${aws_ecr_repository.ecr_repository.repository_url} && + + finch build ${local.image_build_extra_args} \ + -t ${aws_ecr_repository.ecr_repository.repository_url}:${var.image_tag} \ + ${local.image_src_path} && + + finch push ${aws_ecr_repository.ecr_repository.repository_url}:${var.image_tag} + EOT +} + +resource "aws_ecr_repository" "ecr_repository" { + name = var.agent_name +} + +resource "terraform_data" "ecr_image" { + triggers_replace = [ + aws_ecr_repository.ecr_repository.id, + var.force_image_rebuild == true ? timestamp() : local.image_src_hash + ] + + input = "${aws_ecr_repository.ecr_repository.repository_url}:${var.image_tag}" + + provisioner "local-exec" { + command = local.image_build_push_cmd + } +} + +resource "aws_iam_role" "execution_role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AssumeRolePolicy" + Effect = "Allow" + Principal = { + Service = "bedrock-agentcore.amazonaws.com" + } + Action = "sts:AssumeRole" + Condition = { + StringEquals = { + "aws:SourceAccount" = data.aws_caller_identity.current.account_id + } + ArnLike = { + "aws:SourceArn" = "arn:aws:bedrock-agentcore:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*" + } + } + } + ] + }) +} diff --git a/cx-agent-backend/infra/outputs.tf b/cx-agent-backend/infra/outputs.tf new file mode 100644 index 0000000..e4331d2 --- /dev/null +++ b/cx-agent-backend/infra/outputs.tf @@ -0,0 +1,14 @@ +output "ecr_repository_uri" { + description = "URI of the Amazon ECR repository for the agent container image" + value = aws_ecr_repository.ecr_repository.repository_url +} + +output "ecr_image_uri" { + description = "URI of the Amazon ECR repository for the agent container image" + value = terraform_data.ecr_image.output +} + +output "role_arn" { + description = "ARN of the IAM role for the agent" + value = aws_iam_role.execution_role.arn +} \ No newline at end of file diff --git a/cx-agent-backend/infra/terraform.tf b/cx-agent-backend/infra/terraform.tf new file mode 100644 index 0000000..2411285 --- /dev/null +++ b/cx-agent-backend/infra/terraform.tf @@ -0,0 +1,5 @@ +terraform { + required_providers { + aws = {} + } +} diff --git a/cx-agent-backend/infra/variables.tf b/cx-agent-backend/infra/variables.tf new file mode 100644 index 0000000..33c7c68 --- /dev/null +++ b/cx-agent-backend/infra/variables.tf @@ -0,0 +1,21 @@ +variable "agent_name" { + description = "Unique name of the agent" + default = "cx_agent_backend" + type = string + validation { + condition = can(regex("^[a-zA-Z0-9_]+$", var.agent_name)) + error_message = "Agent name must contain only letters, numbers, and underscores." + } +} + +variable "force_image_rebuild" { + description = "Set true to force rebuild & push of image to ECR even if source appears unchanged" + default = false + type = bool +} + +variable "image_tag" { + description = "Tag to apply to the pushed container image in Amazon ECR" + default = "latest" + type = string +} diff --git a/infra/main.tf b/infra/main.tf index 668cf31..b45d60b 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -1,26 +1,30 @@ # Bedrock Agent Role module "bedrock_role" { - source = "./modules/agentcore-iam-role" - role_name = var.bedrock_role_name + source = "./modules/agentcore-iam-role" + role_name = var.bedrock_role_name knowledge_base_id = module.kb_stack.knowledge_base_id - guardrail_id = module.guardrail.guardrail_id + guardrail_id = module.guardrail.guardrail_id +} + +# Example Agent +module "cx_agent_demo" { + source = "../cx-agent-backend/infra" } # Knowledge Base Stack module "kb_stack" { - source = "./modules/kb-stack" - name = var.kb_stack_name - bucket_name = var.kb_bucket_name + source = "./modules/kb-stack" + name = var.kb_stack_name kb_model_arn = var.kb_model_arn } # Guardrail Module module "guardrail" { source = "./modules/bedrock-guardrails" - guardrail_name = "agentic-ai-guardrail" - blocked_input_messaging = "Your input contains content that violates our policy." + guardrail_name = "agentic-ai-guardrail" + blocked_input_messaging = "Your input contains content that violates our policy." blocked_outputs_messaging = "The response was blocked due to policy violations." - description = "Guardrail for agentic AI foundation" + description = "Guardrail for agentic AI foundation" } # Cognito Module @@ -47,19 +51,19 @@ module "parameters" { # Secrets Module (depends on Cognito for client secret) module "secrets" { source = "./modules/secrets" - + cognito_client_secret = module.cognito.client_secret - + # Placeholder values - replace with actual values - zendesk_domain = var.zendesk_domain - zendesk_email = var.zendesk_email - zendesk_api_token = var.zendesk_api_token - langfuse_host = var.langfuse_host + zendesk_domain = var.zendesk_domain + zendesk_email = var.zendesk_email + zendesk_api_token = var.zendesk_api_token + langfuse_host = var.langfuse_host langfuse_public_key = var.langfuse_public_key langfuse_secret_key = var.langfuse_secret_key - gateway_url = var.gateway_url - gateway_api_key = var.gateway_api_key - tavily_api_key = var.tavily_api_key + gateway_url = var.gateway_url + gateway_api_key = var.gateway_api_key + tavily_api_key = var.tavily_api_key depends_on = [module.cognito] }