From 332647b5d3a327240d3227191411007f60c33900 Mon Sep 17 00:00:00 2001 From: 0x1026 <69076992+0x1026@users.noreply.github.com> Date: Thu, 5 Dec 2024 05:52:13 +0100 Subject: [PATCH] fix(ci): resolve Docker image build errors --- .github/workflows/main.yml | 37 +++++++++++++++++++++++++++---------- .github/workflows/tests.yml | 5 ++--- api/Dockerfile | 32 ++++++++++++++++---------------- api/src/config.py | 7 +++---- api/src/main.py | 2 +- api/tests/test_config.py | 16 +++------------- app/Dockerfile | 5 ++--- 7 files changed, 54 insertions(+), 50 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 095e679..082e940 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,41 +51,55 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - changes: - name: ๐Ÿ”„ Detect changes + foundation: + name: ๐ŸŒฑ Foundation setup runs-on: ubuntu-latest if: github.event.action != 'closed' permissions: pull-requests: read outputs: images: ${{ github.event_name == 'push' && '["all"]' || steps.filter.outputs.changes }} - version: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || github.sha }} + version: ${{ steps.version.outputs.version }} steps: # https://github.com/actions/checkout/tree/11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + # https://github.com/dorny/paths-filter/tree/de90cc6fb38fc0963ad72b210f1f284cd68cea36 - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 + - name: ๐Ÿ”„ Detect changes + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 id: filter with: filters: .github/file-filters.yml + - name: ๐Ÿ“ Set version + id: version + shell: bash + run: | + ref=${{ github.ref }} + sha=${{ github.sha }} + if [[ $ref =~ ^refs/tags/v ]]; then + echo "version=${ref#refs/tags/}" >> "$GITHUB_OUTPUT" + else + echo "version=${sha}" >> "$GITHUB_OUTPUT" + fi + tests: name: ๐Ÿงช Tests - needs: changes + needs: foundation uses: ./.github/workflows/tests.yml with: - images: ${{ needs.changes.outputs.images }} + images: ${{ needs.foundation.outputs.images }} secrets: inherit build: name: ๐Ÿณ Docker runs-on: ubuntu-latest - needs: [changes, tests] + needs: [foundation, tests] strategy: fail-fast: false # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#example-adding-configurations matrix: - image: ${{ fromJSON(needs.changes.outputs.images) }} + image: ${{ fromJSON(needs.foundation.outputs.images) }} exclude: - image: all include: @@ -101,7 +115,7 @@ jobs: # with sigstore/fulcio when running outside of PRs. id-token: write outputs: - version: ${{ needs.changes.outputs.version }} + version: ${{ needs.foundation.outputs.version }} steps: # https://github.com/actions/checkout/tree/11bd71901bbe5b1630ceea73d27597364c9af683 @@ -164,7 +178,7 @@ jobs: uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 with: build-args: | - IMAGE_VERSION=${{ needs.changes.outputs.version }} + IMAGE_VERSION=${{ needs.foundation.outputs.version }} context: ${{ matrix.context }} push: ${{ github.event_name != 'pull_request' }} target: final @@ -205,6 +219,9 @@ jobs: if: ${{ github.event_name != 'pull_request' }} steps: + # https://github.com/actions/checkout/tree/11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + # https://github.com/getsentry/action-release/tree/e769183448303de84c5a06aaaddf9da7be26d6c7 # - name: Release to Sentry - name: ๐Ÿ“Ÿ Release to Sentry diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2331d8a..78fec83 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,7 @@ jobs: if: ${{ contains(inputs.images, 'all') || contains(fromJSON(inputs.images), 'urbantree') }} defaults: run: + shell: bash working-directory: app steps: # https://github.com/actions/checkout/tree/11bd71901bbe5b1630ceea73d27597364c9af683 @@ -45,7 +46,6 @@ jobs: working_dir: app - name: ๐Ÿงช Run PHPUnit tests with coverage - shell: bash run: ./vendor/bin/phpunit --log-junit junit.xml --coverage-clover=coverage.xml # https://github.com/codecov/test-results-action/tree/9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 @@ -69,6 +69,7 @@ jobs: if: ${{ contains(inputs.images, 'all') || contains(fromJSON(inputs.images), 'api') }} defaults: run: + shell: bash working-directory: api steps: # https://github.com/actions/checkout/tree/11bd71901bbe5b1630ceea73d27597364c9af683 @@ -87,11 +88,9 @@ jobs: python-version: "3.13.0" - name: ๐Ÿ“ฆ Install Python dependencies - shell: bash run: pip install -r requirements-dev.txt - name: ๐Ÿงช Run Python tests with coverage - shell: bash run: pytest tests --cov=./src --cov-report=xml --junitxml=junit.xml # https://github.com/codecov/test-results-action/tree/9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 diff --git a/api/Dockerfile b/api/Dockerfile index b76e3c9..8391953 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,6 +1,5 @@ # syntax=docker/dockerfile:1 -ARG IMAGE_VERSION ARG PYTHON_VERSION=3.13.0 # Download dependencies as a separate step to take advantage of Docker's caching. @@ -21,28 +20,34 @@ RUN --mount=type=cache,target=/root/.cache/pip \ --mount=type=bind,source=requirements-dev.txt,target=requirements-dev.txt \ python -m pip install -r requirements-dev.txt -FROM dev-deps AS development -ENV APP_ENV=development +FROM python:${PYTHON_VERSION}-slim AS base +ARG IMAGE_VERSION=dev +ENV IMAGE_VERSION=$IMAGE_VERSION +# Prevents Python from writing pyc files. ENV PYTHONDONTWRITEBYTECODE=1 +# Keeps Python from buffering stdout and stderr to avoid situations where +# the application crashes without emitting any logs due to buffering. ENV PYTHONUNBUFFERED=1 WORKDIR /app +# Copy the source code into the container. COPY . . +# Expose the port that the application listens on. EXPOSE 8000 + +FROM base AS development +ENV APP_ENV=development +COPY --from=dev-deps /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages +WORKDIR /app CMD ["python3", "-m", "uvicorn", "src.main:app", "--host=0.0.0.0", "--port=8000"] FROM development AS test ENV APP_ENV=test WORKDIR /app -CMD ["pytest", "tests", "--cov=src"] +CMD ["python3", "-m", "pytest", "tests", "--cov=src"] -FROM prod-deps AS final +FROM base AS final ENV APP_ENV=production -ENV IMAGE_VERSION=$IMAGE_VERSION -# Prevents Python from writing pyc files. -ENV PYTHONDONTWRITEBYTECODE=1 -# Keeps Python from buffering stdout and stderr to avoid situations where -# the application crashes without emitting any logs due to buffering. -ENV PYTHONUNBUFFERED=1 +COPY --from=prod-deps /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages WORKDIR /app # Create a non-privileged user that the app will run under. # See https://docs.docker.com/go/dockerfile-user-best-practices/ @@ -57,10 +62,5 @@ RUN adduser \ appuser # Switch to the non-privileged user to run the application. USER appuser -# Copy the source code into the container. -COPY . . -# Expose the port that the application listens on. -EXPOSE 8000 # Run the application. CMD ["python3", "-m", "uvicorn", "src.main:app", "--host=0.0.0.0", "--port=8000"] -# CMD ["python", "src/main.py"] diff --git a/api/src/config.py b/api/src/config.py index 25cf38d..f3255aa 100644 --- a/api/src/config.py +++ b/api/src/config.py @@ -14,7 +14,7 @@ class Settings(BaseSettings): APP_PACKAGE: str = "api" APP_ENV: str = "development" - IMAGE_VERSION: str | None = None + IMAGE_VERSION: str = "dev" MARIADB_SERVER: str MARIADB_PORT: int = 3306 @@ -66,9 +66,8 @@ def SQLALCHEMY_DATABASE_URI(self) -> MariaDBDsn: @computed_field @property - def SENTRY_RELEASE(self) -> str | None: - if self.IMAGE_VERSION: - return f"{self.APP_PACKAGE}@{self.IMAGE_VERSION}" + def SENTRY_RELEASE(self) -> str: + return f"{self.APP_PACKAGE}@{self.IMAGE_VERSION}" # If the APP_ENV environment variable is not set to test, the settings object is created. diff --git a/api/src/main.py b/api/src/main.py index ea12791..5ba0343 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -56,4 +56,4 @@ def hello(): @app.get("/health") def health_check(): - return {"status": "healthy"} + return {"status": "healthy", "version": settings.SENTRY_RELEASE} diff --git a/api/tests/test_config.py b/api/tests/test_config.py index be3f32e..c91869d 100644 --- a/api/tests/test_config.py +++ b/api/tests/test_config.py @@ -13,7 +13,7 @@ def test_settings_defaults(): assert settings.APP_NAME is None assert settings.APP_PACKAGE == "api" assert settings.APP_ENV == "test" - assert settings.IMAGE_VERSION is None + assert settings.IMAGE_VERSION == "dev" assert settings.MARIADB_SERVER == "localhost" assert settings.MARIADB_PORT == 3306 assert settings.MARIADB_USER == "user" @@ -26,7 +26,7 @@ def test_settings_defaults(): str(settings.SQLALCHEMY_DATABASE_URI) == "mysql+pymysql://user:password@localhost:3306/test_db" ) - assert settings.SENTRY_RELEASE is None + assert settings.SENTRY_RELEASE == "api@dev" def test_settings_missing_password(): @@ -102,7 +102,7 @@ def test_settings_with_custom_port(): ) -def test_sentry_release_with_image_version(): +def test_sentry_release_with_custom_image_version(): settings = Settings( IMAGE_VERSION="1.0.0", MARIADB_SERVER="localhost", @@ -111,13 +111,3 @@ def test_sentry_release_with_image_version(): MARIADB_DB="test_db", ) assert settings.SENTRY_RELEASE == "api@1.0.0" - - -def test_sentry_release_without_image_version(): - settings = Settings( - MARIADB_SERVER="localhost", - MARIADB_USER="user", - MARIADB_PASSWORD="password", - MARIADB_DB="test_db", - ) - assert settings.SENTRY_RELEASE is None diff --git a/app/Dockerfile b/app/Dockerfile index c5ee196..a1e45c1 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,7 +1,5 @@ # syntax=docker/dockerfile:1 -ARG IMAGE_VERSION - #* Create a prod stage for installing app dependencies defined in Composer. FROM composer:lts AS prod-deps WORKDIR /app @@ -20,6 +18,8 @@ RUN --mount=type=bind,source=./composer.json,target=composer.json \ #* Create a base stage for building the app image. FROM php:8.4-apache AS base +ARG IMAGE_VERSION=dev +ENV IMAGE_VERSION=$IMAGE_VERSION RUN docker-php-ext-install pdo pdo_mysql RUN a2enmod rewrite COPY ./src /var/www/html @@ -46,7 +46,6 @@ CMD ["./vendor/bin/phpunit"] #* Create a production stage. FROM base AS final ENV APP_ENV=production -ENV IMAGE_VERSION=$IMAGE_VERSION RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" COPY --from=prod-deps app/vendor/ /var/www/html/vendor # Switch to a non-privileged user (defined in the base image) that the app will run under.