Skip to content
This repository was archived by the owner on Feb 19, 2023. It is now read-only.

Commit 301b048

Browse files
authored
Merging 'develop' into master (#16)
* chore: clean up [#15] * chore: version bump [#15] * ops: fix GHA config[#15]
1 parent 78701b2 commit 301b048

File tree

10 files changed

+184
-53
lines changed

10 files changed

+184
-53
lines changed

.env_example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ SECRET_KEY=test-key
33
ENV=dev
44
PROJECT_NAME=fastapi_backend_base
55
VERSION=v1
6+
ENABLE_BUILD=false
67

78
# Database
89
POSTGRES_USER=fastapibackendbase

.github/workflows/main.yml

Lines changed: 130 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,47 @@
1-
name: lint and test
1+
---
2+
3+
name: build, test and deploy
24

35
on:
46
push:
57
tags:
68
- '*'
7-
pull_request:
89
branches:
910
- master
1011
- develop
1112

13+
env:
14+
DOCTL_VERSION: 1.84.1
15+
1216
jobs:
13-
linter:
14-
runs-on: ubuntu-20.04
17+
flake8:
18+
runs-on: ubuntu-latest
1519
name: Check python linting
1620
steps:
1721
- name: Checkout
18-
uses: actions/checkout@v2
22+
uses: actions/checkout@v3
1923
- name: Start linter
2024
run: |
21-
docker run --rm -w="/code/backend" -v $(pwd):/code alpine/flake8:3.9.2 .
25+
docker run --rm -w="/code/backend" -v $(pwd):/code alpine/flake8:5.0.4 .
2226
2327
black:
24-
runs-on: ubuntu-20.04
28+
runs-on: ubuntu-latest
2529
name: Black code formatting
2630
steps:
2731
- name: Checkout
28-
uses: actions/checkout@v2
32+
uses: actions/checkout@v3
2933
- name: Set up Python
30-
uses: actions/setup-python@v1
34+
uses: actions/setup-python@v4
3135
with:
3236
python-version: '3.9'
3337
- name: run black
3438
working-directory: ./backend
3539
run: |
36-
pip install black==22.6.0
40+
pip install black==21.5b1
3741
black --check .
3842
3943
isort:
40-
runs-on: ubuntu-20.04
44+
runs-on: ubuntu-latest
4145
name: isort imports
4246
steps:
4347
- name: Checkout
@@ -52,12 +56,72 @@ jobs:
5256
pip install isort==5.10.1
5357
isort src
5458
59+
build_backend:
60+
if: ${{ secrets.ENABLE_BUILD }} == 'true'
61+
runs-on: ubuntu-latest
62+
needs: [flake8,black,isort]
63+
name: Build backend
64+
steps:
65+
- name: Checkout
66+
uses: actions/checkout@v3
67+
- name: Install doctl
68+
uses: digitalocean/action-doctl@v2
69+
with:
70+
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
71+
- name: Build backend
72+
run: |
73+
docker build --build-arg env=staging -t ${{ secrets.REGISTRY }}/backend:${{ github.sha }} ./backend
74+
- name: Log in to DigitalOcean Container Registry with short-lived credentials
75+
run: doctl registry login --expiry-seconds 600
76+
- name: Push image to DigitalOcean Container Registry
77+
run: docker push ${{ secrets.REGISTRY }}/backend:${{ github.sha }}
78+
79+
build_proxy:
80+
if: ${{ secrets.ENABLE_BUILD }} == 'true'
81+
runs-on: ubuntu-latest
82+
needs: [flake8,black]
83+
name: Build Nginx proxy
84+
steps:
85+
- name: Checkout
86+
uses: actions/checkout@v3
87+
- name: Install doctl
88+
uses: digitalocean/action-doctl@v2
89+
with:
90+
token: ${{ secrets.DIGITALOCEAN_TOKEN }}
91+
- name: Build proxy
92+
run: |
93+
docker build --build-arg env=staging -t ${{ secrets.REGISTRY }}/proxy:${{ github.sha }} ./proxy
94+
- name: Log in to DigitalOcean Container Registry with short-lived credentials
95+
run: doctl registry login --expiry-seconds 600
96+
- name: Push image to DigitalOcean Container Registry
97+
run: docker push ${{ secrets.REGISTRY }}/proxy:${{ github.sha }}
98+
99+
unit_tests:
100+
runs-on: ubuntu-latest
101+
needs: [build_backend, build_proxy]
102+
name: Run unit tests
103+
steps:
104+
- name: Checkout
105+
uses: actions/checkout@v3
106+
- name: Run unit tests
107+
run: |
108+
docker-compose -f ops/docker-compose.test.yml up --exit-code-from backend
109+
- name: Codecov
110+
uses: codecov/codecov-action@v1
111+
with:
112+
file: /data/coverage.xml
113+
flags: unittests
114+
- name: Clean-up
115+
if: always()
116+
run: |
117+
docker-compose -f ops/docker-compose.test.yml down -v
118+
55119
create-release:
56-
needs: [linter, black, isort]
57-
runs-on: ubuntu-20.04
120+
needs: [flake8,black,isort]
121+
runs-on: ubuntu-latest
58122
steps:
59123
- name: checkout
60-
uses: actions/checkout@v2
124+
uses: actions/checkout@v3
61125
with:
62126
fetch-depth: 0 # need this for all history for all branches and tags
63127
- name: Create Release
@@ -69,3 +133,55 @@ jobs:
69133

70134
outputs:
71135
ReleaseTag: ${{ steps.create_release.outputs.release_tag }}
136+
137+
deploy:
138+
runs-on: ubuntu-latest
139+
name: Deploy
140+
needs: [unit_tests]
141+
steps:
142+
- name: Checkout
143+
uses: actions/checkout@v3
144+
145+
- name: Deploy staging
146+
uses: ironhalik/docker-over-ssh-action@v6
147+
if: github.ref == 'refs/heads/develop'
148+
env:
149+
COMPOSE_FILE: ops/docker-compose.staging.yml
150+
STACK_NAME: tagyoureitbot-staging
151+
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
152+
REGISTRY: ${{ secrets.REGISTRY }}
153+
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
154+
PASSWORD: ${{ secrets.PASSWORD }}
155+
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
156+
with:
157+
user: ubuntu
158+
host: ${{ secrets.STAGING_HOST_IP }}
159+
key: ${{ secrets.SSH_KEY }}
160+
script: |
161+
wget https://github.com/digitalocean/doctl/releases/download/v${{ env.DOCTL_VERSION }}/doctl-${{ env.DOCTL_VERSION }}-linux-amd64.tar.gz
162+
tar xf ./doctl-${{ env.DOCTL_VERSION }}-linux-amd64.tar.gz
163+
mv ./doctl /usr/local/bin
164+
doctl registry login
165+
docker stack deploy --compose-file ${COMPOSE_FILE} --with-registry-auth --prune ${STACK_NAME}
166+
167+
- name: Deploy prod
168+
uses: ironhalik/docker-over-ssh-action@v6
169+
if: github.ref == 'refs/heads/master'
170+
env:
171+
COMPOSE_FILE: ops/docker-compose.prod.yml
172+
STACK_NAME: tagyoureit-prod
173+
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
174+
REGISTRY: ${{ secrets.REGISTRY }}
175+
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
176+
PASSWORD: ${{ secrets.PASSWORD }}
177+
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
178+
with:
179+
user: ubuntu
180+
host: ${{ secrets.PROD_HOST_IP }}
181+
key: ${{ secrets.SSH_KEY }}
182+
script: |
183+
wget https://github.com/digitalocean/doctl/releases/download/v${{ env.DOCTL_VERSION }}/doctl-${{ env.DOCTL_VERSION }}-linux-amd64.tar.gz
184+
tar xf ./doctl-${{ env.DOCTL_VERSION }}-linux-amd64.tar.gz
185+
mv ./doctl /usr/local/bin
186+
doctl registry login
187+
docker stack deploy --compose-file ${COMPOSE_FILE} --with-registry-auth --prune ${STACK_NAME}

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "fastapi-backend-base"
3-
version = "3.0.0"
3+
version = "3.0.1"
44
description = "Base project for building fastapi backends"
55
authors = ["nickatnight <[email protected]>"]
66

backend/src/core/config.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,52 @@
1-
from typing import List, Optional
1+
from typing import Any, Dict, List, Optional
22

3-
from pydantic import AnyHttpUrl, BaseSettings, Field
3+
from pydantic import AnyHttpUrl, BaseSettings, Field, PostgresDsn, validator
44

55

66
class Settings(BaseSettings):
7-
SECRET_KEY: str = Field(..., env="SECRET_KEY")
87
# 60 minutes * 24 hours * 8 days = 8 days
98
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
10-
SERVER_NAME: Optional[str] = Field(..., env="NGINX_HOST")
9+
# SERVER_NAME: Optional[str] = Field(..., env="NGINX_HOST")
1110
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
1211

13-
PROJECT_NAME: str = Field(..., env="PROJECT_NAME")
14-
15-
POSTGRES_HOST: str = Field(..., env="POSTGRES_HOST")
16-
POSTGRES_USER: str = Field(..., env="POSTGRES_USER")
17-
POSTGRES_PASSWORD: str = Field(..., env="POSTGRES_PASSWORD")
18-
POSTGRES_DB: str = Field(..., env="POSTGRES_DB")
19-
POSTGRES_PORT: str = Field(..., env="POSTGRES_PORT")
20-
21-
REDIS_HOST: str = Field(..., env="REDIS_HOST")
22-
REDIS_PORT: str = Field(..., env="REDIS_PORT")
23-
# REDIS_URL: str = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"
24-
25-
CLIENT_ID: str = Field(..., env="CLIENT_ID")
26-
CLIENT_SECRET: str = Field(..., env="CLIENT_SECRET")
27-
28-
VERSION: str = Field(..., env="VERSION")
12+
CLIENT_ID: str = Field(default="", env="CLIENT_ID")
13+
CLIENT_SECRET: str = Field(default="", env="CLIENT_SECRET")
14+
15+
VERSION: str = Field(default="", env="VERSION")
16+
DEBUG: bool = Field(default=True, env="DEBUG")
17+
18+
POSTGRES_USER: str = Field(default="", env="POSTGRES_USER")
19+
POSTGRES_PASSWORD: str = Field(default="", env="POSTGRES_PASSWORD")
20+
POSTGRES_HOST: str = Field(default="", env="POSTGRES_HOST")
21+
POSTGRES_PORT: str = Field(default="", env="POSTGRES_PORT")
22+
POSTGRES_DB: str = Field(default="", env="POSTGRES_DB")
23+
24+
DB_POOL_SIZE: int = Field(default=83, env="DB_POOL_SIZE")
25+
WEB_CONCURRENCY: int = Field(default=9, env="WEB_CONCURRENCY")
26+
MAX_OVERFLOW: int = Field(default=64, env="MAX_OVERFLOW")
27+
POOL_SIZE: Optional[int]
28+
POSTGRES_URL: Optional[str]
29+
30+
@validator("POOL_SIZE", pre=True)
31+
def build_pool(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
32+
if isinstance(v, int):
33+
return v
34+
35+
return max(values.get("DB_POOL_SIZE") // values.get("WEB_CONCURRENCY"), 5) # type: ignore
36+
37+
@validator("POSTGRES_URL", pre=True)
38+
def build_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
39+
if isinstance(v, str):
40+
return v
41+
42+
return PostgresDsn.build(
43+
scheme="postgresql+asyncpg",
44+
user=values.get("POSTGRES_USER"),
45+
password=values.get("POSTGRES_PASSWORD"),
46+
host=values.get("POSTGRES_HOST"),
47+
port=str(values.get("POSTGRES_PORT")),
48+
path=f"/{values.get('POSTGRES_DB') or ''}",
49+
)
2950

3051

3152
settings = Settings()

backend/src/db/session.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
44
from sqlalchemy.orm import sessionmaker
55

6-
from src.db.utils import get_db_url
6+
from src.core.config import settings
77

88

99
logger = logging.getLogger(__name__)
10-
DB_POOL_SIZE = 83
11-
WEB_CONCURRENCY = 9
12-
POOL_SIZE = max(DB_POOL_SIZE // WEB_CONCURRENCY, 5)
13-
POSTGRES_URL = get_db_url()
10+
1411

1512
engine = create_async_engine(
16-
POSTGRES_URL, echo=True, future=True, pool_size=POOL_SIZE, max_overflow=64
13+
settings.POSTGRES_URL,
14+
echo=True,
15+
future=True,
16+
pool_size=settings.POOL_SIZE,
17+
max_overflow=settings.MAX_OVERFLOW,
1718
)
1819
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
1920

backend/src/db/utils.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

backend/src/migrations/env.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@
1212

1313
sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
1414

15-
from src.db.utils import get_db_url # noqa
15+
from src.core.config import settings # noqa
1616
from src.models import * # noqa
1717

1818

19-
POSTGRES_URL = get_db_url()
20-
2119
# this is the Alembic Config object, which provides
2220
# access to the values within the .ini file in use.
2321
config = context.config
@@ -52,7 +50,7 @@ def run_migrations_offline() -> None:
5250
5351
"""
5452
context.configure(
55-
url=POSTGRES_URL,
53+
url=settings.POSTGRES_URL,
5654
target_metadata=target_metadata,
5755
literal_binds=True,
5856
dialect_opts={"paramstyle": "named"},
@@ -84,7 +82,7 @@ async def run_migrations_online() -> None:
8482
# future=True,
8583
# )
8684
# )
87-
connectable = AsyncEngine(create_engine(POSTGRES_URL, echo=True, future=True))
85+
connectable = AsyncEngine(create_engine(settings.POSTGRES_URL, echo=True, future=True))
8886

8987
async with connectable.connect() as connection:
9088
await connection.run_sync(do_run_migrations)

ops/docker-compose.prod.yml

Whitespace-only changes.

ops/docker-compose.test.yml

Whitespace-only changes.

proxy/Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ RUN apk add --no-cache\
1212
musl-dev &&\
1313
pip3 install --upgrade pip &&\
1414
pip3 install --no-cache-dir \
15-
j2cli==0.3.10 \
16-
certbot-nginx==1.20.0 &&\
15+
j2cli==0.3.10 &&\
1716
rm -rf /etc/nginx/nginx.conf /etc/nginx/http.d/*
1817

1918
COPY ./ /code/

0 commit comments

Comments
 (0)