зеркало из https://github.com/microsoft/AzureTRE.git
Implement Health Endpoint (#89)
* fix linting * update documentation * fix docker build * fix linting * fix linting * fix docker workflow * update app gateway path_rule to reflect that api is now at /api * add probe per marrobis request * fix markdown * save files before line endings * Normalize line endings * update code project structure * fix spelling nits * fix per sachins comments Co-authored-by: Tess Ferrandez <tess.ferrandez@microsoft.com>
This commit is contained in:
Родитель
6b8ffc8a9e
Коммит
0fac2eddab
2
.flake8
2
.flake8
|
@ -0,0 +1,2 @@
|
|||
[flake8]
|
||||
ignore = E501
|
|
@ -0,0 +1,39 @@
|
|||
# These settings are for any web project
|
||||
|
||||
# Handle line endings automatically for files detected as text
|
||||
# and leave all files detected as binary untouched.
|
||||
* text=auto
|
||||
|
||||
# Force the following filetypes to have unix eols, so Windows does not break them
|
||||
*.* text eol=lf
|
||||
|
||||
# Windows forced line-endings
|
||||
/.idea/* text eol=crlf
|
||||
|
||||
#
|
||||
## These files are binary and should be left untouched
|
||||
#
|
||||
|
||||
# (binary is a macro for -text -diff)
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.flv binary
|
||||
*.fla binary
|
||||
*.swf binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.pdf binary
|
||||
*.ez binary
|
||||
*.bz2 binary
|
||||
*.swp binary
|
|
@ -0,0 +1,2 @@
|
|||
[flake8]
|
||||
ignore = E501
|
|
@ -3,11 +3,11 @@ name: Docker build and push
|
|||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- core/api/**
|
||||
- management_api_app/**
|
||||
push:
|
||||
branches: [ develop, main ]
|
||||
paths:
|
||||
- core/api/**
|
||||
- management_api_app/**
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
@ -39,5 +39,5 @@ jobs:
|
|||
TAG=$GITHUB_SHA
|
||||
fi
|
||||
|
||||
docker build -t ${{ env.REPOSITORY_NAME}}:$TAG core/api/
|
||||
docker build -t ${{ env.REPOSITORY_NAME}}:$TAG core/
|
||||
docker push ${{ env.REPOSITORY_NAME}}:$TAG
|
|
@ -1,32 +0,0 @@
|
|||
name: Deploy API to Azure App Service
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: Dev
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Azure Login
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: Deploy Web App
|
||||
uses: azure/CLI@v1
|
||||
with:
|
||||
azcliversion: 2.20.0
|
||||
inlineScript: |
|
||||
cd core/api
|
||||
az webapp up --sku S1 -n TREAPIService -g TRERG --location westeurope
|
||||
|
||||
- name: Set startup command for Web App
|
||||
uses: azure/CLI@v1
|
||||
with:
|
||||
azcliversion: 2.20.0
|
||||
inlineScript: |
|
||||
az webapp config set -n TREAPIService -g TRERG --startup-file='gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app'
|
|
@ -104,7 +104,7 @@ celerybeat.pid
|
|||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
**/.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"**/*devcontainer*",
|
||||
".git",
|
||||
".gitignore",
|
||||
".gitattributes",
|
||||
"node_modules",
|
||||
"LICENSE",
|
||||
"**/.dockerignore",
|
||||
|
@ -53,7 +54,11 @@
|
|||
"tfvars",
|
||||
"tmpl",
|
||||
"TREs",
|
||||
"Xamarin"
|
||||
"Xamarin",
|
||||
"devcontainer",
|
||||
"DevContainer",
|
||||
"endpoints",
|
||||
"pytest"
|
||||
],
|
||||
"ignoreText": [
|
||||
// Code sections (anything between ``` and ``` or ` and `)
|
||||
|
|
30
SUPPORT.md
30
SUPPORT.md
|
@ -1,15 +1,15 @@
|
|||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
||||
This project has been created to accelerate the development of Trusted Research Environments by Microsoft and its partners while working with customers.
|
||||
|
||||
This project does not have a dedicated team of maintainers outside of those who are working on an active engagements and as such issues will be responded to on a best efforts basis. No guarantees can be offered as to response times on issues, feature requests, or to the long term road map for the project.
|
||||
|
||||
Taking the above statement into account the team involved in this effort wish for the project to evolve into a stable, production ready resource with active contributors from Microsoft and the wider community.
|
||||
|
||||
This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue.
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
Support for this project is limited to the resources listed above.
|
||||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
||||
This project has been created to accelerate the development of Trusted Research Environments by Microsoft and its partners while working with customers.
|
||||
|
||||
This project does not have a dedicated team of maintainers outside of those who are working on an active engagements and as such issues will be responded to on a best efforts basis. No guarantees can be offered as to response times on issues, feature requests, or to the long term road map for the project.
|
||||
|
||||
Taking the above statement into account the team involved in this effort wish for the project to evolve into a stable, production ready resource with active contributors from Microsoft and the wider community.
|
||||
|
||||
This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue.
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
Support for this project is limited to the resources listed above.
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
# Core Applications
|
||||
|
||||
## TRE API
|
||||
|
||||
### Endpoints
|
||||
|
||||
#### /ping
|
||||
|
||||
- Check if API is alive
|
||||
|
||||
### Run locally
|
||||
|
||||
Install the requirements on the dev machine
|
||||
|
||||
```cmd
|
||||
virtualenv venv
|
||||
source venv/bin/activate # linux
|
||||
venv/Scripts/activate # windows
|
||||
pip install -r core/api/requirements.txt
|
||||
```
|
||||
|
||||
Run the web server locally
|
||||
|
||||
```cmd
|
||||
cd core
|
||||
uvicorn api.main:app
|
||||
```
|
||||
|
||||
This will start the API on [http://127.0.0.1:8000](http://127.0.0.1:8000) and you can interact with the swagger on [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)
|
||||
|
||||
Run the API Tests locally
|
||||
|
||||
```cmd
|
||||
pytest
|
||||
```
|
||||
|
||||
### Build and run in docker
|
||||
|
||||
From the root of the repository, build the container image:
|
||||
|
||||
```cmd
|
||||
cd core/api
|
||||
docker build -t tre-management-api .
|
||||
```
|
||||
|
||||
To run:
|
||||
```cmd
|
||||
docker run -it -p 8000:8000 tre-management-api
|
||||
```
|
||||
|
||||
This will start the API on [http://127.0.0.1:8000](http://127.0.0.1:8000) and you can interact with the swagger on [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)
|
||||
|
||||
### Deploy manually to Azure App Service
|
||||
|
||||
- Install [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
|
||||
- Login to Azure with your credentials `az login`
|
||||
- Change the directory to api `cd api`
|
||||
- Deploy to Azure App Service `az webapp up --sku S1 -n MyUniqueAppName -g MyResourceGroup -l westeurope` This will create a Resource Group, App Service Plan and App Service if they do not exist. If App Service exists, it will deploy the solution to it.
|
||||
- Configure App Service startup command `az webapp config set --startup-file='gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app'`
|
||||
|
||||
### Setup CI/CD deployment to Azure App Service
|
||||
|
||||
- Install [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
|
||||
- Login to Azure with your credentials `az login`
|
||||
- Create a resource group that you want to automatically deploy the solution to `az group create -g MyResourceGroup -l westeurope`
|
||||
- Create a service credential to run the pipeline with `az ad sp create-for-rbac --name MySPNName --role Contributor --scope /subscriptions/{MySubscriptionId}/resourceGroups/{MyResourceGroup} --sdk-auth`
|
||||
- In your repository, use Add secret to create a new secret named AZURE_CREDENTIALS and paste the entire JSON object produced by the `az ad sp create-for-rbac` command as the secret value and save the secret.
|
|
@ -1,11 +0,0 @@
|
|||
from fastapi import FastAPI
|
||||
from routers import ping
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(ping.router)
|
||||
|
||||
|
||||
@app.get('/')
|
||||
async def home():
|
||||
return "Welcome to the TRE API"
|
|
@ -1,4 +0,0 @@
|
|||
Click==7.1.2
|
||||
fastapi[all]==0.63.0
|
||||
uvicorn[standard]==0.13.4
|
||||
gunicorn==20.1.0
|
|
@ -1,9 +0,0 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
|
||||
router = APIRouter(prefix="/ping", tags=["ping"])
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def ping():
|
||||
return "pong"
|
|
@ -1,6 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_DIR = os.path.abspath(os.path.join(TEST_DIR, os.pardir, 'api'))
|
||||
sys.path.insert(0, PROJECT_DIR)
|
|
@ -1,11 +0,0 @@
|
|||
from fastapi.testclient import TestClient
|
||||
from api.main import app
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_ping():
|
||||
response = client.get("/ping")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == "pong"
|
|
@ -0,0 +1,99 @@
|
|||
# Developer Setup
|
||||
|
||||
- [Develop and run locally on Windows](#develop-and-run-locally-on-windows)
|
||||
- [Develop and run in a DevContainer](#develop-and-run-in-a-devcontainer)
|
||||
- [Deploy with docker](#deploy-with-docker)
|
||||
- [Run tests](#run-tests)
|
||||
- [(Optional) Install pre-commit hooks](#optional-install-pre-commit-hooks)
|
||||
- [API Endpoints](#api-endpoints)
|
||||
- [Project Structure](#project-structure)
|
||||
|
||||
## Develop and run locally on Windows
|
||||
|
||||
1. [Install the Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=cli%2Cssl-netstd21)
|
||||
1. Install python dependencies (in a virtual environment)
|
||||
|
||||
```cmd
|
||||
virtualenv venv
|
||||
venv/Scripts/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
1. Copy `.env.tmpl` in the **core** folder to `.env` and configure the variables
|
||||
1. Start the web API
|
||||
|
||||
```cmd
|
||||
cd management_api_app
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
The API will be available at [https://localhost:8000/api](https://localhost:8000/api) in your browser.
|
||||
|
||||
## Develop and run in a DevContainer
|
||||
|
||||
1. [Create a Cosmos DB Database in Azure](https://docs.microsoft.com/en-us/azure/cosmos-db/create-cosmosdb-resources-portal)
|
||||
1. Open the project in Visual Studio Code in the DevContainer
|
||||
1. Copy `.env.tmpl` in the **core** folder to `.env` and configure the variables
|
||||
1. Start the web API
|
||||
|
||||
```cmd
|
||||
cd core
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
The API will be available at [https://localhost:8000/api](https://localhost:8000/api) in your browser.
|
||||
|
||||
## Deploy with docker
|
||||
|
||||
You must have docker and docker-compose tools installed, and an Azure Cosmos DB configured in `.env` as described above.
|
||||
|
||||
Then run:
|
||||
|
||||
```cmd
|
||||
cd core
|
||||
docker-compose up -d app
|
||||
```
|
||||
|
||||
The API will be available at [https://localhost:8000/api](https://localhost:8000/api) in your browser.
|
||||
|
||||
## Run tests
|
||||
|
||||
Tests are written with pytest and located in the `tests` folder
|
||||
|
||||
Run all tests with:
|
||||
|
||||
```cmd
|
||||
pytest
|
||||
```
|
||||
|
||||
## (Optional) Install pre-commit hooks
|
||||
|
||||
Pre commit hooks help you lint your python code on each git commit, to avoid having to fail the build when submitting a PR. Installing pre-commit hooks is completely optional.
|
||||
|
||||
```cmd
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
API endpoints documentation and swagger are available at [https://localhost:8000/docs](https://localhost:8000/docs)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```text
|
||||
management_api_app
|
||||
├── api - web related stuff.
|
||||
│ ├── dependencies - dependencies for routes definition.
|
||||
│ ├── errors - definition of error handlers.
|
||||
│ └── routes - web routes.
|
||||
├── core - application configuration, startup events, logging.
|
||||
├── db - db related stuff.
|
||||
│ ├── migrations - manually written alembic migrations.
|
||||
│ └── repositories - all crud stuff.
|
||||
├── models - pydantic models for this application.
|
||||
│ ├── domain - main models that are used almost everywhere.
|
||||
│ └── schemas - schemas for using in web routes.
|
||||
├── resources - strings that are used in web responses.
|
||||
├── services - logic that is not just crud related.
|
||||
└── main.py - FastAPI application creation and configuration.
|
||||
```
|
|
@ -1,17 +0,0 @@
|
|||
# Developer Setup
|
||||
|
||||
## Install pre-commit hooks for python
|
||||
|
||||
Pre commit hooks help you lint your python code on each git commit, to avoid having to fail the build when submitting a PR. Installing pre-commit hooks is completely optional.
|
||||
|
||||
1. Install flake8, pre-commit etc.
|
||||
|
||||
```cmd
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. Enable pre-commit hooks
|
||||
|
||||
```cmd
|
||||
pre-commit install
|
||||
```
|
|
@ -4,26 +4,28 @@ The Azure TRE uses Terraform infrastructure as code templates that pull down Doc
|
|||
|
||||
The most straightforward way to get up and running is to deploy direct from the `microsoft/AzureTRE` repository. Production deployments should take advantage of your chosen DevOps CD tooling.
|
||||
|
||||
## Pre-requisites
|
||||
## Prerequisites
|
||||
|
||||
You will require the following prerequisites installed. They will already be present if using GitHub Codespaces, or use our Dev Container in VS Code.
|
||||
|
||||
You will require the following pre requisites installed. They will already be present if using GitHub Codespaces, or use our Dev Container in VS Code.
|
||||
- Terraform >= v0.15.3. The following instructions use local terraform state, you may want to consider [storing you state remotely in Azure](https://docs.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage)
|
||||
- Azure CLI >= 2.21.0
|
||||
- Docker
|
||||
|
||||
You will also need:
|
||||
|
||||
- An Azure Subscription
|
||||
- GitHub user id and [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with scope `packages:read`. This token is used to pull the web app Docker images. This can be any GitHub account, and does not need to be part of the Microsoft GitHub organisation.
|
||||
- A GitHub user id and [personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with scope `packages:read`. This token is used to pull the web app Docker images. This can be any GitHub account, and does not need to be part of the Microsoft GitHub organisation.
|
||||
|
||||
## Clone the repository
|
||||
|
||||
Clone the repository to your local machine ( `git clone https://github.com/microsoft/AzureTRE.git` ) or you may choose to use our pre-configured dev container via GitHub Codespaces.
|
||||
Clone the repository to your local machine ( `git clone https://github.com/microsoft/AzureTRE.git` ), or you may choose to use our pre-configured dev container via GitHub Codespaces.
|
||||
|
||||
![Clone Options](../docs/assets/clone_options.png)
|
||||
|
||||
## Management Infrastructure
|
||||
|
||||
In the following steps we will create management infrastructure in your subscription. This includes resources, such as a storage accoutn and container registry that will enable deployment the Azure TRE. Once the infrastructure is deployed we will build the container images required for deployment.
|
||||
In the following steps we will create management infrastructure in your subscription. This includes resources, such as a storage account and container registry that will enable deployment the Azure TRE. Once the infrastructure is deployed we will build the container images required for deployment.
|
||||
|
||||
### Log into your chosen Azure subscription
|
||||
Login and select the azure subscription you wish to deploy to:
|
||||
|
@ -49,11 +51,11 @@ Copy [/devops/terraform/.env.sample](../devops/terraform/.env.sample) to `/devop
|
|||
- `TF_VAR_location` - Azure region to deploy all resources into.
|
||||
- `TF_VAR_image_tag` - Default tag for docker images that will be pushed to the container registry and deployed with the Azure TRE
|
||||
|
||||
### Bootstrap of backend state
|
||||
### Bootstrap of back-end state
|
||||
|
||||
As a principle we want all our resources defined in Terraform, including the storage account used by Terraform to hold backend state. This results in a chicken and egg problem.
|
||||
As a principle we want all our resources defined in Terraform, including the storage account used by Terraform to hold back-end state. This results in a chicken and egg problem.
|
||||
|
||||
To solve this a bootstrap script is used which creates the initial storage account and resource group using the Azure CLI. Then Terraform is initialized using this storage account as a backend, and the storage account imported into state
|
||||
To solve this a bootstrap script is used which creates the initial storage account and resource group using the Azure CLI. Then Terraform is initialized using this storage account as a back-end, and the storage account imported into state
|
||||
|
||||
- From bash run `make bootstrap`
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# Api configuration
|
||||
# ------------------------
|
||||
# PROJECT_NAME=""
|
||||
# DEBUG=False
|
||||
|
||||
# State store configuration
|
||||
# ------------------------
|
||||
STATE_STORE_ENDPOINT=https://localhost:8081
|
||||
STATE_STORE_KEY=
|
|
@ -3,7 +3,7 @@ FROM python:3.8
|
|||
COPY requirements.txt .
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY . /api
|
||||
COPY .. /api
|
||||
|
||||
WORKDIR /api
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
from fastapi import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
async def http_error_handler(_: Request, exc: HTTPException) -> JSONResponse:
|
||||
return JSONResponse({"errors": [exc.detail]}, status_code=exc.status_code)
|
|
@ -0,0 +1,25 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.openapi.constants import REF_PREFIX
|
||||
from fastapi.openapi.utils import validation_error_response_definition
|
||||
from pydantic import ValidationError
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
async def http422_error_handler(_: Request, exc: Union[RequestValidationError, ValidationError]) -> JSONResponse:
|
||||
return JSONResponse(
|
||||
{"errors": exc.errors()},
|
||||
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
)
|
||||
|
||||
|
||||
validation_error_response_definition["properties"] = {
|
||||
"errors": {
|
||||
"title": "Errors",
|
||||
"type": "array",
|
||||
"items": {"$ref": "{0}ValidationError".format(REF_PREFIX)},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
from api.routes import ping, health
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(ping.router, tags=["ping"])
|
||||
router.include_router(health.router, tags=["health"])
|
|
@ -0,0 +1,14 @@
|
|||
from fastapi import APIRouter
|
||||
from models.schemas.health import HealthCheck, ServiceStatus
|
||||
from resources import strings
|
||||
from services.health_checker import create_state_store_status
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health", name="health:get-status-of-services")
|
||||
async def health_check() -> HealthCheck:
|
||||
status, message = create_state_store_status()
|
||||
services = [ServiceStatus(service=strings.COSMOS_DB, status=status, message=message)]
|
||||
return HealthCheck(services=services)
|
|
@ -0,0 +1,12 @@
|
|||
from datetime import datetime
|
||||
from fastapi import APIRouter
|
||||
from models.schemas.ping import Pong
|
||||
from resources import strings
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/ping", name="ping:get-server-alive")
|
||||
async def ping_server() -> Pong:
|
||||
return Pong(message=strings.PONG, time=datetime.now())
|
|
@ -0,0 +1,14 @@
|
|||
from starlette.config import Config
|
||||
|
||||
|
||||
config = Config(".env")
|
||||
|
||||
# api settings
|
||||
API_PREFIX = "/api"
|
||||
PROJECT_NAME: str = config("PROJECT_NAME", default="Azure TRE API")
|
||||
DEBUG: bool = config("DEBUG", cast=bool, default=False)
|
||||
VERSION = "0.0.0"
|
||||
|
||||
# State store configuration
|
||||
STATE_STORE_ENDPOINT: str = config("STATE_STORE_ENDPOINT", default="") # cosmos db endpoint
|
||||
STATE_STORE_KEY: str = config("STATE_STORE_KEY", default="") # cosmos db access key
|
|
@ -0,0 +1,16 @@
|
|||
from typing import Callable
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
def create_start_app_handler(app: FastAPI) -> Callable:
|
||||
async def start_app() -> None:
|
||||
pass
|
||||
|
||||
return start_app
|
||||
|
||||
|
||||
def create_stop_app_handler(app: FastAPI) -> Callable:
|
||||
async def stop_app() -> None:
|
||||
pass
|
||||
|
||||
return stop_app
|
|
@ -0,0 +1,10 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: ""
|
||||
restart: on-failure
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
- .env
|
|
@ -0,0 +1,25 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from api.errors.http_error import http_error_handler
|
||||
from api.errors.validation_error import http422_error_handler
|
||||
from api.routes.api import router as api_router
|
||||
from core.config import API_PREFIX, DEBUG, PROJECT_NAME, VERSION
|
||||
from core.events import create_start_app_handler, create_stop_app_handler
|
||||
|
||||
|
||||
def get_application() -> FastAPI:
|
||||
application = FastAPI(title=PROJECT_NAME, debug=DEBUG, version=VERSION)
|
||||
|
||||
application.add_event_handler("startup", create_start_app_handler(application))
|
||||
application.add_event_handler("shutdown", create_stop_app_handler(application))
|
||||
|
||||
application.add_exception_handler(HTTPException, http_error_handler)
|
||||
application.add_exception_handler(RequestValidationError, http422_error_handler)
|
||||
|
||||
application.include_router(api_router, prefix=API_PREFIX)
|
||||
return application
|
||||
|
||||
|
||||
app = get_application()
|
|
@ -0,0 +1,19 @@
|
|||
from enum import Enum
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from resources import strings
|
||||
|
||||
|
||||
class StatusEnum(str, Enum):
|
||||
ok = strings.OK
|
||||
not_ok = strings.NOT_OK
|
||||
|
||||
|
||||
class ServiceStatus(BaseModel):
|
||||
service: str = ""
|
||||
status: StatusEnum = StatusEnum.ok
|
||||
message: str = ""
|
||||
|
||||
|
||||
class HealthCheck(BaseModel):
|
||||
services: List[ServiceStatus]
|
|
@ -0,0 +1,7 @@
|
|||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Pong(BaseModel):
|
||||
message: str
|
||||
time: datetime
|
|
@ -0,0 +1,7 @@
|
|||
# api
|
||||
Click==7.1.2
|
||||
fastapi[all]==0.63.0
|
||||
uvicorn[standard]==0.13.4
|
||||
gunicorn==20.1.0
|
||||
azure-core==1.13.0
|
||||
azure-cosmos==4.2.0
|
|
@ -0,0 +1,7 @@
|
|||
OK = "OK"
|
||||
NOT_OK = "Not OK"
|
||||
PONG = "pong"
|
||||
COSMOS_DB = "Cosmos DB"
|
||||
|
||||
STATE_STORE_ENDPOINT_NOT_RESPONDING = "Server is not responding"
|
||||
UNSPECIFIED_ERROR = "Unspecified error"
|
|
@ -0,0 +1,19 @@
|
|||
from azure.core import exceptions
|
||||
from azure.cosmos import CosmosClient
|
||||
from core.config import STATE_STORE_ENDPOINT, STATE_STORE_KEY
|
||||
from models.schemas.health import StatusEnum
|
||||
from resources import strings
|
||||
|
||||
|
||||
def create_state_store_status() -> (StatusEnum, str):
|
||||
status = StatusEnum.ok
|
||||
message = ""
|
||||
try:
|
||||
client = CosmosClient(STATE_STORE_ENDPOINT, STATE_STORE_KEY) # noqa: F841 - flake 8 client is not used
|
||||
except exceptions.ServiceRequestError:
|
||||
status = StatusEnum.not_ok
|
||||
message = strings.STATE_STORE_ENDPOINT_NOT_RESPONDING
|
||||
except: # noqa: E722 flake8 - no bare excepts
|
||||
status = StatusEnum.not_ok
|
||||
message = strings.UNSPECIFIED_ERROR
|
||||
return status, message
|
|
@ -0,0 +1,6 @@
|
|||
# import os
|
||||
# import sys
|
||||
|
||||
# TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
# PROJECT_DIR = os.path.abspath(os.path.join(TEST_DIR, os.pardir, 'api'))
|
||||
# sys.path.insert(0, PROJECT_DIR)
|
|
@ -0,0 +1,22 @@
|
|||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient
|
||||
from asgi_lifespan import LifespanManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app() -> FastAPI:
|
||||
from main import get_application
|
||||
return get_application()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def initialized_app(app: FastAPI) -> FastAPI:
|
||||
async with LifespanManager(app):
|
||||
yield app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(initialized_app: FastAPI) -> AsyncClient:
|
||||
async with AsyncClient(app=initialized_app, base_url="http://testserver", headers={"Content-Type": "application/json"}) as client:
|
||||
yield client
|
|
@ -0,0 +1,20 @@
|
|||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_frw_validation_error_format(app: FastAPI):
|
||||
@app.get("/wrong_path/{param}")
|
||||
def route_for_test(param: int) -> None: # pragma: no cover
|
||||
pass
|
||||
|
||||
async with AsyncClient(base_url="http://testserver", app=app) as client:
|
||||
response = await client.get("/wrong_path/asd")
|
||||
|
||||
assert response.status_code == HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
assert "errors" in response.json()
|
|
@ -0,0 +1,15 @@
|
|||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient
|
||||
from starlette.status import HTTP_404_NOT_FOUND
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_frw_validation_error_format(app: FastAPI):
|
||||
async with AsyncClient(base_url="http://testserver", app=app) as client:
|
||||
response = await client.get("/wrong_path/asd")
|
||||
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
|
||||
assert "errors" in response.json()
|
|
@ -0,0 +1,19 @@
|
|||
import pytest
|
||||
from mock import patch
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient
|
||||
from resources import strings
|
||||
from models.schemas.health import StatusEnum
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@patch("api.routes.health.create_state_store_status")
|
||||
async def test_health_response_contains_cosmos_status(health_check_mock, app: FastAPI, client: AsyncClient) -> None:
|
||||
message = ""
|
||||
health_check_mock.return_value = StatusEnum.ok, message
|
||||
|
||||
response = await client.get(app.url_path_for("health:get-status-of-services"))
|
||||
|
||||
assert {"message": message, "service": strings.COSMOS_DB, "status": strings.OK} in response.json()["services"]
|
|
@ -0,0 +1,12 @@
|
|||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from httpx import AsyncClient
|
||||
from resources import strings
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_ping(app: FastAPI, client: AsyncClient) -> None:
|
||||
response = await client.get(app.url_path_for("ping:get-server-alive"))
|
||||
assert response.json()["message"] == strings.PONG
|
|
@ -0,0 +1,38 @@
|
|||
from mock import patch
|
||||
from azure.core.exceptions import ServiceRequestError
|
||||
|
||||
from models.schemas.health import StatusEnum
|
||||
from resources import strings
|
||||
from services import health_checker
|
||||
|
||||
|
||||
@patch("services.health_checker.CosmosClient")
|
||||
def test_get_state_store_status_responding(cosmos_client_mock) -> None:
|
||||
cosmos_client_mock.return_value = None
|
||||
|
||||
status, message = health_checker.create_state_store_status()
|
||||
|
||||
assert status == StatusEnum.ok
|
||||
assert message == ""
|
||||
|
||||
|
||||
@patch("services.health_checker.CosmosClient")
|
||||
def test_get_state_store_status_not_responding(cosmos_client_mock) -> None:
|
||||
cosmos_client_mock.return_value = None
|
||||
cosmos_client_mock.side_effect = ServiceRequestError(message="some message")
|
||||
|
||||
status, message = health_checker.create_state_store_status()
|
||||
|
||||
assert status == StatusEnum.not_ok
|
||||
assert message == strings.STATE_STORE_ENDPOINT_NOT_RESPONDING
|
||||
|
||||
|
||||
@patch("services.health_checker.CosmosClient")
|
||||
def test_get_state_store_status_other_exception(cosmos_client_mock) -> None:
|
||||
cosmos_client_mock.return_value = None
|
||||
cosmos_client_mock.side_effect = Exception()
|
||||
|
||||
status, message = health_checker.create_state_store_status()
|
||||
|
||||
assert status == StatusEnum.not_ok
|
||||
assert message == strings.UNSPECIFIED_ERROR
|
|
@ -1,7 +1,16 @@
|
|||
# requirements for api
|
||||
Click==7.1.2
|
||||
fastapi[all]==0.63.0
|
||||
fastapi[all]==0.65.1
|
||||
uvicorn[standard]==0.13.4
|
||||
gunicorn==20.1.0
|
||||
azure-core==1.13.0
|
||||
azure-cosmos==4.2.0
|
||||
|
||||
# dev requirements
|
||||
pytest==6.2.4
|
||||
pytest-asyncio~=0.14.0
|
||||
flake8==3.9.1
|
||||
httpx~=0.18.1
|
||||
asgi-lifespan~=1.0.1
|
||||
pre-commit==2.12.1
|
||||
mock==4.0.3
|
||||
|
|
|
@ -58,6 +58,17 @@ resource "azurerm_application_gateway" "agw" {
|
|||
protocol = "Http"
|
||||
}
|
||||
|
||||
probe {
|
||||
name = "management-api"
|
||||
pick_host_name_from_backend_http_settings = true
|
||||
interval = 10
|
||||
protocol = "Https"
|
||||
path = "/api/health"
|
||||
timeout = 10
|
||||
unhealthy_threshold = 2
|
||||
minimum_servers = 0
|
||||
}
|
||||
|
||||
request_routing_rule {
|
||||
name = local.request_routing_rule_name
|
||||
rule_type = "PathBasedRouting"
|
||||
|
@ -72,7 +83,7 @@ resource "azurerm_application_gateway" "agw" {
|
|||
|
||||
path_rule {
|
||||
name = "api"
|
||||
paths = ["/*"]
|
||||
paths = ["/api/*"]
|
||||
backend_address_pool_name = local.management_api_backend_address_pool_name
|
||||
backend_http_settings_name = local.http_setting_name
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче