Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Properly collect Python dependencies during image build. Next attempt at build cache #47

Merged
merged 59 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a48552b
fix: Properly collect Python dependencies during image build. Next at…
hostcc Sep 10, 2024
90b7306
* Github actions: use matrix job to build target images for each
hostcc Sep 12, 2024
265261c
* `Dockerfile`: removed cache mounts for installing dependencies,
hostcc Sep 13, 2024
a64c7af
* Github Actions: removed `context` from `docker/build-push-action`,
hostcc Sep 13, 2024
7f0da84
* Github actions: override package version for `setuptools_scm` for
hostcc Sep 14, 2024
516a1db
* Github Actions: fixed environment variable name overriding the package
hostcc Sep 14, 2024
bbbc799
* `Dockerfile`: use build arg `VERSION` to override package version
hostcc Sep 15, 2024
97be67b
* Github Actions: fixed `build-args` argument for `build-push-action`
hostcc Sep 15, 2024
4d104a2
* Github Actions: use platform name as tag for build cache
hostcc Sep 15, 2024
d2b612b
* Github Actions: use dedicated matrix job property for cache tag,
hostcc Sep 15, 2024
cc663bf
* Github Actions: use platform name as a suffix to
hostcc Sep 15, 2024
8b672f7
* Github Actions: added global suffix being platform
hostcc Sep 15, 2024
d28ca2f
* Github Actions: attempt to use `build-push-action`
hostcc Sep 15, 2024
e526bb9
* `Dockerfile`: Fixed copying resulting binaries into target image
hostcc Sep 15, 2024
23a8907
* `Dockerfile`: properly copy the package into
hostcc Sep 16, 2024
684c1a2
* Github Actions: added image for `linux/amd64` platform
hostcc Sep 16, 2024
ca445d6
* `Dockerfile`: moved `ARG VERSION` to a layer before
hostcc Sep 16, 2024
f7468e9
* Github Actions: added explanatory comments around
hostcc Sep 16, 2024
24419e0
* Removed generating `version.txt` as it seems keeping
hostcc Sep 16, 2024
489b046
* `Dockerfile`: moved check for `VERSION` argument
hostcc Sep 16, 2024
fd78758
* Documentation has been updated on tool's usage
hostcc Sep 16, 2024
b74159b
* `Dockerfile`: removed copying context in `build`
hostcc Sep 17, 2024
8a33a94
* `Dockerfile`: added comment re: writeable mount
hostcc Sep 17, 2024
155070e
* `Dockerfile`: dependencies are now installed in
hostcc Sep 17, 2024
2adb7fd
* `Dockerfile`: Limit use of the build context to
hostcc Sep 17, 2024
98e8373
* Github Actions: remove unused `pypi-publish` job
hostcc Sep 18, 2024
53b32ee
* Github actions: added job to test Docker images
hostcc Sep 24, 2024
f45cd00
* `Dockerfile`: optimized `build` stage to skip
hostcc Sep 25, 2024
c0b3235
* Github Actions: reverted to use matrix job for
hostcc Oct 4, 2024
d755d9d
* Github Actions: added AMD64 platform to build images for
hostcc Oct 4, 2024
7e8cded
* Github Actios: Fixed reference to container image version in `docke…
hostcc Oct 5, 2024
ba87624
* Github Actions: fixed typo in reference to version output for `dock…
hostcc Oct 5, 2024
a9614c7
* Github Actions: another type fixed in reference
hostcc Oct 5, 2024
b454ab0
* Github Actions: updated versions for
hostcc Oct 5, 2024
a3ae8dd
* Github Actions: attempt to use per-platform outputs
hostcc Oct 5, 2024
d624c47
* Github Actions: attempt using `format` funciton
hostcc Oct 5, 2024
949f0d3
* Github Actions: use `cloudposse/github-action-matrix-outputs-{read,…
hostcc Oct 6, 2024
ad94158
* Github Actions: removed unsupported expressions
hostcc Oct 6, 2024
5d135c0
* Github ACtions: use `GoCodeAlone` actions instead
hostcc Oct 6, 2024
46cef53
* Github Actions: use platform name for storing
hostcc Oct 6, 2024
a8b9f26
* Github Actions: use image version w/o image name
hostcc Oct 6, 2024
019784b
* Github Actions: push and test images using digests only,
hostcc Oct 7, 2024
ccf6791
* Github Actions: updated image digest refefence format for `docker-t…
hostcc Oct 7, 2024
658db82
* Github Actions: write multi-platform Docker manifest and test again…
hostcc Oct 7, 2024
7f54dc0
* Github Actions: encode image tags and annotations
hostcc Oct 7, 2024
8ce5976
* Github Actions: reference image metadata properties
hostcc Oct 7, 2024
e0c748a
* Github Actions: properly store/retrieve image
hostcc Oct 7, 2024
14ee250
* Github Actions: generating package version and
hostcc Oct 7, 2024
76d15fd
* Github Actions: attempt to store image digests
hostcc Oct 7, 2024
2058389
* Github Actions: properly access image digests in `docker-manifest` job
hostcc Oct 7, 2024
37c5759
* Github Actions: properly compose command line arguments when creati…
hostcc Oct 7, 2024
2661ff7
* Github Actions: fixed missing space separator for tags in image man…
hostcc Oct 7, 2024
2a8d211
* Github Actions: added inline JavaScript to
hostcc Oct 7, 2024
3c27f70
* Github Actions: properly set result in `buildx-imagetools-create-ar…
hostcc Oct 7, 2024
6f4cfe0
* Github Actions: fixed composing arguments for
hostcc Oct 7, 2024
de91f5e
* Github Actions: consider only tags for resulting
hostcc Oct 7, 2024
d11ab7a
* Github Actions: login to GHCR before pushing resulting manifest
hostcc Oct 7, 2024
7829788
* Github Actions: handle single tag per manifest
hostcc Oct 7, 2024
ee0174b
* Github Actions: more comments and some cleanup of unused code
hostcc Oct 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 181 additions & 79 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,32 @@ on:
- master

jobs:
version:
name: Generate package version
runs-on: ubuntu-latest
outputs:
value: ${{ steps.package-version.outputs.value }}
steps:
- name: Checkout the code
uses: actions/checkout@v4
with:
# Disable shallow clone for `setuptools_scm`, as it needs access to the
# history
fetch-depth: 0

- name: Set Python up
uses: actions/setup-python@v5

- name: Install dependencies
run: >-
python -m pip install --upgrade setuptools setuptools_scm

- name: Determine package version
id: package-version
run: |
package_version=`python -m setuptools_scm --format plain`
echo "value=$package_version" >> $GITHUB_OUTPUT

tests:
name: Tests
strategy:
Expand All @@ -33,31 +59,31 @@ jobs:
python: '3.12'
toxenv: py
runs-on: ${{ matrix.os }}
outputs:
version: ${{ steps.package-version.outputs.VALUE }}
needs: [version]
steps:
- name: Checkout the code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Disable shallow clone for Sonar scanner, as it needs access to the
# history
fetch-depth: 0

- name: Set Python up
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

- name: Install testing tools
run: >-
python -m pip install --upgrade setuptools pip tox virtualenv coverage
python -m pip install --upgrade \
setuptools setuptools_scm pip tox virtualenv coverage

- name: Run the tests
run: tox -e ${{ matrix.toxenv }}

- name: Generage Coverage combined XML report
run: coverage xml
- name: Determine package version
id: package-version
run: |
package_version=`cat version.txt`
echo "VALUE=$package_version" >> $GITHUB_OUTPUT

- name: SonarCloud scanning
uses: sonarsource/sonarcloud-github-action@master
env:
Expand All @@ -68,73 +94,26 @@ jobs:
args: >-
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}
-Dsonar.organization=${{ github.repository_owner }}
-Dsonar.projectVersion=${{ steps.package-version.outputs.VALUE }}
-Dsonar.projectVersion=${{ needs.version.outputs.value }}
# yamllint enable rule:line-length

pypi-publish:
name: Publish to PyPi
runs-on: ubuntu-latest
# PyPi disallows to publish packages with direct dependencies (GitHub
# sourced dependency in this case), so disable publishing for now
if: false
needs: [tests]
steps:
- name: Checkout the code
uses: actions/checkout@v3
with:
fetch-depth: 0 # `setuptools_scm` needs tags
- name: Set Python up
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install the PEP517 package builder
run: python -m pip install --upgrade build
- name: Build the package
run: python -m build
- name: Publish the package to Test PyPi
# Skip publishing to test PyPI if we're performing release, there might
# be already the version of the package from the merge to master branch
if: github.event_name != 'release'
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.TEST_PYPI_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- name: Publish the release to PyPi
# Publish to production PyPi only happens when a release published out
# of the main branch
if: >-
github.event_name == 'release'
&& github.event.action == 'published'
&& (github.event.release.target_commitish == 'main'
|| github.event.release.target_commitish == 'master')
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}

docker-publish:
name: Build and publish Docker images
docker-metadata:
name: Generate metadata for container images
runs-on: ubuntu-latest
needs: [tests]
permissions:
contents: read
packages: write
needs: [version]
outputs:
version: ${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
json: ${{ steps.meta.outputs.json }}
steps:
- name: Checkout the code
uses: actions/checkout@v3

- name: Set up QEMU for more platforms supported by Buildx
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Prepare Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=pep440,pattern={{raw}},value=${{ needs.tests.outputs.version }}
type=pep440,pattern={{raw}},value=${{ needs.version.outputs.value }}
type=raw,value=latest,enable=${{
github.event_name == 'release'
&& github.event.action == 'published'
Expand All @@ -144,22 +123,145 @@ jobs:
type=ref,event=pr
type=edge

docker-publish:
# The job uses platform as variations, since `buildx` can't properly cache
# those if done single shot (multiple platform specified to single command
# invocation)
name: Build and publish Docker images
strategy:
fail-fast: false
matrix:
include:
- platform_id: linux/arm/v7
platform_name: linux-arm-v7
- platform_id: linux/arm/v6
platform_name: linux-arm-v6
- platform_id: linux/arm64
platform_name: linux-arm64
- platform_id: linux/amd64
platform_name: linux-amd64
runs-on: ubuntu-latest
needs: [version, tests, docker-metadata]
permissions:
contents: read
packages: write
steps:
- name: Checkout the code
uses: actions/checkout@v4

- name: Set up QEMU for more platforms supported by Buildx
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push images
uses: docker/build-push-action@v6
id: build
with:
# No explicit context used, since that makes cache misses most of the
# time.
# See https://github.com/docker/build-push-action/issues/286 for more
# details
platforms: ${{ matrix.platform_id }}
labels: ${{ needs.docker-metadata.outputs.labels }}
annotations: ${{ needs.docker-metadata.outputs.annotations }}
# Implicit context points to working copy, not Git respository, so
# `setuptools_scm` needs to receive the version explicitly
build-args: |
VERSION=${{ needs.version.outputs.value }}
# Push by digest only, manifest will be added later
outputs: >-
type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
# Cache the buildx cache between builds using GitHub registry. `gha`
# cache has been mentioned to introduce cache misses for
# multi-platform builds, see https://github.com/docker/buildx/discussions/1382
# for potential hints
cache-from: |
type=registry,ref=ghcr.io/${{ github.repository }}/buildcache:${{ matrix.platform_name }}
cache-to: |
type=registry,ref=ghcr.io/${{ github.repository }}/buildcache:${{ matrix.platform_name }},mode=max

- name: Store image information
uses: GoCodeAlone/github-action-matrix-outputs-write@v1
id: out
with:
matrix-step-name: ${{ github.job }}
matrix-key: ${{ matrix.platform_name }}
outputs: |-
image_digest:
value: ${{ steps.build.outputs.digest }}

docker-manifest:
# The job uses image for for variations, hence each corresponding manifest
# is created separately - multiple tags in single command invocation might
# result in GHCR errors (not fully confirmed)
name: Create and push Docker manifest
runs-on: ubuntu-latest
needs: [docker-metadata, docker-publish]
strategy:
fail-fast: false
matrix:
tag: ${{ fromJson(needs.docker-metadata.outputs.json).tags }}
steps:
- name: Read image information from publish job
uses: GoCodeAlone/github-action-matrix-outputs-read@v1
id: read
with:
matrix-step-name: docker-publish

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push images
uses: docker/build-push-action@v6
# The token above should have read/write access
# (`Settings` -> `Actions` -> `General` -> `Workflow permissions` -> `Read and write permissions`)
- name: Create and push Docker manifest
run: >-
docker buildx imagetools create
--tag ${{ matrix.tag }}
${{ join(fromJson(steps.read.outputs.result).image_digest.*.value, ' ') }}

docker-test:
name: Test Docker images
runs-on: ubuntu-latest
needs: [docker-metadata, docker-manifest]
strategy:
fail-fast: false
matrix:
include:
- platform_id: linux/arm/v7
platform_name: linux-arm-v7
- platform_id: linux/arm/v6
platform_name: linux-arm-v6
- platform_id: linux/arm64
platform_name: linux-arm64
- platform_id: linux/amd64
platform_name: linux-amd64
steps:
- name: Set up QEMU for more platforms supported by Buildx
uses: docker/setup-qemu-action@v3
with:
context: .
platforms: linux/arm/v7,linux/arm/v6,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
# Cache the buildx cache between builds using GitHub Actions cache
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: ${{ matrix.platform_id }}

- name: Test the image
# Running the image with `--help` should be sufficient to ensure all
# dependencies are present
run: >-
docker run --rm
--platform ${{ matrix.platform_id }}
ghcr.io/${{ github.repository }}:${{ needs.docker-metadata.outputs.version }}
--help
43 changes: 35 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
FROM python:3.12.5-alpine AS build
COPY . /usr/src/
WORKDIR /usr/src/
FROM python:3.12.5-alpine AS deps

# Rust and Cargo are required to build `pyndatic-core` on ARM platforms
RUN apk add -U cargo git rust \
&& pip install build \
&& apk cache clean

# Limit use of the build context to the requirements file only, to avoid cache
# invalidation when other files get changed
COPY requirements.txt .
# Install dependencies in a separate layer to cache them
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
RUN pip install --root /tmp/target/ -r requirements.txt

FROM python:3.12.5-alpine AS build

RUN pip install build

# Build the package
RUN python -m build \
&& pip install --root target/ dist/*-`cat version.txt`*.whl
ARG VERSION
RUN test -z "${VERSION}" && echo "No 'VERSION' argument provided, exiting" \
&& exit 1 || true

# Writeable mount is needed for src/*.egg-info the `setup` module wants to
# create. `pip install --no-deps` is to skip installing dependencies to the
# package thus requiring extra prerequisites and extending the build time -
# those already fulfilled by `deps` stage
RUN \
--mount=type=bind,target=source/,rw \
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_ENERGOMERA_HASS_MQTT=${VERSION} \
python -m build --outdir /tmp/dist/ source/ \
&& pip install --no-deps --root /tmp/target/ /tmp/dist/*-${VERSION}*.whl

FROM python:3.12.5-alpine
# Ensure all the OS updates are applied to the resulting image
RUN apk -U upgrade \
&& apk cache clean

COPY --from=deps \
/tmp/target/usr/local/lib/ \
/usr/local/lib/
COPY --from=build \
/usr/src/target/root/.local/lib/ /usr/local/lib/
/tmp/target/usr/local/lib/ \
/usr/local/lib/
COPY --from=build \
/usr/src/target/root/.local/bin/ \
/tmp/target/usr/local/bin/ \
/usr/local/bin/

ENTRYPOINT ["energomera-hass-mqtt"]
15 changes: 9 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ Usage

.. code::

usage: energomera-hass-mqtt [-h] [-c CONFIG_FILE]

optional arguments:
-h, --help show this help message and exit
-c CONFIG_FILE, --config-file CONFIG_FILE
Path to configuration file (default: '/etc/energomera/config.yaml')
usage: energomera-hass-mqtt [-h] [-c CONFIG_FILE] [-a] [-d] [-o]

options:
-h, --help show this help message and exit
-c CONFIG_FILE, --config-file CONFIG_FILE
Path to configuration file (default: '/etc/energomera/config.yaml')
-a, --dry-run Dry run, do not actually send any data
-d, --debug Enable debug logging
-o, --one-shot Run only once, then exit

Configuration file format
=========================
Expand Down
Loading
Loading