Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,28 @@ jobs:
infrastructure:
runs-on: ubuntu-latest
needs: changes
environment: ${{ needs.changes.outputs.tfvars }}
if: needs.changes.outputs.infra == 'true'
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Generate token from GitHub App
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.AC_APP_ID }}
private-key: ${{ secrets.AC_APP_PRIVATE_KEY }}
owner: AccelerationConsortium
repositories: infrastructure-modules

- name: Checkout infrastructure-modules
uses: actions/checkout@v4
with:
repository: AccelerationConsortium/infrastructure-modules
ref: main
path: .modules/infrastructure-modules
token: ${{ secrets.GH_PAT }}
token: ${{ steps.app-token.outputs.token }}

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
Expand Down Expand Up @@ -174,6 +184,7 @@ jobs:
deploy-app:
runs-on: ubuntu-latest
needs: [changes, infrastructure]
environment: ${{ needs.changes.outputs.tfvars }}
if: always() && !failure() && !cancelled() && needs.changes.outputs.app == 'true' && github.event_name == 'push'
steps:
- name: Checkout
Expand Down
6 changes: 4 additions & 2 deletions app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN chmod +x entrypoint.sh

HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 \
CMD pgrep -f "main.py" || exit 1
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

ENTRYPOINT ["./entrypoint.sh"]
2 changes: 1 addition & 1 deletion app/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ else
fi

echo "Starting uploader agent..."
exec python main.py run --config /app/config.yaml
exec uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}
66 changes: 62 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,64 @@
"""Lab Data Uploader Agent — entry point."""
"""Lab Data Uploader Agent — FastAPI entry point with background scheduler."""

from agent.cli import app
from __future__ import annotations

if __name__ == "__main__":
app()
import os
import threading
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI

from agent.config import load_config
from agent.logging_utils import get_logger, setup_logging
from agent.scheduler import UploadScheduler

_scheduler: UploadScheduler | None = None
_scheduler_thread: threading.Thread | None = None


def _run_scheduler(scheduler: UploadScheduler) -> None:
"""Run the upload scheduler loop in a background thread."""
scheduler.run_loop()


@asynccontextmanager
async def lifespan(application: FastAPI): # noqa: ARG001
"""Start scheduler on startup, stop on shutdown."""
global _scheduler, _scheduler_thread

config_path = Path(os.environ.get("AGENT_CONFIG", "/app/config.yaml"))
cfg = load_config(config_path)
setup_logging(cfg.storage.log_dir)
logger = get_logger("main")

roots = [r.path for r in cfg.watch.session_roots]
logger.info(
"agent_startup",
machine_id=cfg.agent.machine_id,
lab_id=cfg.agent.lab_id,
session_roots=roots,
scan_interval=cfg.agent.scan_interval_seconds,
)

_scheduler = UploadScheduler(cfg)
_scheduler_thread = threading.Thread(target=_run_scheduler, args=(_scheduler,), daemon=True)
_scheduler_thread.start()

logger.info("health_server_ready", port=int(os.environ.get("PORT", "8000")))
yield

logger.info("agent_shutdown", reason="lifespan_shutdown")
if _scheduler:
_scheduler.stop()
if _scheduler_thread:
_scheduler_thread.join(timeout=10)


app = FastAPI(title="Lab Data Uploader Agent", lifespan=lifespan)


@app.get("/health")
def health():
"""Health check endpoint for ALB."""
return {"status": "healthy"}
3 changes: 2 additions & 1 deletion app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ tenacity>=8.0,<10
structlog>=24.0,<26
typer>=0.12,<1
boto3>=1.34,<2
psycopg2-binary>=2.9,<3
fastapi>=0.115,<1
uvicorn>=0.32,<1
10 changes: 5 additions & 5 deletions platform/vars/dev.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ app_name = "lab-data-uploader"
environment = "dev"

# Container configuration
container_port = 8080
container_port = 8000
cpu = 512
memory = 1024
desired_count = 1

# Health check
health_check_path = "/health"
health_check_interval = 60
health_check_timeout = 10
health_check_interval = 30
health_check_timeout = 5

# No public access needed
# No public access needed — internal service
enable_cloudfront = false
allow_cloudfront_access = false
enable_waf = false
Expand All @@ -22,7 +22,7 @@ enable_cognito_auth = false
# Environment variables
environment_variables = {
ENVIRONMENT = "dev"
NFS_MOUNTS = "100.x.x.x:/labdata:/mnt/lab1"
NFS_MOUNTS = "100.115.219.51:/labdata:/mnt/lab1"
}

# Secrets (stored in AWS Secrets Manager)
Expand Down
10 changes: 5 additions & 5 deletions platform/vars/prod.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ app_name = "lab-data-uploader"
environment = "prod"

# Container configuration
container_port = 8080
container_port = 8000
cpu = 512
memory = 1024
desired_count = 1

# Health check
health_check_path = "/health"
health_check_interval = 60
health_check_timeout = 10
health_check_interval = 30
health_check_timeout = 5

# No public access needed
# No public access needed — internal service
enable_cloudfront = false
allow_cloudfront_access = false
enable_waf = false
Expand All @@ -22,7 +22,7 @@ enable_cognito_auth = false
# Environment variables
environment_variables = {
ENVIRONMENT = "prod"
NFS_MOUNTS = "100.x.x.x:/labdata:/mnt/lab1"
NFS_MOUNTS = "100.115.219.51:/labdata:/mnt/lab1"
}

# Secrets (stored in AWS Secrets Manager)
Expand Down
10 changes: 5 additions & 5 deletions platform/vars/test.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ app_name = "lab-data-uploader"
environment = "test"

# Container configuration
container_port = 8080
container_port = 8000
cpu = 512
memory = 1024
desired_count = 1

# Health check
health_check_path = "/health"
health_check_interval = 60
health_check_timeout = 10
health_check_interval = 30
health_check_timeout = 5

# No public access needed
# No public access needed — internal service
enable_cloudfront = false
allow_cloudfront_access = false
enable_waf = false
Expand All @@ -22,7 +22,7 @@ enable_cognito_auth = false
# Environment variables
environment_variables = {
ENVIRONMENT = "test"
NFS_MOUNTS = "100.x.x.x:/labdata:/mnt/lab1"
NFS_MOUNTS = "100.115.219.51:/labdata:/mnt/lab1"
}

# Secrets (stored in AWS Secrets Manager)
Expand Down
Loading