From e1f67d6def98d4b8470185d88c76e6227c5767b3 Mon Sep 17 00:00:00 2001 From: christian-calabrese Date: Fri, 29 Nov 2024 13:58:02 +0100 Subject: [PATCH 1/3] [DEV-2044][DEV-2041][DEV-2028] Setup active campaign syncer infra (#1258) * feat: started implementation * feat: active campaign sync infrastructure * chore: added changeset * feat: add active campaign base url ssm parameter * feat: deploy ac sync lambda workflow implemented --- .changeset/silly-geese-shop.md | 5 + .github/workflows/deploy_ac_sync_lambda.yaml | 75 +++++++++++++++ .gitignore | 3 + apps/infrastructure/src/main.tf | 10 ++ .../src/modules/active_campaign/data.tf | 1 + .../modules/active_campaign/eventbridge.tf | 67 +++++++++++++ .../active_campaign/functions/index.js | 4 + .../src/modules/active_campaign/iam.tf | 95 +++++++++++++++++++ .../src/modules/active_campaign/lambda.tf | 63 ++++++++++++ .../src/modules/active_campaign/locals.tf | 5 + .../src/modules/active_campaign/sqs.tf | 63 ++++++++++++ .../src/modules/active_campaign/variables.tf | 45 +++++++++ .../src/modules/website/dynamodb.tf | 3 + .../src/modules/website/outputs.tf | 4 + 14 files changed, 443 insertions(+) create mode 100644 .changeset/silly-geese-shop.md create mode 100644 .github/workflows/deploy_ac_sync_lambda.yaml create mode 100644 apps/infrastructure/src/modules/active_campaign/data.tf create mode 100644 apps/infrastructure/src/modules/active_campaign/eventbridge.tf create mode 100644 apps/infrastructure/src/modules/active_campaign/functions/index.js create mode 100644 apps/infrastructure/src/modules/active_campaign/iam.tf create mode 100644 apps/infrastructure/src/modules/active_campaign/lambda.tf create mode 100644 apps/infrastructure/src/modules/active_campaign/locals.tf create mode 100644 apps/infrastructure/src/modules/active_campaign/sqs.tf create mode 100644 apps/infrastructure/src/modules/active_campaign/variables.tf diff --git a/.changeset/silly-geese-shop.md b/.changeset/silly-geese-shop.md new file mode 100644 index 0000000000..a257302d65 --- /dev/null +++ b/.changeset/silly-geese-shop.md @@ -0,0 +1,5 @@ +--- +"infrastructure": minor +--- + +Implemented active campaign syncer infrastructure diff --git a/.github/workflows/deploy_ac_sync_lambda.yaml b/.github/workflows/deploy_ac_sync_lambda.yaml new file mode 100644 index 0000000000..9c1c3fa779 --- /dev/null +++ b/.github/workflows/deploy_ac_sync_lambda.yaml @@ -0,0 +1,75 @@ +name: Deploy Lambda Active Campaign Syncer + +on: + push: + branches: + - "main" + paths: + - "packages/active-campaign-client/**" + - ".github/workflows/deploy_ac_sync_lambda.yaml" + workflow_dispatch: + inputs: + environment: + description: 'Choose environment' + type: choice + required: true + default: dev + options: + - dev + - prod + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: '20.x' + + - name: Build + working-directory: packages/active-campaign-client + run: npm run build + + - name: Package + working-directory: packages/active-campaign-client/dist + run: zip -r function.zip . + + - name: Archive build artifacts + uses: actions/upload-artifact@v3 + with: + name: active-campaign-client + path: packages/active-campaign-client/dist/function.zip + + deploy: + name: Deploy Lambda Active Campaign Syncer + runs-on: ubuntu-22.04 + needs: build + + environment: ${{ github.event.inputs.environment || 'dev' }} + permissions: + id-token: write + contents: read + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: active-campaign-client + path: ./packages/active-campaign-client/target + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.DEPLOY_IAM_ROLE }} + aws-region: eu-south-1 + + - name: Deploy Lambda function (${{ github.event.inputs.environment || 'dev' }}) + run: | + aws lambda update-function-code \ + --function-name ac-${{ github.event.inputs.environment || 'dev' }}-sync-lambda \ + --zip-file fileb://packages/active-campaign-client/target/function.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5a11476e5f..398b8ada23 100644 --- a/.gitignore +++ b/.gitignore @@ -409,5 +409,8 @@ apps/chatbot/empty_htmls.json /results +# terraform +apps/infrastructure/src/builds + ## Python ## __pycache__/ diff --git a/apps/infrastructure/src/main.tf b/apps/infrastructure/src/main.tf index f14227059b..b1534ee827 100644 --- a/apps/infrastructure/src/main.tf +++ b/apps/infrastructure/src/main.tf @@ -145,3 +145,13 @@ module "cicd" { website_bucket = module.website.website_bucket website_cdn = module.website.website_cdn } + +module "active_campaign" { + source = "./modules/active_campaign" + + environment = var.environment + tags = var.tags + + cognito_user_pool = module.website.cognito_user_pool + webinar_subscriptions_ddb_stream_arn = module.website.webinar_subscriptions_ddb_stream_arn +} \ No newline at end of file diff --git a/apps/infrastructure/src/modules/active_campaign/data.tf b/apps/infrastructure/src/modules/active_campaign/data.tf new file mode 100644 index 0000000000..8fc4b38cc5 --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/data.tf @@ -0,0 +1 @@ +data "aws_caller_identity" "current" {} diff --git a/apps/infrastructure/src/modules/active_campaign/eventbridge.tf b/apps/infrastructure/src/modules/active_campaign/eventbridge.tf new file mode 100644 index 0000000000..c584d96e38 --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/eventbridge.tf @@ -0,0 +1,67 @@ +# EventBridge Pipe for DynamoDB Stream -> SQS FIFO +resource "aws_pipes_pipe" "dynamodb_to_sqs" { + name = "${local.prefix}-webinar-subscriptions-pipe" + source = var.webinar_subscriptions_ddb_stream_arn + target = aws_sqs_queue.fifo_queue.arn + role_arn = aws_iam_role.pipes_role.arn + + log_configuration { + include_execution_data = ["ALL"] + level = "ERROR" + cloudwatch_logs_log_destination { + log_group_arn = aws_cloudwatch_log_group.pipe.arn + } + } + + source_parameters { + dynamodb_stream_parameters { + batch_size = 1 + starting_position = "LATEST" + } + } + + target_parameters { + input_template = <", + "additionalEventData": { + "sub": "<$.dynamodb.Keys.username.S>" + } + }, + "webinarId": "<$.dynamodb.Keys.webinarId.S>" +} +EOF + sqs_queue_parameters { + message_group_id = local.sqs_message_group_id + } + } +} + +resource "aws_cloudwatch_log_group" "pipe" { + name = "${local.prefix}-webinar-subscriptions-pipe" + retention_in_days = 30 +} + +# EventBridge Rule to Capture Cognito Events +resource "aws_cloudwatch_event_rule" "cognito_events" { + name = "${local.prefix}-cognito-events-rule" + event_pattern = jsonencode({ + source = ["aws.cognito-idp"] + detail-type = ["AWS API Call via CloudTrail"], + detail = { + eventSource = ["cognito-idp.amazonaws.com"], + eventName = ["UpdateUserAttributes", "DeleteUser", "ConfirmSignUp"] + } + }) +} + +resource "aws_cloudwatch_event_target" "cognito_events_sqs" { + target_id = aws_sqs_queue.fifo_queue.name + rule = aws_cloudwatch_event_rule.cognito_events.name + arn = aws_sqs_queue.fifo_queue.arn + + sqs_target { + message_group_id = local.sqs_message_group_id + } +} diff --git a/apps/infrastructure/src/modules/active_campaign/functions/index.js b/apps/infrastructure/src/modules/active_campaign/functions/index.js new file mode 100644 index 0000000000..73796e9a95 --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/functions/index.js @@ -0,0 +1,4 @@ +module.exports.handler = async (event, context) => { + console.log("EVENT: \n" + JSON.stringify(event, null, 2)); + return context.logStreamName; +}; diff --git a/apps/infrastructure/src/modules/active_campaign/iam.tf b/apps/infrastructure/src/modules/active_campaign/iam.tf new file mode 100644 index 0000000000..6dc67e8858 --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/iam.tf @@ -0,0 +1,95 @@ +resource "aws_iam_role" "pipes_role" { + name = "${local.prefix}-webinar-subscriptions-pipe-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "pipes.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_policy" "pipes" { + name = "${local.prefix}-webinar-subscriptions-pipe-policy" + description = "Policy to allow webinar subscriptions pipe to deploy website" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams" + ] + Effect = "Allow" + Resource = [ + var.webinar_subscriptions_ddb_stream_arn + ] + }, + { + Action = [ + "sqs:SendMessage" + ] + Effect = "Allow" + Resource = [ + aws_sqs_queue.fifo_queue.arn + ] + }, + { + Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], + Effect = "Allow", + Resource = aws_cloudwatch_log_group.pipe.arn + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "pipes" { + role = aws_iam_role.pipes_role.name + policy_arn = aws_iam_policy.pipes.arn +} + +# Lambda function +resource "aws_iam_policy" "lambda_policy" { + name = "${local.prefix}-sync-lambda-policy" + description = "Lambda policy for accessing SQS and logging" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"], + Effect = "Allow", + Resource = [aws_sqs_queue.fifo_queue.arn, aws_sqs_queue.fifo_dlq_queue.arn] + }, + { + Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], + Effect = "Allow", + Resource = "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:*${local.prefix}*" + }, + { + Action = ["ssm:GetParameter", "ssm:GetParameters"] + Effect = "Allow" + Resource = ["arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/ac/*"] + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_policy_attach" { + role = module.lambda_sync.lambda_role_name + policy_arn = aws_iam_policy.lambda_policy.arn +} + +resource "aws_iam_role_policy_attachment" "lambda_cognito_policy_attach" { + role = module.lambda_sync.lambda_role_name + policy_arn = "arn:aws:iam::aws:policy/AmazonCognitoReadOnly" +} \ No newline at end of file diff --git a/apps/infrastructure/src/modules/active_campaign/lambda.tf b/apps/infrastructure/src/modules/active_campaign/lambda.tf new file mode 100644 index 0000000000..dbcaeb6bbc --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/lambda.tf @@ -0,0 +1,63 @@ +# Lambda Function for SQS FIFO +module "lambda_sync" { + source = "git::github.com/terraform-aws-modules/terraform-aws-lambda.git?ref=9633abb6b6d275d3a28604dbfa755098470420d4" # v6.5.0 + + function_name = "${local.prefix}-sync-lambda" + description = "Lambda function that syncs Active Campaign" + + environment_variables = { + AC_API_KEY_PARAM = module.active_campaign_api_key.ssm_parameter_name + AC_BASE_URL_PARAM = module.active_campaign_base_url.ssm_parameter_name + COGNITO_USER_POOL_ID = var.cognito_user_pool.id + } + + runtime = "nodejs20.x" + architectures = ["x86_64"] + + handler = "index.handler" + source_path = "${path.module}/functions" + ignore_source_code_hash = true + create_current_version_allowed_triggers = false + + timeout = 15 + memory_size = 256 + + event_source_mapping = { + sqs = { + event_source_arn = aws_sqs_queue.fifo_queue.arn + batch_size = 1 + scaling_config = { + maximum_concurrency = 2 + } + } + } + + allowed_triggers = { + sqs = { + principal = "sqs.amazonaws.com" + source_arn = aws_sqs_queue.fifo_queue.arn + } + } + + tags = var.tags +} + +module "active_campaign_api_key" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/ac/api_key" + value = "Substitute with real api key from the console" + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +module "active_campaign_base_url" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/ac/base_url" + value = "Substitute with real api key from the console" + type = "SecureString" + secure_type = true + ignore_value_changes = true +} diff --git a/apps/infrastructure/src/modules/active_campaign/locals.tf b/apps/infrastructure/src/modules/active_campaign/locals.tf new file mode 100644 index 0000000000..4f2f172cbf --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/locals.tf @@ -0,0 +1,5 @@ +locals { + prefix = "${var.module}-${var.environment}" + + sqs_message_group_id = "userEvents" +} \ No newline at end of file diff --git a/apps/infrastructure/src/modules/active_campaign/sqs.tf b/apps/infrastructure/src/modules/active_campaign/sqs.tf new file mode 100644 index 0000000000..e3784d1925 --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/sqs.tf @@ -0,0 +1,63 @@ +# SQS FIFO Queue +resource "aws_sqs_queue" "fifo_queue" { + name = "${local.prefix}-events.fifo" + fifo_queue = true + content_based_deduplication = true + deduplication_scope = "messageGroup" + fifo_throughput_limit = "perMessageGroupId" + + redrive_policy = jsonencode({ + deadLetterTargetArn = aws_sqs_queue.fifo_dlq_queue.arn + maxReceiveCount = 1 + }) + + policy = jsonencode({ + "Version" : "2008-10-17", + "Id" : "__default_policy_ID", + "Statement" : [ + { + "Sid" : "__owner_statement", + "Effect" : "Allow", + "Principal" : { + "AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + }, + "Action" : "SQS:*", + "Resource" : "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${local.prefix}-events.fifo" + }, + { + "Sid" : "AllowFromEventBridge", + "Effect" : "Allow", + "Principal" : { + "Service" : "events.amazonaws.com" + }, + "Action" : "sqs:SendMessage", + "Resource" : "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${local.prefix}-events.fifo", + "Condition" : { + "ArnEquals" : { + "aws:SourceArn" : aws_cloudwatch_event_rule.cognito_events.arn + } + } + }, + { + "Sid" : "AllowFromEventPipes", + "Effect" : "Allow", + "Principal" : { + "Service" : "pipes.amazonaws.com" + }, + "Action" : "sqs:SendMessage", + "Resource" : "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${local.prefix}-events.fifo", + "Condition" : { + "ArnEquals" : { + "aws:SourceArn" : "arn:aws:pipes:${var.aws_region}:${data.aws_caller_identity.current.account_id}:pipe/${local.prefix}-webinar-subscriptions-pipe" + } + } + } + ] + }) +} + +# Dead Letter Queue (DLQ) +resource "aws_sqs_queue" "fifo_dlq_queue" { + name = "${local.prefix}-events-dlq.fifo" + fifo_queue = true +} diff --git a/apps/infrastructure/src/modules/active_campaign/variables.tf b/apps/infrastructure/src/modules/active_campaign/variables.tf new file mode 100644 index 0000000000..5e3aaf7411 --- /dev/null +++ b/apps/infrastructure/src/modules/active_campaign/variables.tf @@ -0,0 +1,45 @@ +variable "aws_region" { + type = string + description = "AWS region to create resources. Default Milan" + default = "eu-south-1" +} + +variable "environment" { + type = string + description = "Environment" +} + +variable "tags" { + type = map(any) + default = { + CreatedBy = "Terraform", + Wbs = "BD110 - PORTALS E TOOLS" + CostCenter = "BD110 - PORTALS E TOOLS" + Owner = "CloudGaaP-AI" + ManagementTeam = "team_cloudgaap_ai" + } +} + +variable "module" { + type = string + description = "Prefix for resources" + default = "ac" +} + +variable "cognito_user_pool" { + type = object({ + id = string + arn = string + domain = string + region = string + client_id = string + endpoint = string + }) + sensitive = true + description = "The cognito user pool used to authenticate api calls" +} + +variable "webinar_subscriptions_ddb_stream_arn" { + type = string + description = "The ARN of the webinar subscriptions ddb stream" +} \ No newline at end of file diff --git a/apps/infrastructure/src/modules/website/dynamodb.tf b/apps/infrastructure/src/modules/website/dynamodb.tf index eaffd28f85..6475dd7447 100644 --- a/apps/infrastructure/src/modules/website/dynamodb.tf +++ b/apps/infrastructure/src/modules/website/dynamodb.tf @@ -36,6 +36,9 @@ module "dynamodb_webinar_subscriptions" { range_key = "webinarId" server_side_encryption_enabled = true + stream_enabled = true + stream_view_type = "KEYS_ONLY" + attributes = [ { name = "username" diff --git a/apps/infrastructure/src/modules/website/outputs.tf b/apps/infrastructure/src/modules/website/outputs.tf index ee73c332d7..a791b5b83a 100644 --- a/apps/infrastructure/src/modules/website/outputs.tf +++ b/apps/infrastructure/src/modules/website/outputs.tf @@ -23,3 +23,7 @@ output "cognito_user_pool" { sensitive = true } + +output "webinar_subscriptions_ddb_stream_arn" { + value = module.dynamodb_webinar_subscriptions.dynamodb_table_stream_arn +} \ No newline at end of file From e623940ed94783118382ab8c3a9b9d743d2ec61b Mon Sep 17 00:00:00 2001 From: christian-calabrese Date: Mon, 2 Dec 2024 12:26:11 +0100 Subject: [PATCH 2/3] [CAI-214] Implement langfuse infrastructure (#1261) * feat: implement langfuse infrastructure * chore: changeset added * fix: reduce aurora acus * chore: removed unused LANGFUSE_TAG env variable to ecs --- .changeset/soft-socks-build.md | 5 + apps/infrastructure/src/main.tf | 1 + .../infrastructure/src/modules/chatbot/acm.tf | 16 +- .../infrastructure/src/modules/chatbot/alb.tf | 178 +++++++++++++++++ .../src/modules/chatbot/cognito_user.tf | 187 ++++++++++++++++++ .../src/modules/chatbot/data.tf | 12 ++ .../infrastructure/src/modules/chatbot/ecr.tf | 2 +- .../infrastructure/src/modules/chatbot/ecs.tf | 178 +++++++++++++++++ .../infrastructure/src/modules/chatbot/iam.tf | 46 +++++ .../src/modules/chatbot/lambda_chatbot.tf | 4 +- .../src/modules/chatbot/locals.tf | 5 + .../src/modules/chatbot/rds_aurora.tf | 76 +++++++ .../src/modules/chatbot/route53.tf | 34 +++- .../task-definitions/langfuse.json.tpl | 111 +++++++++++ .../src/modules/chatbot/variables.tf | 27 ++- .../infrastructure/src/modules/cms/outputs.tf | 13 +- apps/infrastructure/src/variables.tf | 21 ++ 17 files changed, 900 insertions(+), 16 deletions(-) create mode 100644 .changeset/soft-socks-build.md create mode 100644 apps/infrastructure/src/modules/chatbot/alb.tf create mode 100644 apps/infrastructure/src/modules/chatbot/cognito_user.tf create mode 100644 apps/infrastructure/src/modules/chatbot/rds_aurora.tf create mode 100644 apps/infrastructure/src/modules/chatbot/task-definitions/langfuse.json.tpl diff --git a/.changeset/soft-socks-build.md b/.changeset/soft-socks-build.md new file mode 100644 index 0000000000..ee002d02e0 --- /dev/null +++ b/.changeset/soft-socks-build.md @@ -0,0 +1,5 @@ +--- +"infrastructure": minor +--- + +Langfuse infrastructure implemented diff --git a/apps/infrastructure/src/main.tf b/apps/infrastructure/src/main.tf index b1534ee827..b8ffb44dfe 100644 --- a/apps/infrastructure/src/main.tf +++ b/apps/infrastructure/src/main.tf @@ -128,6 +128,7 @@ module "chatbot" { security_groups = module.cms.security_groups dns_domain_name = var.dns_domain_name ecs_redis = var.chatbot_ecs_redis + ecs_monitoring = var.chatbot_ecs_monitoring } module "cicd" { diff --git a/apps/infrastructure/src/modules/chatbot/acm.tf b/apps/infrastructure/src/modules/chatbot/acm.tf index 5b151226c6..35c59fbcb4 100644 --- a/apps/infrastructure/src/modules/chatbot/acm.tf +++ b/apps/infrastructure/src/modules/chatbot/acm.tf @@ -28,4 +28,18 @@ module "ssl_certificate_us_east_1" { wait_for_validation = false # https://github.com/terraform-aws-modules/terraform-aws-acm/blob/8d0b22f1f242a1b36e29b8cb38aaeac9b887500d/README.md?plain=1#L174 validation_method = "DNS" dns_ttl = 3600 -} \ No newline at end of file +} + +module "internal_ssl_certificate" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-acm.git?ref=8d0b22f1f242a1b36e29b8cb38aaeac9b887500d" # v5.0.0 + domain_name = "dummy.${aws_route53_zone.chatbot_internal.name}" + zone_id = var.dns_chatbot_hosted_zone.id + + subject_alternative_names = [ + "*.${aws_route53_zone.chatbot_internal.name}" + ] + + wait_for_validation = false # https://github.com/terraform-aws-modules/terraform-aws-acm/blob/8d0b22f1f242a1b36e29b8cb38aaeac9b887500d/README.md?plain=1#L174 + validation_method = "DNS" + dns_ttl = 3600 +} diff --git a/apps/infrastructure/src/modules/chatbot/alb.tf b/apps/infrastructure/src/modules/chatbot/alb.tf new file mode 100644 index 0000000000..3551003333 --- /dev/null +++ b/apps/infrastructure/src/modules/chatbot/alb.tf @@ -0,0 +1,178 @@ +## Application Load Balancer for Chatbot Monitoring tool +module "monitoring_load_balancer" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-alb.git?ref=3e9c6cbaf4c1d858c3bbee6f086f0c8ef17522ab" # v9.6.0 + + name = "${local.prefix}-monitoring-alb" + vpc_id = var.vpc.id + subnets = var.vpc.public_subnets + security_groups = [aws_security_group.monitoring_lb.id] + internal = false + create_security_group = false + load_balancer_type = "application" + + listeners = { + front_end_http = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } + front_end_https = { + port = 443 + protocol = "HTTPS" + certificate_arn = module.ssl_certificate.acm_certificate_arn + forward = { + target_group_key = "monitoring-target-group" + } + } + } + + target_groups = { + monitoring-target-group = { + name = "monitoring-target-group" + protocol = "HTTP" + port = var.ecs_monitoring.port + target_type = "ip" + vpc_id = var.vpc.id + + health_check = { + healthy_threshold = "3" + interval = "30" + protocol = "HTTP" + matcher = "200" + timeout = "3" + path = "/api/public/ready" + unhealthy_threshold = "2" + } + create_attachment = false + } + } +} + +### AWS Security Group ### +# Traffic to the DB should only come from ECS +# Traffic to the ECS cluster should only come from the ALB + +resource "aws_security_group" "monitoring_lb" { + name = "${local.prefix}-monitoring-lb" + description = "Ingress - Load Balancer" + vpc_id = var.vpc.id + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 80 + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + protocol = "tcp" + from_port = 443 + to_port = 443 + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + # https://registry.terraform.io/providers/hashicorp/aws/5.35.0/docs/resources/security_group#recreating-a-security-group + lifecycle { + create_before_destroy = true + } +} + +## Application Load Balancer for Chatbot Monitoring tool internally +module "internal_monitoring_load_balancer" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-alb.git?ref=3e9c6cbaf4c1d858c3bbee6f086f0c8ef17522ab" # v9.6.0 + + name = "${local.prefix}-int-monitoring-alb" + vpc_id = var.vpc.id + subnets = var.vpc.private_subnets + security_groups = [aws_security_group.monitoring_lb.id] + internal = true + create_security_group = false + load_balancer_type = "application" + + listeners = { + front_end_http = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } + front_end_https = { + port = 443 + protocol = "HTTPS" + certificate_arn = module.internal_ssl_certificate.acm_certificate_arn + forward = { + target_group_key = "internal-monitoring-target-group" + } + } + } + + target_groups = { + internal-monitoring-target-group = { + name = "internal-monitoring-target-group" + protocol = "HTTP" + port = var.ecs_monitoring.port + target_type = "ip" + vpc_id = var.vpc.id + + health_check = { + healthy_threshold = "3" + interval = "30" + protocol = "HTTP" + matcher = "200" + timeout = "3" + path = "/api/public/ready" + unhealthy_threshold = "2" + } + create_attachment = false + } + } +} + +### AWS Security Group ### +# Traffic to Langfuse comes only from the chatbot lambda + +resource "aws_security_group" "internal_monitoring_lb" { + name = "${local.prefix}-internal-monitoring-lb" + description = "Ingress - Load Balancer" + vpc_id = var.vpc.id + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 80 + security_groups = [aws_security_group.lambda.id] + } + + ingress { + protocol = "tcp" + from_port = 443 + to_port = 443 + security_groups = [aws_security_group.lambda.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + # https://registry.terraform.io/providers/hashicorp/aws/5.35.0/docs/resources/security_group#recreating-a-security-group + lifecycle { + create_before_destroy = true + } +} \ No newline at end of file diff --git a/apps/infrastructure/src/modules/chatbot/cognito_user.tf b/apps/infrastructure/src/modules/chatbot/cognito_user.tf new file mode 100644 index 0000000000..797f39629f --- /dev/null +++ b/apps/infrastructure/src/modules/chatbot/cognito_user.tf @@ -0,0 +1,187 @@ +locals { + from_email_address = format("PagoPA DevPortal ", var.dns_domain_name) +} + +resource "aws_cognito_user_pool" "monitoring" { + lifecycle { + prevent_destroy = true + } + + name = "${local.prefix}-monitoring-user-pool" + deletion_protection = "ACTIVE" + + user_pool_add_ons { + advanced_security_mode = "OFF" + } + + username_attributes = ["email"] + auto_verified_attributes = ["email"] + + user_attribute_update_settings { + attributes_require_verification_before_update = ["email"] + } + + account_recovery_setting { + recovery_mechanism { + name = "verified_email" + priority = 1 + } + } + + admin_create_user_config { + # allow user to create via signup page + allow_admin_create_user_only = true + } + + password_policy { + minimum_length = 8 + require_lowercase = true + require_numbers = true + require_uppercase = true + require_symbols = true + temporary_password_validity_days = 7 + } + + # Custom attributes cannot be required. + # Terraform cannot update or delete an attribute. + # Terraform can add a new attribute as update in-place. + # https://github.com/hashicorp/terraform-provider-aws/issues/24844 + schema { + name = "email" + attribute_data_type = "String" + developer_only_attribute = false + mutable = true + required = true + + string_attribute_constraints { + min_length = 1 + # https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.1 + max_length = 512 + } + } + + schema { + name = "given_name" + attribute_data_type = "String" + developer_only_attribute = false + mutable = true + required = true + string_attribute_constraints { + min_length = 1 + max_length = 256 + } + } + + schema { + name = "family_name" + attribute_data_type = "String" + developer_only_attribute = false + mutable = true + required = true + string_attribute_constraints { + min_length = 1 + max_length = 256 + } + } +} + +resource "aws_cognito_user_pool_client" "langfuse" { + name = "${local.prefix}-monitoring-langfuse" + + user_pool_id = aws_cognito_user_pool.monitoring.id + generate_secret = true + prevent_user_existence_errors = "ENABLED" + + callback_urls = (var.environment == "dev" ? + [ + "http://localhost:3001/api/auth/callback/cognito", + "https://${local.monitoring_host}/api/auth/callback/cognito" + ] : + [ + "https://${local.monitoring_host}/api/auth/callback/cognito" + ] + ) + + allowed_oauth_flows_user_pool_client = true + allowed_oauth_flows = ["code"] + allowed_oauth_scopes = ["openid"] + supported_identity_providers = ["COGNITO"] + explicit_auth_flows = ["ALLOW_USER_PASSWORD_AUTH", "ALLOW_REFRESH_TOKEN_AUTH", "ALLOW_USER_SRP_AUTH"] + auth_session_validity = 3 + + refresh_token_validity = 30 + access_token_validity = 1 + id_token_validity = 1 + + token_validity_units { + refresh_token = "days" + access_token = "hours" + id_token = "hours" + } +} + +resource "aws_cognito_user_pool_domain" "monitoring" { + domain = "monitoring" + user_pool_id = aws_cognito_user_pool.monitoring.id +} + +resource "random_password" "master" { + length = 16 + special = true + min_upper = 1 + min_special = 1 + min_lower = 1 + override_special = "!?" +} + +resource "aws_cognito_user" "master" { + user_pool_id = aws_cognito_user_pool.monitoring.id + username = local.langfuse_master_email + password = random_password.master.result + message_action = "SUPPRESS" + attributes = { + email = local.langfuse_master_email + email_verified = true + given_name = "Master" + family_name = "User" + } +} + +module "master_user_password" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/master_user_password" + value = random_password.master.result + type = "SecureString" + secure_type = true +} + +module "user_pool_client_id" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/user_pool_client_id" + value = aws_cognito_user_pool_client.langfuse.id + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +module "user_pool_client_secret" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/user_pool_client_secret" + value = aws_cognito_user_pool_client.langfuse.client_secret + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +module "user_pool_issuer" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/user_pool_issuer" + value = "https://${aws_cognito_user_pool.monitoring.endpoint}" + type = "SecureString" + secure_type = true + ignore_value_changes = true +} diff --git a/apps/infrastructure/src/modules/chatbot/data.tf b/apps/infrastructure/src/modules/chatbot/data.tf index 3e20bd5788..a895dd6a5d 100644 --- a/apps/infrastructure/src/modules/chatbot/data.tf +++ b/apps/infrastructure/src/modules/chatbot/data.tf @@ -130,4 +130,16 @@ data "aws_iam_policy_document" "bedrock_logging" { "${module.bedrock_log_group[0].cloudwatch_log_group_arn}:log-stream:aws/bedrock/modelinvocations" ] } +} + +data "aws_iam_policy_document" "ecs_monitoring_ssm_policy" { + statement { + sid = "AllowSSMOperations" + effect = "Allow" + actions = [ + "ssm:GetParameter", + "ssm:GetParameters" + ] + resources = ["arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/chatbot/monitoring/*"] + } } \ No newline at end of file diff --git a/apps/infrastructure/src/modules/chatbot/ecr.tf b/apps/infrastructure/src/modules/chatbot/ecr.tf index e2958d3e21..d51224b185 100644 --- a/apps/infrastructure/src/modules/chatbot/ecr.tf +++ b/apps/infrastructure/src/modules/chatbot/ecr.tf @@ -1,4 +1,4 @@ -## ECR Container Registry for CMS Strapi +## ECR Container Registry for Chatbot module "ecr" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-ecr.git?ref=9f4b587846551110b0db199ea5599f016570fefe" # v1.6.0 diff --git a/apps/infrastructure/src/modules/chatbot/ecs.tf b/apps/infrastructure/src/modules/chatbot/ecs.tf index 4258568785..399f078740 100644 --- a/apps/infrastructure/src/modules/chatbot/ecs.tf +++ b/apps/infrastructure/src/modules/chatbot/ecs.tf @@ -110,3 +110,181 @@ resource "aws_security_group_rule" "nlb_ingress" { security_group_id = aws_security_group.redis.id source_security_group_id = aws_security_group.nlb.id } + +##### Monitoring (Langfuse) +resource "aws_ecs_task_definition" "monitoring_task_def" { + family = "langfuse-task-def" + execution_role_arn = module.iam_role_ecs_monitoring_task_execution.iam_role_arn + task_role_arn = module.ecs_monitoring_task_iam_role.iam_role_arn + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.ecs_monitoring.cpu + memory = var.ecs_monitoring.memory + container_definitions = templatefile( + "${path.module}/task-definitions/langfuse.json.tpl", + { + image = var.ecs_monitoring.image_uri + fargate_cpu = var.ecs_monitoring.cpu + fargate_memory = var.ecs_monitoring.memory + container_port = var.ecs_monitoring.port + container_name = "langfuse" + aws_region = var.aws_region + log_group = module.ecs_log_group.cloudwatch_log_group_name + + postgres_url = module.postgres_url.ssm_parameter_arn + base_url = "https://${local.monitoring_host}" + client_id = module.user_pool_client_id.ssm_parameter_arn + client_secret = module.user_pool_client_secret.ssm_parameter_arn + issuer = module.user_pool_issuer.ssm_parameter_arn + master_user_email = local.langfuse_master_email + master_user_name = "master" + master_user_password = module.master_user_password.ssm_parameter_arn + encryption_key = module.encryption_key.ssm_parameter_arn + salt = module.salt.ssm_parameter_arn + nextauth_secret = module.nextauth_secret.ssm_parameter_arn + langfuse_public_key = module.langfuse_public_key.ssm_parameter_arn + langfuse_secret_key = module.langfuse_secret_key.ssm_parameter_arn + }) +} + +module "monitoring_ecs_service" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ecs.git//modules/service?ref=8b97783def49997d18a6fcb00dc21ce1edc0f538" # v5.9.0 + + name = "langfuse-ecs" + cluster_arn = module.ecs_cluster.arn + desired_count = 1 + create_task_definition = false + create_iam_role = false + create_task_exec_iam_role = false + create_security_group = false + launch_type = "FARGATE" + force_new_deployment = true + enable_execute_command = true + task_definition_arn = aws_ecs_task_definition.monitoring_task_def.arn + tasks_iam_role_arn = module.ecs_monitoring_task_iam_role.iam_role_arn + task_exec_iam_role_arn = module.iam_role_ecs_task_execution.iam_role_arn + ignore_task_definition_changes = false + + security_group_ids = [aws_security_group.monitoring_ecs.id] + subnet_ids = var.vpc.private_subnets + assign_public_ip = false + + load_balancer = { + monitoring-target-group = { + target_group_arn = module.monitoring_load_balancer.target_groups["monitoring-target-group"].arn + container_name = "langfuse" + container_port = var.ecs_monitoring.port + } + + internal-monitoring-target-group = { + target_group_arn = module.internal_monitoring_load_balancer.target_groups["internal-monitoring-target-group"].arn + container_name = "langfuse" + container_port = var.ecs_monitoring.port + } + } +} + +resource "aws_security_group" "monitoring_ecs" { + name = "${local.prefix}-monitoring-ecs" + description = "Monitoring ECS" + vpc_id = var.vpc.id + + # https://registry.terraform.io/providers/hashicorp/aws/5.35.0/docs/resources/security_group#recreating-a-security-group + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "monitoring_ecs_egress" { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.monitoring_ecs.id +} + +resource "aws_security_group_rule" "alb_ingress" { + type = "ingress" + from_port = var.ecs_monitoring.port + to_port = var.ecs_monitoring.port + protocol = "tcp" + security_group_id = aws_security_group.monitoring_ecs.id + source_security_group_id = aws_security_group.monitoring_lb.id +} + +resource "aws_security_group_rule" "internal_alb_ingress" { + type = "ingress" + from_port = var.ecs_monitoring.port + to_port = var.ecs_monitoring.port + protocol = "tcp" + security_group_id = aws_security_group.monitoring_ecs.id + source_security_group_id = aws_security_group.internal_monitoring_lb.id +} + +resource "random_password" "encryption_key" { + length = 64 # 32 bytes in hexadecimal = 64 characters + special = false # Disable special characters + override_special = "" # Ensure no special characters are added +} + +module "encryption_key" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/encryption_key" + value = random_password.encryption_key.result + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +resource "random_id" "salt" { + byte_length = 32 # Generate 32 random bytes +} + +module "salt" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/salt" + value = base64encode(random_id.salt.b64_std) + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +resource "random_id" "nextauth_secret" { + byte_length = 32 # Generate 32 random bytes +} + +module "nextauth_secret" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/nextauth_secret" + value = base64encode(random_id.nextauth_secret.b64_std) + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +resource "random_uuid" "public_key_uuid" {} +resource "random_uuid" "secret_key_uuid" {} + +module "langfuse_public_key" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/langfuse_public_key" + value = "pk-lf-${random_uuid.public_key_uuid.result}" + type = "SecureString" + secure_type = true + ignore_value_changes = true +} + +module "langfuse_secret_key" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/langfuse_secret_key" + value = "sk-lf-${random_uuid.secret_key_uuid.result}" + type = "SecureString" + secure_type = true + ignore_value_changes = true +} diff --git a/apps/infrastructure/src/modules/chatbot/iam.tf b/apps/infrastructure/src/modules/chatbot/iam.tf index aef7ef2dec..4159ef1692 100644 --- a/apps/infrastructure/src/modules/chatbot/iam.tf +++ b/apps/infrastructure/src/modules/chatbot/iam.tf @@ -90,4 +90,50 @@ module "iam_role_bedrock_logging" { "bedrock.amazonaws.com" ] role_requires_mfa = false +} + +############################################################################### +# IAM Role used by Monitoring ECS # +############################################################################### +module "ecs_monitoring_task_iam_role" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-iam.git//modules/iam-assumable-role?ref=f37809108f86d8fbdf17f735df734bf4abe69315" # v5.34.0 + + create_role = true + role_name = "${local.prefix}-monitoring-ecs-task-role" + + custom_role_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", + module.iam_policy_ecs_task_role_ssm.arn, + ] + number_of_custom_role_policy_arns = 2 + trusted_role_services = [ + "ecs-tasks.amazonaws.com" + ] + role_requires_mfa = false +} + +module "iam_role_ecs_monitoring_task_execution" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-iam.git//modules/iam-assumable-role?ref=f37809108f86d8fbdf17f735df734bf4abe69315" # v5.34.0 + + create_role = true + role_name = "${local.prefix}-ecs-monitoring-task-execution-role" + + custom_role_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", + module.iam_policy_ecs_monitoring_task_role_ssm.arn + ] + number_of_custom_role_policy_arns = 3 + trusted_role_services = [ + "ecs-tasks.amazonaws.com" + ] + role_requires_mfa = false +} + +module "iam_policy_ecs_monitoring_task_role_ssm" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-iam.git//modules/iam-policy?ref=f37809108f86d8fbdf17f735df734bf4abe69315" # v5.34.0 + + name = "ECSMonitoringTaskRolePoliciesSSM" + path = "/" + policy = data.aws_iam_policy_document.ecs_monitoring_ssm_policy.json } \ No newline at end of file diff --git a/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf b/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf index 452bef0838..5a7331ede0 100644 --- a/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf +++ b/apps/infrastructure/src/modules/chatbot/lambda_chatbot.tf @@ -11,7 +11,9 @@ locals { LOG_LEVEL = "INFO" LLAMA_INDEX_CACHE_DIR = "/tmp" NLTK_DATA = "_static/nltk_cache/" - + CHB_LANGFUSE_HOST = "https://${local.priv_monitoring_host}" + CHB_LANGFUSE_PUBLIC_KEY = module.langfuse_public_key.ssm_parameter_name + CHB_LANGFUSE_SECRET_KEY = module.langfuse_secret_key.ssm_parameter_name # Be extremely careful when changing the provider # both the generation and the embedding models would be changed # embeddings size change would break the application and requires reindexing diff --git a/apps/infrastructure/src/modules/chatbot/locals.tf b/apps/infrastructure/src/modules/chatbot/locals.tf index a7d32eac97..1dde3aad92 100644 --- a/apps/infrastructure/src/modules/chatbot/locals.tf +++ b/apps/infrastructure/src/modules/chatbot/locals.tf @@ -5,4 +5,9 @@ locals { redis_container_name = "redis-stack" lambda_timeout = 180 + + monitoring_host = aws_route53_record.monitoring.fqdn + priv_monitoring_host = aws_route53_record.internal_monitoring.fqdn + monitoring_database_name = "monitoring" + langfuse_master_email = "devportal@pagopa.it" } \ No newline at end of file diff --git a/apps/infrastructure/src/modules/chatbot/rds_aurora.tf b/apps/infrastructure/src/modules/chatbot/rds_aurora.tf new file mode 100644 index 0000000000..2319806d35 --- /dev/null +++ b/apps/infrastructure/src/modules/chatbot/rds_aurora.tf @@ -0,0 +1,76 @@ +# RDS Aurora PostgreSQL Serverless for Chatbot Monitoring tool +resource "random_password" "monitoring_database_password" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + + +module "rds" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-rds-aurora.git?ref=7bf5933100eb355b13854232e5d63c62ea7cad35" # v9.0.0 + + name = "${local.prefix}-monitoring-database" + engine = "aurora-postgresql" + engine_mode = "provisioned" + engine_version = "14" + auto_minor_version_upgrade = true + database_name = local.monitoring_database_name + master_username = "postgres" + master_password = random_password.monitoring_database_password.result + vpc_id = var.vpc.id + db_subnet_group_name = var.vpc.database_subnet_group_name + vpc_security_group_ids = [aws_security_group.monitoring_database.id] + apply_immediately = true + skip_final_snapshot = true + manage_master_user_password = false + backup_retention_period = 3 + + serverlessv2_scaling_configuration = { + min_capacity = 0.5 + max_capacity = 1 + } + + instance_class = "db.serverless" + instances = { + one = {} + } +} + +### AWS RDS Security Group ### +# Traffic to the DB should only come from ECS +resource "aws_security_group" "monitoring_database" { + name = "${local.prefix}-monitoring-database-sg" + description = "Ingress - RDS instance" + vpc_id = var.vpc.id + + ingress { + protocol = "tcp" + from_port = 5432 + to_port = 5432 + security_groups = [ + aws_security_group.monitoring_ecs.id + ] + } + + egress { + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["0.0.0.0/0"] + } + + # https://registry.terraform.io/providers/hashicorp/aws/5.35.0/docs/resources/security_group#recreating-a-security-group + lifecycle { + create_before_destroy = true + } +} + +module "postgres_url" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-ssm-parameter.git?ref=77d2c139784197febbc8f8e18a33d23eb4736879" # v1.1.0 + + name = "/chatbot/monitoring/postgres_url" + value = "postgresql://${module.rds.cluster_master_username}:${module.rds.cluster_master_password}@${module.rds.cluster_endpoint}:${module.rds.cluster_port}/${local.monitoring_database_name}" + type = "SecureString" + secure_type = true + ignore_value_changes = true +} diff --git a/apps/infrastructure/src/modules/chatbot/route53.tf b/apps/infrastructure/src/modules/chatbot/route53.tf index a84111200b..616a2b92a9 100644 --- a/apps/infrastructure/src/modules/chatbot/route53.tf +++ b/apps/infrastructure/src/modules/chatbot/route53.tf @@ -8,4 +8,36 @@ resource "aws_route53_record" "apigw" { name = aws_api_gateway_domain_name.domain_name.regional_domain_name zone_id = aws_api_gateway_domain_name.domain_name.regional_zone_id } -} \ No newline at end of file +} + +resource "aws_route53_record" "monitoring" { + name = "mon" + type = "A" + zone_id = var.dns_chatbot_hosted_zone.id + + alias { + name = module.monitoring_load_balancer.dns_name + zone_id = module.monitoring_load_balancer.zone_id + evaluate_target_health = true + } +} + + +resource "aws_route53_zone" "chatbot_internal" { + name = "internal.${var.dns_chatbot_hosted_zone.name}" + vpc { + vpc_id = var.vpc.id + } +} + +resource "aws_route53_record" "internal_monitoring" { + name = "mon" + type = "A" + zone_id = aws_route53_zone.chatbot_internal.zone_id + + alias { + name = module.internal_monitoring_load_balancer.dns_name + zone_id = module.internal_monitoring_load_balancer.zone_id + evaluate_target_health = true + } +} diff --git a/apps/infrastructure/src/modules/chatbot/task-definitions/langfuse.json.tpl b/apps/infrastructure/src/modules/chatbot/task-definitions/langfuse.json.tpl new file mode 100644 index 0000000000..250e381bbf --- /dev/null +++ b/apps/infrastructure/src/modules/chatbot/task-definitions/langfuse.json.tpl @@ -0,0 +1,111 @@ +[ + { + "name": "${container_name}", + "image": "${image}", + "portMappings": [ + { + "containerPort": ${container_port} + } + ], + "linuxParameters": { + "initProcessEnabled": true + }, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "${log_group}", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "${container_name}" + }, + "secretOptions": [] + }, + "environment": [ + { + "name": "NEXTAUTH_URL", + "value": "${base_url}" + }, + { + "name": "AUTH_COGNITO_ALLOW_ACCOUNT_LINKING", + "value": "true" + }, + { + "name": "LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES", + "value": "false" + }, + { + "name": "LANGFUSE_INIT_ORG_ID", + "value": "pagopa" + }, + { + "name": "LANGFUSE_INIT_ORG_NAME", + "value": "PagoPA" + }, + { + "name": "LANGFUSE_INIT_PROJECT_ID", + "value": "cloudgaapai-discovery" + }, + { + "name": "LANGFUSE_INIT_PROJECT_NAME", + "value": "CloudGaapAI - Discovery" + }, + { + "name": "AUTH_DISABLE_USERNAME_PASSWORD", + "value": "true" + }, + { + "name": "AUTH_DISABLE_SIGNUP", + "value": "false" + }, + { + "name": "LANGFUSE_INIT_USER_EMAIL", + "value": "${master_user_email}" + }, + { + "name": "LANGFUSE_INIT_USER_NAME", + "value": "${master_user_name}" + } + ], + "secrets": [ + { + "name": "DATABASE_URL", + "valueFrom": "${postgres_url}" + }, + { + "name" : "AUTH_COGNITO_CLIENT_ID", + "valueFrom" : "${client_id}" + }, + { + "name" : "AUTH_COGNITO_CLIENT_SECRET", + "valueFrom" : "${client_secret}" + }, + { + "name" : "AUTH_COGNITO_ISSUER", + "valueFrom" : "${issuer}" + }, + { + "name" : "LANGFUSE_INIT_USER_PASSWORD", + "valueFrom" : "${master_user_password}" + }, + { + "name": "ENCRYPTION_KEY", + "valueFrom": "${encryption_key}" + }, + { + "name": "SALT", + "valueFrom": "${salt}" + }, + { + "name": "NEXTAUTH_SECRET", + "valueFrom": "${nextauth_secret}" + }, + { + "name": "LANGFUSE_INIT_PROJECT_PUBLIC_KEY", + "valueFrom": "${langfuse_public_key}" + }, + { + "name": "LANGFUSE_INIT_PROJECT_SECRET_KEY", + "valueFrom": "${langfuse_secret_key}" + } + ] + } +] diff --git a/apps/infrastructure/src/modules/chatbot/variables.tf b/apps/infrastructure/src/modules/chatbot/variables.tf index 2c07b97647..3420e987b5 100644 --- a/apps/infrastructure/src/modules/chatbot/variables.tf +++ b/apps/infrastructure/src/modules/chatbot/variables.tf @@ -59,12 +59,13 @@ variable "cognito_user_pool" { variable "vpc" { type = object({ - id = string - cidr_block = string - public_subnets = list(string) - database_subnets = list(string) - private_subnets = list(string) - elasticache_subnets = list(string) + id = string + cidr_block = string + public_subnets = list(string) + database_subnets = list(string) + private_subnets = list(string) + elasticache_subnets = list(string) + database_subnet_group_name = string }) description = "The VPC used to deploy the resources" @@ -106,4 +107,18 @@ variable "api_gateway" { default = { integration_timeout_sec = 60 } +} + +################################################################################ +# ECS - Monitoring +################################################################################ + +variable "ecs_monitoring" { + type = object({ + cpu = number + memory = number + image_uri = string + port = number + }) + description = "Langfuse configuration for the AI chatbot" } \ No newline at end of file diff --git a/apps/infrastructure/src/modules/cms/outputs.tf b/apps/infrastructure/src/modules/cms/outputs.tf index a13df4bb21..ae43046083 100644 --- a/apps/infrastructure/src/modules/cms/outputs.tf +++ b/apps/infrastructure/src/modules/cms/outputs.tf @@ -1,11 +1,12 @@ output "vpc" { value = { - id = module.vpc.vpc_id - cidr_block = module.vpc.vpc_cidr_block - public_subnets = module.vpc.public_subnets - database_subnets = module.vpc.database_subnets - private_subnets = module.vpc.private_subnets - elasticache_subnets = module.vpc.elasticache_subnets + id = module.vpc.vpc_id + cidr_block = module.vpc.vpc_cidr_block + public_subnets = module.vpc.public_subnets + database_subnets = module.vpc.database_subnets + private_subnets = module.vpc.private_subnets + elasticache_subnets = module.vpc.elasticache_subnets + database_subnet_group_name = module.vpc.database_subnet_group_name } } diff --git a/apps/infrastructure/src/variables.tf b/apps/infrastructure/src/variables.tf index d4951495f6..15c03bceb7 100644 --- a/apps/infrastructure/src/variables.tf +++ b/apps/infrastructure/src/variables.tf @@ -118,3 +118,24 @@ variable "chatbot_ecs_redis" { port = 6379 } } + +################################################################################ +# ECS - Chatbot Monitoring +################################################################################ + +variable "chatbot_ecs_monitoring" { + type = object({ + cpu = optional(number, 2048) + memory = optional(number, 4096) + image_uri = optional(string, "ghcr.io/langfuse/langfuse:sha-9375250") + port = optional(number, 3000) + }) + description = "Redis configuration for the AI chatbot" + + default = { + cpu = 2048 + memory = 4096 + image_uri = "ghcr.io/langfuse/langfuse:sha-9375250" + port = 3000 + } +} \ No newline at end of file From 455614999231457e614844eae04b3338539f5873 Mon Sep 17 00:00:00 2001 From: christian-calabrese Date: Mon, 2 Dec 2024 14:35:59 +0100 Subject: [PATCH 3/3] fix: add npm install on deploy ac sync lambda workflow --- .github/workflows/deploy_ac_sync_lambda.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deploy_ac_sync_lambda.yaml b/.github/workflows/deploy_ac_sync_lambda.yaml index 9c1c3fa779..c89085b421 100644 --- a/.github/workflows/deploy_ac_sync_lambda.yaml +++ b/.github/workflows/deploy_ac_sync_lambda.yaml @@ -30,6 +30,10 @@ jobs: uses: actions/setup-node@v3 with: node-version: '20.x' + + - name: Install dependencies + shell: bash + run: npm install - name: Build working-directory: packages/active-campaign-client