Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI

on:
pull_request:
branches: [ "**" ]
push:
branches: [ "feature/**", "fix/**", "chore/**" ]

jobs:
test-and-lint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: .
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: ${{ runner.os }}-pip-

- name: Install deps (root + services)
run: |
pip install -U pip wheel
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
for svc in trading_ai_system/ingestion_service trading_ai_system/inference_service trading_ai_system/control_service; do
if [ -f "$svc/requirements.txt" ]; then pip install -r "$svc/requirements.txt"; fi
done
pip install pytest flake8

- name: Lint
run: flake8 .

- name: Test
run: |
if [ -d tests ]; then pytest -q; else echo "No tests dir"; fi

build-check:
name: Docker build (no push)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service:
- { name: ingestion_service, path: trading_ai_system/ingestion_service }
- { name: inference_service, path: trading_ai_system/inference_service }
- { name: control_service, path: trading_ai_system/control_service }
steps:
- uses: actions/checkout@v4
- name: Build ${{ matrix.service.name }}
run: docker build -t neurobank/${{ matrix.service.name }}:ci ${{ matrix.service.path }}

84 changes: 84 additions & 0 deletions .github/workflows/deploy-prod-ecs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Deploy Prod (ECS)

on:
push:
tags:
- "prod-*"

env:
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
matrix:
service:
- { name: api-gateway, path: . } # Ajusta si API tiene Dockerfile propio en raíz/otra ruta
- { name: ingestion, path: trading_ai_system/ingestion_service }
- { name: inference, path: trading_ai_system/inference_service }
- { name: control, path: trading_ai_system/control_service }
steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }} # IAM Role con trust para GitHub
aws-region: ${{ env.AWS_REGION }}

- name: Login to ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build & Push ${{ matrix.service.name }}
run: |
IMAGE=${{ env.ECR_REGISTRY }}/neurobank/${{ matrix.service.name }}:${{ github.ref_name }}
docker build -t "$IMAGE" ${{ matrix.service.path }}
docker push "$IMAGE"

deploy-ecs:
needs: build-and-push
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
matrix:
svc:
- { ecs_service: api-gateway-svc, taskdef: trading_ai_system/ecs/api-gateway-task.json, container: api-gateway, image_repo: neurobank/api-gateway }
- { ecs_service: ingestion-svc, taskdef: trading_ai_system/ecs/ingestion-service-task.json, container: ingestion, image_repo: neurobank/ingestion }
- { ecs_service: inference-svc, taskdef: trading_ai_system/ecs/inference-service-task.json, container: inference, image_repo: neurobank/inference }
- { ecs_service: control-svc, taskdef: trading_ai_system/ecs/control-service-task.json, container: control, image_repo: neurobank/control }
Comment on lines +68 to +70
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent container naming between task definition files and deployment matrix. Task definitions use 'ingestion-service', 'inference-service', 'control-service' as container names, but the matrix specifies 'ingestion', 'inference', 'control'. This mismatch will cause deployment failures.

Suggested change
- { ecs_service: ingestion-svc, taskdef: trading_ai_system/ecs/ingestion-service-task.json, container: ingestion, image_repo: neurobank/ingestion }
- { ecs_service: inference-svc, taskdef: trading_ai_system/ecs/inference-service-task.json, container: inference, image_repo: neurobank/inference }
- { ecs_service: control-svc, taskdef: trading_ai_system/ecs/control-service-task.json, container: control, image_repo: neurobank/control }
- { ecs_service: ingestion-svc, taskdef: trading_ai_system/ecs/ingestion-service-task.json, container: ingestion-service, image_repo: neurobank/ingestion }
- { ecs_service: inference-svc, taskdef: trading_ai_system/ecs/inference-service-task.json, container: inference-service, image_repo: neurobank/inference }
- { ecs_service: control-svc, taskdef: trading_ai_system/ecs/control-service-task.json, container: control-service, image_repo: neurobank/control }

Copilot uses AI. Check for mistakes.
steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Render Task Definition
id: taskdef
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ matrix.svc.taskdef }}
container-name: ${{ matrix.svc.container }}
image: ${{ env.ECR_REGISTRY }}/${{ matrix.svc.image_repo }}:${{ github.ref_name }}

- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.taskdef.outputs.task-definition }}
service: ${{ matrix.svc.ecs_service }}
cluster: neurobank-prod
wait-for-service-stability: true

42 changes: 42 additions & 0 deletions .github/workflows/deploy-staging-railway.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Deploy Staging (Railway)

on:
push:
branches: [ "main" ]

concurrency:
group: staging-railway
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
steps:
- uses: actions/checkout@v4

- name: Setup Node (Railway CLI)
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install Railway CLI
run: npm i -g @railway/cli

- name: Auth
run: railway login --token "$RAILWAY_TOKEN"

