mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-04-19 08:55:15 +00:00
Workflow: Docker: Add more archs: armv6/v7, i386
* Build amd64 on any PR.
This commit is contained in:
parent
480adf8100
commit
87ac127817
2
.env
2
.env
@ -3,3 +3,5 @@ EOS_SERVER__PORT=8503
|
|||||||
EOS_SERVER__EOSDASH_PORT=8504
|
EOS_SERVER__EOSDASH_PORT=8504
|
||||||
|
|
||||||
PYTHON_VERSION=3.12.6
|
PYTHON_VERSION=3.12.6
|
||||||
|
BASE_IMAGE=python
|
||||||
|
IMAGE_SUFFIX=-slim
|
||||||
|
89
.github/workflows/docker-build.yml
vendored
89
.github/workflows/docker-build.yml
vendored
@ -7,13 +7,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
- 'feature/config-overhaul'
|
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- '**'
|
||||||
- 'feature/config-overhaul'
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKERHUB_REPO: akkudoktor/eos
|
DOCKERHUB_REPO: akkudoktor/eos
|
||||||
@ -40,7 +38,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if ${{ github.event_name == 'pull_request' }}; then
|
if ${{ github.event_name == 'pull_request' }}; then
|
||||||
echo 'matrix=[
|
echo 'matrix=[
|
||||||
{"platform": "linux/arm64"}
|
{"platform": {"name": "linux/amd64"}},
|
||||||
|
{"platform": {"name": "linux/arm64"}},
|
||||||
|
{"platform": {"name": "linux/386"}},
|
||||||
]' | tr -d '[:space:]' >> $GITHUB_OUTPUT
|
]' | tr -d '[:space:]' >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo 'matrix=[]' >> $GITHUB_OUTPUT
|
echo 'matrix=[]' >> $GITHUB_OUTPUT
|
||||||
@ -58,13 +58,69 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- linux/amd64
|
- name: linux/amd64
|
||||||
- linux/arm64
|
base: python
|
||||||
|
python: 3.12 # pendulum not yet on pypi for 3.13
|
||||||
|
rustup_install: ""
|
||||||
|
apt_packages: ""
|
||||||
|
apt_build_packages: ""
|
||||||
|
pip_extra_url: ""
|
||||||
|
- name: linux/arm64
|
||||||
|
base: python
|
||||||
|
python: 3.12 # pendulum not yet on pypi for 3.13
|
||||||
|
rustup_install: ""
|
||||||
|
apt_packages: ""
|
||||||
|
apt_build_packages: ""
|
||||||
|
pip_extra_url: ""
|
||||||
|
- name: linux/arm/v6
|
||||||
|
base: python
|
||||||
|
python: 3.11 # highest version on piwheels
|
||||||
|
rustup_install: true
|
||||||
|
# numpy: libopenblas0
|
||||||
|
# h5py: libhdf5-hl-310
|
||||||
|
#apt_packages: "libopenblas0 libhdf5-hl-310"
|
||||||
|
apt_packages: "" #TODO verify
|
||||||
|
# pendulum: git (apply patch)
|
||||||
|
# matplotlib (countourpy): g++
|
||||||
|
# fastapi (MarkupSafe): gcc
|
||||||
|
# rustup installer: curl
|
||||||
|
apt_build_packages: "curl git g++"
|
||||||
|
pip_extra_url: "https://www.piwheels.org/simple" # armv6/v7 packages
|
||||||
|
- name: linux/arm/v7
|
||||||
|
base: python
|
||||||
|
python: 3.11 # highest version on piwheels
|
||||||
|
rustup_install: true
|
||||||
|
# numpy: libopenblas0
|
||||||
|
# h5py: libhdf5-hl-310
|
||||||
|
#apt_packages: "libopenblas0 libhdf5-hl-310"
|
||||||
|
apt_packages: "" #TODO verify
|
||||||
|
# pendulum: git (apply patch)
|
||||||
|
# matplotlib (countourpy): g++
|
||||||
|
# fastapi (MarkupSafe): gcc
|
||||||
|
# rustup installer: curl
|
||||||
|
apt_build_packages: "curl git g++"
|
||||||
|
pip_extra_url: "https://www.piwheels.org/simple" # armv6/v7 packages
|
||||||
|
- name: linux/386
|
||||||
|
# Get 32bit distributor fix for pendulum, not yet officially released.
|
||||||
|
# Needs Debian testing instead of python:xyz which is based on Debian stable.
|
||||||
|
base: debian
|
||||||
|
python: trixie
|
||||||
|
rustup_install: ""
|
||||||
|
# numpy: libopenblas0
|
||||||
|
# h5py: libhdf5-hl-310
|
||||||
|
apt_packages: "python3-pendulum python3-pip libopenblas0 libhdf5-hl-310"
|
||||||
|
# numpy: g++, libc-dev
|
||||||
|
# skikit: pkgconf python3-dev, libopenblas-dev
|
||||||
|
# uvloop: make
|
||||||
|
# h5py: libhdf5-dev
|
||||||
|
# many others g++/gcc
|
||||||
|
apt_build_packages: "g++ pkgconf libc-dev python3-dev make libopenblas-dev libhdf5-dev"
|
||||||
|
pip_extra_url: ""
|
||||||
exclude: ${{ fromJSON(needs.platform-excludes.outputs.excludes) }}
|
exclude: ${{ fromJSON(needs.platform-excludes.outputs.excludes) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform.name }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
@ -98,7 +154,8 @@ jobs:
|
|||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
# skip for pull requests
|
# skip for pull requests
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
#TODO: uncomment again
|
||||||
|
#if: ${{ github.event_name != 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@ -106,8 +163,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
# skip for pull requests
|
#if: ${{ github.event_name != 'pull_request' }}
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@ -116,10 +172,19 @@ jobs:
|
|||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform.name }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,"push=${{ github.event_name != 'pull_request' }}","annotation-index.org.opencontainers.image.description=${{ env.EOS_REPO_DESCRIPTION }}"
|
#TODO: uncomment again
|
||||||
|
#outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,"push=${{ github.event_name != 'pull_request' }}","annotation-index.org.opencontainers.image.description=${{ env.EOS_REPO_DESCRIPTION }}"
|
||||||
|
outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true,"annotation-index.org.opencontainers.image.description=${{ env.EOS_REPO_DESCRIPTION }}"
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=${{ matrix.platform.base }}
|
||||||
|
PYTHON_VERSION=${{ matrix.platform.python }}
|
||||||
|
PIP_EXTRA_INDEX_URL=${{ matrix.platform.pip_extra_url }}
|
||||||
|
APT_PACKAGES=${{ matrix.platform.apt_packages }}
|
||||||
|
APT_BUILD_PACKAGES=${{ matrix.platform.apt_build_packages }}
|
||||||
|
RUSTUP_INSTALL=${{ matrix.platform.rustup_install }}
|
||||||
|
|
||||||
- name: Generate artifact attestation DockerHub
|
- name: Generate artifact attestation DockerHub
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
|
88
Dockerfile
88
Dockerfile
@ -1,5 +1,7 @@
|
|||||||
ARG PYTHON_VERSION=3.12.7
|
ARG PYTHON_VERSION=3.12.8
|
||||||
FROM python:${PYTHON_VERSION}-slim
|
ARG BASE_IMAGE=python
|
||||||
|
ARG IMAGE_SUFFIX=-slim
|
||||||
|
FROM ${BASE_IMAGE}:${PYTHON_VERSION}${IMAGE_SUFFIX} AS base
|
||||||
|
|
||||||
LABEL source="https://github.com/Akkudoktor-EOS/EOS"
|
LABEL source="https://github.com/Akkudoktor-EOS/EOS"
|
||||||
|
|
||||||
@ -11,7 +13,8 @@ ENV EOS_CONFIG_DIR="${EOS_DIR}/config"
|
|||||||
|
|
||||||
WORKDIR ${EOS_DIR}
|
WORKDIR ${EOS_DIR}
|
||||||
|
|
||||||
RUN adduser --system --group --no-create-home eos \
|
# Use useradd over adduser to support both debian:x-slim and python:x-slim base images
|
||||||
|
RUN useradd --system --no-create-home --shell /usr/sbin/nologin eos \
|
||||||
&& mkdir -p "${MPLCONFIGDIR}" \
|
&& mkdir -p "${MPLCONFIGDIR}" \
|
||||||
&& chown eos "${MPLCONFIGDIR}" \
|
&& chown eos "${MPLCONFIGDIR}" \
|
||||||
&& mkdir -p "${EOS_CACHE_DIR}" \
|
&& mkdir -p "${EOS_CACHE_DIR}" \
|
||||||
@ -21,13 +24,85 @@ RUN adduser --system --group --no-create-home eos \
|
|||||||
&& mkdir -p "${EOS_CONFIG_DIR}" \
|
&& mkdir -p "${EOS_CONFIG_DIR}" \
|
||||||
&& chown eos "${EOS_CONFIG_DIR}"
|
&& chown eos "${EOS_CONFIG_DIR}"
|
||||||
|
|
||||||
|
ARG APT_PACKAGES
|
||||||
|
ENV APT_PACKAGES="${APT_PACKAGES}"
|
||||||
|
RUN --mount=type=cache,sharing=locked,target=/var/lib/apt/lists \
|
||||||
|
--mount=type=cache,sharing=locked,target=/var/cache/apt \
|
||||||
|
rm /etc/apt/apt.conf.d/docker-clean; \
|
||||||
|
if [ -n "${APT_PACKAGES}" ]; then \
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ${APT_PACKAGES}; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
ARG APT_BUILD_PACKAGES
|
||||||
|
ENV APT_BUILD_PACKAGES="${APT_BUILD_PACKAGES}"
|
||||||
|
RUN --mount=type=cache,sharing=locked,target=/var/lib/apt/lists \
|
||||||
|
--mount=type=cache,sharing=locked,target=/var/cache/apt \
|
||||||
|
rm /etc/apt/apt.conf.d/docker-clean; \
|
||||||
|
if [ -n "${APT_BUILD_PACKAGES}" ]; then \
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ${APT_BUILD_PACKAGES}; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARG RUSTUP_INSTALL
|
||||||
|
ENV RUSTUP_INSTALL="${RUSTUP_INSTALL}"
|
||||||
|
ENV RUSTUP_HOME=/opt/rust
|
||||||
|
ENV CARGO_HOME=/opt/rust
|
||||||
|
ENV PATH="$RUSTUP_HOME/bin:$PATH"
|
||||||
|
ARG PIP_EXTRA_INDEX_URL
|
||||||
|
ENV PIP_EXTRA_INDEX_URL="${PIP_EXTRA_INDEX_URL}"
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
--mount=type=tmpfs,target=/root/.cargo \
|
||||||
|
dpkgArch=$(dpkg --print-architecture) \
|
||||||
|
&& if [ -n "${RUSTUP_INSTALL}" ]; then \
|
||||||
|
case "$dpkgArch" in \
|
||||||
|
# armv6
|
||||||
|
armel) \
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target arm-unknown-linux-gnueabi --no-modify-path \
|
||||||
|
;; \
|
||||||
|
*) \
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path \
|
||||||
|
;; \
|
||||||
|
esac \
|
||||||
|
&& rustc --version \
|
||||||
|
&& cargo --version; \
|
||||||
|
fi \
|
||||||
|
# Install 32bit fix for pendulum, can be removed after next pendulum release (> 3.0.0)
|
||||||
|
&& case "$dpkgArch" in \
|
||||||
|
# armv7/armv6
|
||||||
|
armhf|armel) \
|
||||||
|
git clone https://github.com/python-pendulum/pendulum.git \
|
||||||
|
&& git -C pendulum checkout -b 3.0.0 3.0.0 \
|
||||||
|
# Apply 32bit patch
|
||||||
|
&& git -C pendulum -c user.name=ci -c user.email=ci@github.com cherry-pick b84b97625cdea00f8ab150b8b35aa5ccaaf36948 \
|
||||||
|
&& cd pendulum \
|
||||||
|
# Use pip3 over pip to support both debian:x and python:x base images
|
||||||
|
&& pip3 install maturin \
|
||||||
|
&& maturin build --release --out dist \
|
||||||
|
&& pip3 install dist/*.whl --break-system-packages \
|
||||||
|
&& cd - \
|
||||||
|
;; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Use tmpfs for cargo due to qemu (multiarch) limitations
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install -r requirements.txt
|
--mount=type=tmpfs,target=/root/.cargo \
|
||||||
|
# Use pip3 over pip to support both debian:x and python:x base images
|
||||||
|
pip3 install -r requirements.txt --break-system-packages
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
# Copy all python dependencies previously installed or built to the final stage.
|
||||||
|
COPY --from=build /usr/local/ /usr/local/
|
||||||
|
COPY --from=build /opt/eos/requirements.txt .
|
||||||
|
|
||||||
COPY pyproject.toml .
|
COPY pyproject.toml .
|
||||||
RUN mkdir -p src && pip install -e .
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
# Use pip3 over pip to support both debian:x and python:x base images
|
||||||
|
mkdir -p src && pip3 install -e . --break-system-packages
|
||||||
|
|
||||||
COPY src src
|
COPY src src
|
||||||
|
|
||||||
@ -37,6 +112,7 @@ ENTRYPOINT []
|
|||||||
EXPOSE 8503
|
EXPOSE 8503
|
||||||
EXPOSE 8504
|
EXPOSE 8504
|
||||||
|
|
||||||
CMD ["python", "src/akkudoktoreos/server/eos.py", "--host", "0.0.0.0"]
|
# Use python3 over python to support both debian:x and python:x base images
|
||||||
|
CMD ["python3", "src/akkudoktoreos/server/eos.py", "--host", "0.0.0.0"]
|
||||||
|
|
||||||
VOLUME ["${MPLCONFIGDIR}", "${EOS_CACHE_DIR}", "${EOS_OUTPUT_DIR}", "${EOS_CONFIG_DIR}"]
|
VOLUME ["${MPLCONFIGDIR}", "${EOS_CACHE_DIR}", "${EOS_OUTPUT_DIR}", "${EOS_CONFIG_DIR}"]
|
||||||
|
@ -11,6 +11,12 @@ services:
|
|||||||
dockerfile: "Dockerfile"
|
dockerfile: "Dockerfile"
|
||||||
args:
|
args:
|
||||||
PYTHON_VERSION: "${PYTHON_VERSION}"
|
PYTHON_VERSION: "${PYTHON_VERSION}"
|
||||||
|
BASE_IMAGE: "${BASE_IMAGE}"
|
||||||
|
IMAGE_SUFFIX: "${IMAGE_SUFFIX}"
|
||||||
|
APT_PACKAGES: "${APT_PACKAGES:-}"
|
||||||
|
APT_BUILD_PACKAGES: "${APT_BUILD_PACKAGES:-}"
|
||||||
|
PIP_EXTRA_INDEX_URL: "${PIP_EXTRA_INDEX_URL:-}"
|
||||||
|
RUSTUP_INSTALL: "${RUSTUP_INSTALL:-}"
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
@ -167,7 +167,10 @@ class SettingsEOS(BaseSettings):
|
|||||||
utils: Optional[UtilsCommonSettings] = None
|
utils: Optional[UtilsCommonSettings] = None
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_nested_delimiter="__", nested_model_default_partial_update=True, env_prefix="EOS_"
|
env_nested_delimiter="__",
|
||||||
|
nested_model_default_partial_update=True,
|
||||||
|
env_prefix="EOS_",
|
||||||
|
ignored_types=(classproperty,),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -307,13 +310,11 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
|||||||
default_settings,
|
default_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def config_default_file_path(cls) -> Path:
|
def config_default_file_path(cls) -> Path:
|
||||||
"""Compute the default config file path."""
|
"""Compute the default config file path."""
|
||||||
return cls.package_root_path.joinpath("data/default.config.json")
|
return cls.package_root_path.joinpath("data/default.config.json")
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def package_root_path(cls) -> Path:
|
def package_root_path(cls) -> Path:
|
||||||
"""Compute the package root path."""
|
"""Compute the package root path."""
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from collections.abc import Callable
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from akkudoktoreos.core.logging import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
@ -5,18 +6,20 @@ from akkudoktoreos.core.logging import get_logger
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class classproperty(property):
|
class classproperty:
|
||||||
"""A decorator to define a read-only property at the class level.
|
"""A decorator to define a read-only property at the class level.
|
||||||
|
|
||||||
This class extends the built-in `property` to allow a method to be accessed
|
This class replaces the built-in `property` which is no longer available in
|
||||||
as a property on the class itself, rather than an instance. This is useful
|
combination with @classmethod since Python 3.13 to allow a method to be
|
||||||
when you want a property-like syntax for methods that depend on the class
|
accessed as a property on the class itself, rather than an instance. This
|
||||||
rather than any instance of the class.
|
is useful when you want a property-like syntax for methods that depend on
|
||||||
|
the class rather than any instance of the class.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
class MyClass:
|
class MyClass:
|
||||||
_value = 42
|
_value = 42
|
||||||
|
|
||||||
|
@classmethod
|
||||||
@classproperty
|
@classproperty
|
||||||
def value(cls):
|
def value(cls):
|
||||||
return cls._value
|
return cls._value
|
||||||
@ -28,13 +31,16 @@ class classproperty(property):
|
|||||||
decorated method on the class.
|
decorated method on the class.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
fget (Callable[[type], Any]): A method that takes the class as an
|
fget (Callable[[Any], Any]): A method that takes the class as an
|
||||||
argument and returns a value.
|
argument and returns a value.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: If `fget` is not defined when `__get__` is called.
|
AssertionError: If `fget` is not defined when `__get__` is called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fget: Callable[[Any], Any]) -> None:
|
||||||
|
self.fget = fget
|
||||||
|
|
||||||
def __get__(self, _: Any, owner_cls: Optional[type[Any]] = None) -> Any:
|
def __get__(self, _: Any, owner_cls: Optional[type[Any]] = None) -> Any:
|
||||||
if owner_cls is None:
|
if owner_cls is None:
|
||||||
return self
|
return self
|
||||||
|
Loading…
x
Reference in New Issue
Block a user