Skip to content

Commit

Permalink
Merge pull request #3 from max-pfeiffer/feature/docker_image_upgrade
Browse files Browse the repository at this point in the history
Feature/docker image upgrade
  • Loading branch information
max-pfeiffer authored Dec 18, 2023
2 parents fac9048 + f50922f commit c1efb0e
Show file tree
Hide file tree
Showing 11 changed files with 709 additions and 757 deletions.
29 changes: 16 additions & 13 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,32 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: 3.11
- name: Install Poetry
uses: abatilo/actions-poetry@v2.0.0
uses: snok/install-poetry@v1
with:
poetry-version: 1.1.11
version: 1.7.1
virtualenvs-in-project: true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
run: |
poetry config virtualenvs.in-project true
poetry install --no-root
poetry install --no-interaction --no-root
- name: Run all tests with pytest
run: |
source .venv/bin/activate
export DOCKER_BUILDKIT=1
pytest
pytest --cov=./ --cov-report=xml
pytest --cov --cov-report=xml:coverage_report.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
files: ./coverage_report.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.idea
.venv

.coverage
.pytest_cache
__pycache__/
coverage_report.xml
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.5.0
hooks:
- id: check-ast
- id: check-merge-conflict
- id: detect-private-key
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.8.0
rev: 23.11.0
hooks:
- id: black
language_version: python3.9
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![codecov](https://codecov.io/gh/max-pfeiffer/uvicorn-poetry-project-template/branch/main/graph/badge.svg?token=WQI2SJJLZN)](https://codecov.io/gh/max-pfeiffer/uvicorn-poetry-project-template)
![pipeline workflow](https://github.com/max-pfeiffer/uvicorn-poetry-project-template/actions/workflows/pipeline.yml/badge.svg)
# uvicorn-poetry-project-template
Expand All @@ -22,7 +24,7 @@ poetry run uvicorn --workers 1 --host 0.0.0.0 --port 80 app.main:app
```
Build the production Docker image:
```shell
docker build --target production-image --tag my-application:1.0.0 .
docker build --tag my-application:1.0.0 .
```
Run the containerized application:
```shell
Expand Down
1,199 changes: 660 additions & 539 deletions poetry.lock

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ description = "A Cookiecutter template for spinning up a micro service"
authors = ["Max Pfeiffer <[email protected]>"]

[tool.poetry.dependencies]
python = "3.9.*"
python = "3.11.*"

[tool.poetry.dev-dependencies]
cookiecutter = "2.1.1"
pytest = "7.1.3"
pytest-cookies = "0.6.1"
black = "22.8.0"
pre-commit = "2.17.0"
docker = "6.0.0"
httpx = "0.23.0"
black = "23.12.0"
cookiecutter = "2.5.0"
coverage = "7.3.3"
docker = "7.0.0"
furl = "2.1.3"
httpx = "0.25.2"
pytest = "7.4.3"
pre-commit = "3.6.0"
pytest-cookies = "0.7.0"
pytest-cov = "4.1.0"
toml = "0.10.2"

# https://docs.pytest.org/en/latest/reference/customize.html
[tool.pytest.ini_options]
Expand All @@ -25,7 +28,7 @@ testpaths = [

[tool.black]
line-length = 80
target-version = ['py39']
target-version = ['py311']

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
55 changes: 0 additions & 55 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,61 +50,6 @@ def production_image(
path=path,
dockerfile=docker_file_name,
tag=tag,
target="production-image",
)[0]
image_tag: str = image.tags[0]
yield image_tag
docker_client.images.remove(image_tag, force=True)


@pytest.fixture(scope="session")
def development_image(
docker_client, docker_image_test_project, docker_file_name
) -> str:
path: str = str(docker_image_test_project.project_path)
tag: str = str(uuid4())

image: Image = docker_client.images.build(
path=path,
dockerfile=docker_file_name,
tag=tag,
target="development-image",
)[0]
image_tag: str = image.tags[0]
yield image_tag
docker_client.images.remove(image_tag, force=True)


@pytest.fixture(scope="session")
def black_test_image(
docker_client, docker_image_test_project, docker_file_name
) -> str:
path: str = str(docker_image_test_project.project_path)
tag: str = str(uuid4())

image: Image = docker_client.images.build(
path=path,
dockerfile=docker_file_name,
tag=tag,
target="black-test-image",
)[0]
image_tag: str = image.tags[0]
yield image_tag
docker_client.images.remove(image_tag, force=True)


@pytest.fixture(scope="session")
def test_image(
docker_client, docker_image_test_project, docker_file_name
) -> str:
path: str = str(docker_image_test_project.project_path)
tag: str = str(uuid4())

image: Image = docker_client.images.build(
path=path,
dockerfile=docker_file_name,
tag=tag,
target="test-image",
)[0]
image_tag: str = image.tags[0]
yield image_tag
Expand Down
47 changes: 1 addition & 46 deletions tests/test_docker_image_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,54 +36,9 @@ def test_production_image_build(
test_container: Container = docker_client.containers.run(
production_image,
name=cleaned_up_test_container,
ports={"80": "80"},
ports={"8000": "80"},
detach=True,
)
sleep(SLEEP_TIME)
assert test_container is not None
call_api_endpoints()


@pytest.mark.parametrize(
"cleaned_up_test_container", [str(uuid4())], indirect=True
)
def test_development_image_build(
docker_client, development_image, cleaned_up_test_container
):
test_container: Container = docker_client.containers.run(
development_image,
name=cleaned_up_test_container,
ports={"80": "80"},
detach=True,
)
sleep(SLEEP_TIME)
assert test_container is not None
call_api_endpoints()


@pytest.mark.parametrize(
"cleaned_up_test_container", [str(uuid4())], indirect=True
)
def test_black_test_image_build(
docker_client, black_test_image, cleaned_up_test_container
):
api_response: dict = docker_client.containers.run(
black_test_image,
name=cleaned_up_test_container,
ports={"80": "80"},
detach=True,
).wait()
assert api_response["StatusCode"] == 0


@pytest.mark.parametrize(
"cleaned_up_test_container", [str(uuid4())], indirect=True
)
def test_test_image_build(docker_client, test_image, cleaned_up_test_container):
api_response: dict = docker_client.containers.run(
test_image,
name=cleaned_up_test_container,
ports={"80": "80"},
detach=True,
).wait()
assert api_response["StatusCode"] == 0
4 changes: 2 additions & 2 deletions {{cookiecutter.project_slug}}/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.5.0
hooks:
- id: check-ast
- id: check-merge-conflict
- id: detect-private-key
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 23.11.0
hooks:
- id: black
language_version: python3.9
84 changes: 3 additions & 81 deletions {{cookiecutter.project_slug}}/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,90 +1,12 @@
# Be aware that you need to specify these arguments before the first FROM
# see: https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG OFFICIAL_PYTHON_IMAGE=python:3.9.14-slim-bullseye
ARG APPLICATION_SERVER_PORT=80
FROM pfeiffermax/uvicorn-poetry:2.1.0-python3.9.14-slim-bullseye as production-dependencies-build-stage
FROM pfeiffermax/uvicorn-poetry:3.2.0-python3.11.6-slim-bookworm

# install [tool.poetry.dependencies]
# this will install virtual environment into /.venv because of POETRY_VIRTUALENVS_IN_PROJECT=true
# see: https://python-poetry.org/docs/configuration/#virtualenvsin-project
COPY ./poetry.lock ./pyproject.toml /application_root/
COPY --chown=python_application:python_application ./poetry.lock ./pyproject.toml /application_root/
RUN poetry install --no-interaction --no-root --without dev

FROM ${OFFICIAL_PYTHON_IMAGE} as production-image
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=/application_root

WORKDIR /application_root

COPY --from=production-dependencies-build-stage /application_root/.venv /application_root/.venv
COPY --from=production-dependencies-build-stage /application_server/start_uvicorn.sh /application_server/start_uvicorn.sh

# Copy application files
COPY /app /application_root/app/

# Activate entrypoint for running the uvicorn application server
CMD ["/application_server/start_uvicorn.sh"]

# Document the exposed port which was configured in start_uvicorn.sh
# https://docs.docker.com/engine/reference/builder/#expose
EXPOSE ${APPLICATION_SERVER_PORT}

FROM production-dependencies-build-stage as dev-dependencies-build-stage

# install [tool.poetry.dev-dependencies]
RUN poetry install --no-interaction --no-root

FROM ${OFFICIAL_PYTHON_IMAGE} as development-image
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=/application_root \
UVICORN_RELOAD=1 \
UVICORN_LOG_LEVEL=debug

WORKDIR /application_root

COPY --from=dev-dependencies-build-stage /application_root/.venv /application_root/.venv
COPY --from=dev-dependencies-build-stage /application_server/start_uvicorn.sh /application_server/

COPY . /application_root/

# Activate entrypoint for running the uvicorn application server
CMD ["/application_server/start_uvicorn.sh"]

# Document the exposed port which was configured in start_uvicorn.sh
# https://docs.docker.com/engine/reference/builder/#expose
EXPOSE ${APPLICATION_SERVER_PORT}

FROM ${OFFICIAL_PYTHON_IMAGE} as test-base-image

COPY --from=dev-dependencies-build-stage /application_root/.venv /application_root/.venv

COPY /app /application_root/app/
COPY /tests /application_root/tests/


# image for running pep8 checks
FROM test-base-image as black-test-image

WORKDIR /application_root

COPY --from=dev-dependencies-build-stage /entrypoints/black_entrypoint.sh /entrypoints/

ENTRYPOINT /entrypoints/black_entrypoint.sh $0 $@

CMD ["--target-version py39", "--check", " --line-length 80", "."]

# image for running unit tests
FROM test-base-image as test-image

WORKDIR /application_root

COPY --from=dev-dependencies-build-stage /entrypoints/pytest_entrypoint.sh /entrypoints/

ENTRYPOINT /entrypoints/pytest_entrypoint.sh $0 $@

# You need to use pytest-cov as pytest plugin. Makes life very simple.
# tests directory is configured in pyproject.toml
# https://github.com/pytest-dev/pytest-cov
CMD ["--cov=app", "--cov-report=xml:/test_coverage_reports/test_coverage.xml"]
COPY --chown=python_application:python_application /app /application_root/app/
16 changes: 8 additions & 8 deletions {{cookiecutter.project_slug}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.9"
uvicorn = {version = "0.18.3", extras = ["standard"]}
fastapi = "0.82.0"
python = "^3.10"
uvicorn = {version = "0.24.0.post1", extras = ["standard"]}
fastapi = "0.104.1"

[tool.poetry.dev-dependencies]
pytest = "7.1.3"
pytest-cov = "3.0.0"
coverage = "6.4.4"
black = "22.8.0"
black = "23.11.0"
coverage = "7.3.3"
pre-commit = "3.6.0"
pytest = "7.4.3"
pytest-cov = "4.1.0"
requests = "2.28.1"
pre-commit = "2.17.0"

# https://docs.pytest.org/en/latest/reference/customize.html
[tool.pytest.ini_options]
Expand Down

0 comments on commit c1efb0e

Please sign in to comment.