diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index b709f90a..7b9b3da1 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -61,7 +61,13 @@ jobs: push: true file: Dockerfile.base tags: localhost:5000/osm/osm_base - + - name: Download data from S3 + # only build if the image tag doesn't exist + if: steps.check-image-tag.outputs.found_tag == 1 + run: | + aws s3 cp s3://osm-terraform-storage/dashboard_data/matches.parquet dashboard_data/matches.parquet + + - name: Build and push Docker image # only build if the image tag doesn't exist if: steps.check-image-tag.outputs.found_tag == 1 diff --git a/web/api/main.py b/web/api/main.py index 08b122eb..28d32bc6 100644 --- a/web/api/main.py +++ b/web/api/main.py @@ -5,9 +5,10 @@ import os import motor.motor_asyncio -from fastapi import FastAPI, File, Form, HTTPException, UploadFile +from fastapi import FastAPI, File, Form, HTTPException, UploadFile, status from fastapi.responses import JSONResponse from odmantic import AIOEngine, ObjectId +from pydantic import BaseModel from osm.schemas import Invocation, PayloadError, Quarantine @@ -17,6 +18,33 @@ engine = AIOEngine(client=client, database="osm") +class HealthCheck(BaseModel): + """Response model to validate and return when performing a health check.""" + + status: str = "OK" + + +@app.get( + "/health", + tags=["healthcheck"], + summary="Perform a Health Check", + response_description="Return HTTP Status Code 200 (OK)", + status_code=status.HTTP_200_OK, + response_model=HealthCheck, +) +def get_health() -> HealthCheck: + """ + ## Perform a Health Check + Endpoint to perform a healthcheck on. This endpoint can primarily be used Docker + to ensure a robust container orchestration and management is in place. Other + services which rely on proper functioning of the API service will not deploy if this + endpoint returns any other HTTP status code except 200 (OK). + Returns: + HealthCheck: Returns a JSON response with the health status + """ + return HealthCheck(status="OK") + + @app.put("/upload/", response_model=Invocation) async def upload_invocation(invocation: Invocation): await engine.save(invocation) diff --git a/web/dashboard/Dockerfile b/web/dashboard/Dockerfile index 1163b835..d7e0eaa7 100644 --- a/web/dashboard/Dockerfile +++ b/web/dashboard/Dockerfile @@ -2,4 +2,5 @@ ARG BASE_IMAGE=osm/osm_base FROM ${BASE_IMAGE} COPY web/dashboard/ /app ENV LOCAL_DATA_PATH=/opt/data/matches.parquet +COPY ./dashboard_data/matches.parquet /opt/data/matches.parquet CMD ["python", "app.py"] diff --git a/web/deploy/docker-compose.yaml b/web/deploy/docker-compose.yaml index 077af121..ef55cc06 100644 --- a/web/deploy/docker-compose.yaml +++ b/web/deploy/docker-compose.yaml @@ -4,43 +4,55 @@ services: image: "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/api-shared:${RELEASE_TAG}" pull_policy: always environment: - - MONGODB_URI="${MONGODB_URI}" + MONGODB_URI: ${MONGODB_URI} working_dir: /app/app expose: - "80" labels: - traefik.enable=true - traefik.docker.network=osm_traefik-public - - traefik.http.routers.osm_web_api.rule=Host("`${DEPLOYMENT_URI}`") && PathPrefix(`/api`) + - traefik.http.routers.osm_web_api.rule=Host(`${DEPLOYMENT_URI}`) && PathPrefix(`/api`) - "traefik.http.routers.osm_web_api.entrypoints=web,websecure" + - "traefik.http.routers.osm_web_api.service=osm_web_api" - traefik.http.services.osm_web_api.loadbalancer.server.port=80 - traefik.http.routers.osm_web_api.tls=true - traefik.http.routers.osm_web_api.tls.certresolver=le + - "traefik.http.routers.osm_web_api.priority=100" # Higher priority than dashboard networks: - traefik-public restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/health"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 30s + deploy: + update_config: + order: start-first + failure_action: rollback + delay: 10s dashboard: image: "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/dashboard-shared:${RELEASE_TAG}" pull_policy: always environment: - - MONGODB_URI="${MONGODB_URI}" + MONGODB_URI: ${MONGODB_URI} working_dir: /app labels: - traefik.enable=true - traefik.docker.network=osm_traefik-public - - traefik.http.routers.dashboard.rule=Host("`${DEPLOYMENT_URI}`") + - traefik.http.routers.dashboard.rule=Host(`${DEPLOYMENT_URI}`) - traefik.http.routers.dashboard.entrypoints=web,websecure - traefik.http.services.dashboard.loadbalancer.server.port=8501 - traefik.http.routers.dashboard.tls=true - traefik.http.routers.dashboard.tls.certresolver=le expose: - "8501" - networks: - traefik-public restart: always - + reverse_proxy: image: traefik restart: always diff --git a/web/deploy/terraform/README.md b/web/deploy/terraform/README.md index 5ecabdb7..029ac27d 100644 --- a/web/deploy/terraform/README.md +++ b/web/deploy/terraform/README.md @@ -12,7 +12,7 @@ You must set the following [repository secrets](https://github.com/nimh-dsst/osm * `AWS_ACCOUNT_ID`: The [AWS account ID](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html#FindAccountId) used for the deployments * `AWS_REGION`: The AWS region where the deployments reside -* `MONGODB_URI`: The URI of the MongoDB holding the data. This variable was temperamental. Please try saving it as a secret surrounded by single quotes `'`. +* `MONGODB_URI`: The URI of the MongoDB holding the data. * `SSH_PRIVATE_KEY`: The private SSH key used to ssh into the ec2 instances * `SSH_PUBLIC_KEY`: The public SSH key used to ssh into the ec2 instances corresponding to the above private key (use `ssh-keygen -y -f