diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b528f44 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +# Ignore everything +* +# Except +!function/ +!function/** +!pyproject.toml +!README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 656bdc9..e78d0b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,14 +14,14 @@ on: env: # Common versions - PYTHON_VERSION: '3.11' - HATCH_VERSION: '1.12.0' - DOCKER_BUILDX_VERSION: 'v0.24.0' + PYTHON_VERSION: '3.13' + HATCH_VERSION: '1.15.1' + DOCKER_BUILDX_VERSION: 'v0.29.1' # These environment variables are important to the Crossplane CLI install.sh # script. They determine what version it installs. XP_CHANNEL: stable - XP_VERSION: v1.20.0 + XP_VERSION: v2.1.0 # The package to push, without a version tag. The default matches GitHub. For # example xpkg.crossplane.io/crossplane/function-template-go. Note that @@ -37,10 +37,10 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: ${{ env.PYTHON_VERSION }} @@ -54,10 +54,10 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: ${{ env.PYTHON_VERSION }} @@ -81,24 +81,24 @@ jobs: - arm64 steps: - name: Setup QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: platforms: all - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 with: version: ${{ env.DOCKER_BUILDX_VERSION }} install: true - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 # We ask Docker to use GitHub Action's native caching support to speed up # the build, per https://docs.docker.com/build/cache/backends/gha/. - name: Build Runtime id: image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 with: context: . platforms: linux/${{ matrix.arch }} @@ -116,7 +116,7 @@ jobs: run: ./crossplane xpkg build --package-file=${{ matrix.arch }}.xpkg --package-root=package/ --embed-runtime-image-tarball=runtime-${{ matrix.arch }}.tar - name: Upload Single-Platform Package - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: package-${{ matrix.arch }} path: "*.xpkg" @@ -131,10 +131,10 @@ jobs: - build steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Download Single-Platform Packages - uses: actions/download-artifact@v6 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: # See https://github.com/docker/build-push-action/blob/263435/README.md#summaries pattern: "!*.dockerbuild" @@ -145,7 +145,7 @@ jobs: run: "curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh" - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/Dockerfile b/Dockerfile index c53e71c..0a6ca86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,34 @@ # syntax=docker/dockerfile:1 -# It's important that this is Debian 12 to match the distroless image. -FROM debian:12-slim AS build +ARG UV_VERSION=0.9.7 +FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv -RUN --mount=type=cache,target=/var/lib/apt/lists \ - --mount=type=cache,target=/var/cache/apt \ - rm -f /etc/apt/apt.conf.d/docker-clean \ - && apt-get update \ - && apt-get install --no-install-recommends --yes python3-venv git +# It's important that this is Debian 12 to match the distroless image. +FROM --platform=${BUILDPLATFORM} debian:12-slim AS build # Don't write .pyc bytecode files. These speed up imports when the program is # loaded. There's no point doing that in a container where they'll never be # persisted across restarts. -ENV PYTHONDONTWRITEBYTECODE=true +ENV UV_PYTHON_INSTALL_DIR=/python PYTHONDONTWRITEBYTECODE=true +COPY --from=uv /uv /uvx /bin/ -# Use Hatch to build a wheel. The build stage must do this in a venv because -# Debian doesn't have a hatch package, and it won't let you install one globally -# using pip. -WORKDIR /build -RUN --mount=target=. \ - --mount=type=cache,target=/root/.cache/pip \ - python3 -m venv /venv/build \ - && /venv/build/bin/pip install hatch \ - && /venv/build/bin/hatch build -t wheel /whl - -# Create a fresh venv and install only the function wheel into it. -RUN --mount=type=cache,target=/root/.cache/pip \ - python3 -m venv /venv/fn \ - && /venv/fn/bin/pip install /whl/*.whl +WORKDIR /app +ADD . . +# Create a fresh venv and install the dependencies. +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --no-dev --no-cache --no-editable --python-preference=only-managed # Copy the function venv to our runtime stage. It's important that the path be -# the same as in the build stage, to avoid shebang paths and symlinks breaking. -FROM gcr.io/distroless/python3-debian12 AS image -WORKDIR / -COPY --from=build /venv/fn /venv/fn +# the same as in the build stage, to avoid shebang paths and symlinks breaking. +FROM gcr.io/distroless/cc-debian12:nonroot AS image +LABEL org.opencontainers.image.description="A Crossplane composition function template in Python" +WORKDIR /app + +# Copy python interpreter and the application from the builder +COPY --from=build --chown=python:python /python /python +COPY --from=build --chown=nonroot:nonroot /app/.venv /app/.venv +ENV PATH="/app/.venv/bin:${PATH}" EXPOSE 9443 USER nonroot:nonroot -ENTRYPOINT ["/venv/fn/bin/function"] +ENTRYPOINT ["function"] diff --git a/function/fn.py b/function/fn.py index 7cf8c25..43512f3 100644 --- a/function/fn.py +++ b/function/fn.py @@ -12,6 +12,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService): def __init__(self): """Create a new FunctionRunner.""" self.log = logging.get_logger() + self.log.info("Starting function-template-python") async def RunFunction( self, req: fnv1.RunFunctionRequest, _: grpc.aio.ServicerContext diff --git a/pyproject.toml b/pyproject.toml index 192932a..9ddb9e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,20 +6,20 @@ build-backend = "hatchling.build" name = "function" description = 'A composition function' readme = "README.md" -requires-python = ">=3.11,<3.12" +requires-python = ">=3.12,<3.14" license = "Apache-2.0" keywords = [] authors = [{ name = "Crossplane Maintainers", email = "info@crossplane.io" }] classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python", - "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ - "crossplane-function-sdk-python==0.8.0", + "crossplane-function-sdk-python==0.9.0", "click==8.3.0", - "grpcio==1.73.1", ] dynamic = ["version"] @@ -42,21 +42,34 @@ validate-bump = false # Allow going from 0.0.0.dev0+x to 0.0.0.dev0+y [tool.hatch.envs.default] type = "virtual" path = ".venv-default" -dependencies = ["ipython==9.4.0"] +dependencies = ["ipython==9.7.0"] [tool.hatch.envs.default.scripts] development = "python function/main.py --insecure --debug" -# This special environment is used by hatch fmt. -[tool.hatch.envs.hatch-static-analysis] -dependencies = ["ruff==0.12.7"] -config-path = "none" # Disable Hatch's default Ruff config. +[tool.hatch.envs.lint] +type = "virtual" +detached = true +path = ".venv-lint" +dependencies = ["ruff==0.14.3"] + +[tool.hatch.envs.lint.scripts] +check = "ruff check function tests --fix && ruff format --diff function tests" + +[tool.hatch.envs.test] +type = "virtual" +path = ".venv-test" + +[tool.hatch.envs.test.scripts] +unit = "python -m unittest tests/*.py" [tool.ruff] -target-version = "py311" +target-version = "py313" +line-length = 100 exclude = ["function/proto/*"] [tool.ruff.lint] +preview = true select = [ "A", "ARG",