# Si tienes railway.toml en la raíz con todos los servicios:
- name: Deploy API Gateway
run: railway up --service api-gateway --yes

- name: Deploy Ingestion
run: railway up --service ingestion-service --yes

- name: Deploy Inference
run: railway up --service inference-service --yes

- name: Deploy Control
run: railway up --service control-service --yes

40 changes: 40 additions & 0 deletions .github/workflows/ecs-taskdef-api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"family": "api-gateway-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskAppRole",
"containerDefinitions": [
{
"name": "api-gateway",
"image": "<ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/neurobank/api-gateway:latest",
"portMappings": [{ "containerPort": 8000, "protocol": "tcp" }],
"environment": [
{ "name": "ENVIRONMENT", "value": "production" },
{ "name": "LOG_LEVEL", "value": "INFO" },
{ "name": "OTEL_SERVICE_NAME", "value": "api_gateway" }
],
"secrets": [
{ "name": "SECRET_KEY", "valueFrom": "arn:aws:ssm:<AWS_REGION>:<ACCOUNT_ID>:parameter/neurobank/secret_key" },
{ "name": "API_KEY", "valueFrom": "arn:aws:ssm:<AWS_REGION>:<ACCOUNT_ID>:parameter/neurobank/api_key" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/api-gateway",
"awslogs-region": "<AWS_REGION>",
"awslogs-stream-prefix": "ecs"
}
}
},
{
"name": "otel-collector",
"image": "otel/opentelemetry-collector:latest",
"command": ["--config=/etc/otelcol-config.yaml"],
"essential": false
}
]
}

39 changes: 39 additions & 0 deletions .github/workflows/infra-terraform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Infra (Terraform)

on:
workflow_dispatch:
push:
paths:
- "infra/**.tf"

jobs:
terraform:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.5

- name: AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Terraform Init/Plan
working-directory: infra
run: |
terraform init
terraform plan -out=tfplan

- name: Terraform Apply (manual gate)
if: github.event_name == 'workflow_dispatch'
working-directory: infra
run: terraform apply -auto-approve tfplan

13 changes: 13 additions & 0 deletions Makefile.deploy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
AWS_REGION ?= eu-west-1
AWS_ACCOUNT_ID ?= 000000000000
ECR = $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com

login:
aws ecr get-login-password --region $(AWS_REGION) | docker login --username AWS --password-stdin $(ECR)

build-all:
docker build -t $(ECR)/neurobank/api-gateway:local .
docker build -t $(ECR)/neurobank/ingestion:local trading_ai_system/ingestion_service
docker build -t $(ECR)/neurobank/inference:local trading_ai_system/inference_service
docker build -t $(ECR)/neurobank/control:local trading_ai_system/control_service

1 change: 1 addition & 0 deletions NeuroBank-FastAPI-Toolkit
Submodule NeuroBank-FastAPI-Toolkit added at 00fadb
1 change: 1 addition & 0 deletions NeuroBank-FastAPI-Toolkit-1
Submodule NeuroBank-FastAPI-Toolkit-1 added at f0c60e
24 changes: 19 additions & 5 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import json
import sys
from functools import lru_cache
from typing import List, Optional
Expand All @@ -17,15 +18,19 @@ class Settings(BaseSettings):
# Server Configuration
host: str = "0.0.0.0"
port: int = int(os.getenv("PORT", 8000))
workers: int = int(os.getenv("WORKERS", 1))

# Security / Secrets
secret_key: Optional[str] = os.getenv("SECRET_KEY")

# Environment Configuration
environment: str = os.getenv(
"ENVIRONMENT", "development"
) # Default to development, not production
debug: bool = os.getenv("DEBUG", "false").lower() == "true"

# CORS Configuration - usando el dominio privado de Railway
cors_origins: List[str] = []
# CORS Configuration: definir como string para evitar parseo JSON automático
cors_origins: Optional[str] = os.getenv("CORS_ORIGINS")

# AWS Configuration
aws_region: str = os.getenv("AWS_REGION", "eu-west-1")
Expand All @@ -46,9 +51,18 @@ class Settings(BaseSettings):

def _get_cors_origins(self) -> List[str]:
"""Configura CORS origins usando variables de Railway"""
# Si hay CORS_ORIGINS configurado manualmente, usarlo
if os.getenv("CORS_ORIGINS"):
return os.getenv("CORS_ORIGINS").split(",")
# Si hay CORS_ORIGINS configurado manualmente, usarlo con parseo robusto
raw = self.cors_origins
if raw is not None:
raw_str = raw.strip()
if raw_str == "":
return []
try:
parsed = json.loads(raw_str)
if isinstance(parsed, list):
return [str(x).strip() for x in parsed if str(x).strip()]
except Exception:
return [part.strip() for part in raw_str.split(",") if part.strip()]

# Si no, construir automáticamente desde Railway
origins = ["https://*.railway.app"]
Expand Down
Loading
Loading