diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index 9f6b07beb..e8e2de981 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -100,6 +100,15 @@ on: TF_VAR_open_epc_api_token: required: false + + TF_VAR_abri_relay_url: + required: false + TF_VAR_abri_relay_username: + required: false + TF_VAR_abri_relay_password: + required: false + TF_VAR_abri_relay_default_resource: + required: false jobs: deploy: runs-on: ubuntu-latest @@ -175,6 +184,10 @@ jobs: TF_VAR_magicplan_api_key: ${{ secrets.TF_VAR_magicplan_api_key }} TF_VAR_openai_api_key: ${{ secrets.TF_VAR_openai_api_key }} TF_VAR_open_epc_api_token: ${{ secrets.TF_VAR_open_epc_api_token }} + TF_VAR_abri_relay_url: ${{ secrets.TF_VAR_abri_relay_url }} + TF_VAR_abri_relay_username: ${{ secrets.TF_VAR_abri_relay_username }} + TF_VAR_abri_relay_password: ${{ secrets.TF_VAR_abri_relay_password }} + TF_VAR_abri_relay_default_resource: ${{ secrets.TF_VAR_abri_relay_default_resource }} run: | ECR_REPO_URL_VAR="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then @@ -228,6 +241,10 @@ jobs: TF_VAR_magicplan_api_key: ${{ secrets.TF_VAR_magicplan_api_key }} TF_VAR_openai_api_key: ${{ secrets.TF_VAR_openai_api_key }} TF_VAR_open_epc_api_token: ${{ secrets.TF_VAR_open_epc_api_token }} + TF_VAR_abri_relay_url: ${{ secrets.TF_VAR_abri_relay_url }} + TF_VAR_abri_relay_username: ${{ secrets.TF_VAR_abri_relay_username }} + TF_VAR_abri_relay_password: ${{ secrets.TF_VAR_abri_relay_password }} + TF_VAR_abri_relay_default_resource: ${{ secrets.TF_VAR_abri_relay_default_resource }} run: | EXTRA_VARS="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 5e469dc66..468b0c6eb 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -700,6 +700,49 @@ jobs: TF_VAR_magicplan_customer_id: ${{ secrets.MAGICPLAN_CUSTOMER_ID }} TF_VAR_magicplan_api_key: ${{ secrets.MAGICPLAN_API_KEY }} + # ============================================================ + # Build Abri Lambda image + # ============================================================ + abri_image: + needs: [determine_stage, shared_terraform] + uses: ./.github/workflows/_build_image.yml + with: + ecr_repo: abri-${{ needs.determine_stage.outputs.stage }} + dockerfile_path: applications/abri/handler/Dockerfile + build_context: . + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + + # ============================================================ + # Deploy Abri Lambda + # ============================================================ + abri_lambda: + needs: [abri_image, determine_stage] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: abri + lambda_path: deployment/terraform/lambda/abri + stage: ${{ needs.determine_stage.outputs.stage }} + ecr_repo: abri-${{ needs.determine_stage.outputs.stage }} + image_digest: ${{ needs.abri_image.outputs.image_digest }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} + TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} + TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} + TF_VAR_hubspot_api_key: ${{ secrets.HUBSPOT_API_KEY }} + # Staging points at Abri's UAT relay endpoint; production values are + # pending from Abri (prod apply is blocked until they exist). + TF_VAR_abri_relay_url: ${{ secrets.ABRI_RELAY_URL }} + TF_VAR_abri_relay_username: ${{ secrets.ABRI_RELAY_USERNAME }} + TF_VAR_abri_relay_password: ${{ secrets.ABRI_RELAY_PASSWORD }} + TF_VAR_abri_relay_default_resource: ${{ secrets.ABRI_RELAY_DEFAULT_RESOURCE }} + # ============================================================ # Build Audit Generator image # ============================================================ @@ -778,7 +821,7 @@ jobs: # Deploy Hubspot ETL Lambda # ============================================================ hubspot_etl_lambda: - needs: [hubspot_etl_image, determine_stage, pashub_to_ara_lambda, magic_plan_lambda] + needs: [hubspot_etl_image, determine_stage, pashub_to_ara_lambda, magic_plan_lambda, abri_lambda] uses: ./.github/workflows/_deploy_lambda.yml with: lambda_name: hubspot-etl-to-ara diff --git a/deployment/terraform/lambda/abri/main.tf b/deployment/terraform/lambda/abri/main.tf new file mode 100644 index 000000000..205e38eac --- /dev/null +++ b/deployment/terraform/lambda/abri/main.tf @@ -0,0 +1,40 @@ +data "aws_secretsmanager_secret_version" "db_credentials" { + secret_id = "${var.stage}/assessment_model/db_credentials" +} + +locals { + db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string) +} + +module "lambda" { + source = "../../modules/lambda_with_sqs" + + name = "abri" + stage = var.stage + + image_uri = local.image_uri + + maximum_concurrency = var.maximum_concurrency + reserved_concurrent_executions = var.reserved_concurrent_executions + batch_size = var.batch_size + + environment = { + STAGE = var.stage + LOG_LEVEL = "info" + + # Abri relay credentials are per-stage: the dev/staging stage points at + # Abri's UAT relay endpoint; production values are pending from Abri. + ABRI_RELAY_URL = var.abri_relay_url + ABRI_RELAY_USERNAME = var.abri_relay_username + ABRI_RELAY_PASSWORD = var.abri_relay_password + ABRI_RELAY_DEFAULT_RESOURCE = var.abri_relay_default_resource + + HUBSPOT_API_KEY = var.hubspot_api_key + + POSTGRES_USERNAME = local.db_credentials.db_assessment_model_username + POSTGRES_PASSWORD = local.db_credentials.db_assessment_model_password + POSTGRES_HOST = var.db_host + POSTGRES_DATABASE = var.db_name + POSTGRES_PORT = var.db_port + } +} diff --git a/deployment/terraform/lambda/abri/outputs.tf b/deployment/terraform/lambda/abri/outputs.tf new file mode 100644 index 000000000..aae33f3a5 --- /dev/null +++ b/deployment/terraform/lambda/abri/outputs.tf @@ -0,0 +1,9 @@ +output "abri_queue_url" { + value = module.lambda.queue_url + description = "URL of the Abri SQS queue" +} + +output "abri_queue_arn" { + value = module.lambda.queue_arn + description = "ARN of the Abri SQS queue" +} diff --git a/deployment/terraform/lambda/abri/provider.tf b/deployment/terraform/lambda/abri/provider.tf new file mode 100644 index 000000000..3ae50128a --- /dev/null +++ b/deployment/terraform/lambda/abri/provider.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } + + backend "s3" { + bucket = "abri-terraform-state" + key = "terraform.tfstate" + region = "eu-west-2" + } + + required_version = ">= 1.2.0" +} diff --git a/deployment/terraform/lambda/abri/variables.tf b/deployment/terraform/lambda/abri/variables.tf new file mode 100644 index 000000000..bef54cc41 --- /dev/null +++ b/deployment/terraform/lambda/abri/variables.tf @@ -0,0 +1,84 @@ +variable "lambda_name" { + type = string + description = "Logical name of the lambda" + default = "abri" +} + +variable "stage" { + description = "Deployment stage (e.g. dev, prod)" + type = string +} + +variable "ecr_repo_url" { + type = string + description = "ECR repository URL (no tag, no digest)" +} + +variable "image_digest" { + type = string + description = "Image digest (sha256:...)" +} + +variable "maximum_concurrency" { + type = number + default = null + description = "Maximum number of concurrent Lambda invocations from SQS (2-1000). null = no limit." +} + +variable "reserved_concurrent_executions" { + type = number + default = 1 +} + +variable "batch_size" { + type = number + default = 1 +} + +locals { + image_uri = "${var.ecr_repo_url}@${var.image_digest}" +} + +output "resolved_image_uri" { + value = local.image_uri +} + +variable "abri_relay_url" { + type = string + sensitive = true +} + +variable "abri_relay_username" { + type = string + sensitive = true +} + +variable "abri_relay_password" { + type = string + sensitive = true +} + +variable "abri_relay_default_resource" { + type = string + sensitive = true +} + +variable "hubspot_api_key" { + type = string + sensitive = true +} + +variable "db_host" { + type = string + sensitive = true +} + +variable "db_name" { + type = string + sensitive = true +} + +variable "db_port" { + type = string + sensitive = true +} diff --git a/deployment/terraform/lambda/hubspot_deal_etl/main.tf b/deployment/terraform/lambda/hubspot_deal_etl/main.tf index ffb5f6f53..76459b9a4 100644 --- a/deployment/terraform/lambda/hubspot_deal_etl/main.tf +++ b/deployment/terraform/lambda/hubspot_deal_etl/main.tf @@ -25,6 +25,15 @@ data "terraform_remote_state" "magic_plan" { } } +data "terraform_remote_state" "abri" { + backend = "s3" + config = { + bucket = "abri-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + data "aws_secretsmanager_secret_version" "db_credentials" { secret_id = "${var.stage}/assessment_model/db_credentials" } @@ -59,6 +68,7 @@ module "hubspot_deal_etl" { PASHUB_TO_ARA_SQS_URL = data.terraform_remote_state.pashub_to_ara.outputs.pashub_to_ara_queue_url MAGICPLAN_SQS_URL = data.terraform_remote_state.magic_plan.outputs.magic_plan_queue_url + ABRI_SQS_URL = data.terraform_remote_state.abri.outputs.abri_queue_url } } @@ -100,4 +110,18 @@ module "hubspot_deal_etl_magicplan_sqs_policy" { resource "aws_iam_role_policy_attachment" "hubspot_deal_etl_magicplan_sqs_send" { role = module.hubspot_deal_etl.role_name policy_arn = module.hubspot_deal_etl_magicplan_sqs_policy.policy_arn +} + +module "hubspot_deal_etl_abri_sqs_policy" { + source = "../../modules/general_iam_policy" + + policy_name = "hubspot-deal-etl-abri-sqs-send-${var.stage}" + policy_description = "Allow HubSpot ETL Lambda to send messages to the Abri queue" + actions = ["sqs:SendMessage"] + resources = [data.terraform_remote_state.abri.outputs.abri_queue_arn] +} + +resource "aws_iam_role_policy_attachment" "hubspot_deal_etl_abri_sqs_send" { + role = module.hubspot_deal_etl.role_name + policy_arn = module.hubspot_deal_etl_abri_sqs_policy.policy_arn } \ No newline at end of file diff --git a/deployment/terraform/shared/main.tf b/deployment/terraform/shared/main.tf index 3ff5bda4b..5cb1a93ac 100644 --- a/deployment/terraform/shared/main.tf +++ b/deployment/terraform/shared/main.tf @@ -894,3 +894,17 @@ output "modelling_e2e_ecr_url" { value = module.modelling_e2e_registry.repository_url } + +################################################ +# Abri OpenHousing – Lambda +################################################ +module "abri_state_bucket" { + source = "../modules/tf_state_bucket" + bucket_name = "abri-terraform-state" +} + +module "abri_registry" { + source = "../modules/container_registry" + name = "abri" + stage = var.stage +}