diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 0000000..78efa71 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,58 @@ +name: Deployment + +on: + workflow_dispatch: + workflow_run: + workflows: ["Integration"] + branches: [main] + types: + - completed + +jobs: + release: + runs-on: ubuntu-24.04-arm + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + concurrency: + group: ${{ github.workflow }}-release-${{ github.ref }} + cancel-in-progress: true + + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Authenticate at registry + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Extract metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,prefix=,format=short + type=raw,value=latest + + - name: Build and push container + id: build-push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + push: true + platforms: linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + sbom: true + provenance: true + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..08c0951 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,53 @@ +name: Integration + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup uv + uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Setup Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: gen/go/go.mod + cache-dependency-path: gen/go/go.sum + + - name: Install dependencies + run: uv sync --locked + + - name: Lint and format + run: | + uv run --locked ruff check + uv run --locked ruff format --check + + - name: Check generated bindings + run: | + uv run --locked python scripts/generate.py --check + uv run --locked python scripts/generate.py --go --check + + - name: Test Python service + run: uv run --locked pytest + + - name: Test Go client against Python + run: uv run --locked python scripts/test_go.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e58b7b4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM cgr.dev/chainguard/python:latest-dev@sha256:261ceae8cf0ee5055341cd5c417984a70eb0e1406f2ddf83a4c93002bb10c26c AS build +USER root +WORKDIR /app +COPY pyproject.toml uv.lock ./ +COPY src ./src +RUN uv sync --locked --no-dev --no-editable --python /usr/bin/python + +FROM cgr.dev/chainguard/python:latest@sha256:89281daac77a3d91ef298d70ce3b7a6ccb2ebf268c084fa9a9bda1c92e71c64d +WORKDIR /app +COPY --from=build /app/.venv /app/.venv +ENV PYTHONUNBUFFERED=1 +EXPOSE 50051 +HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \ + CMD ["/app/.venv/bin/python", "-m", "optimizer.health"] +ENTRYPOINT ["/app/.venv/bin/python", "-m", "optimizer.main"] diff --git a/gen/go/go.mod b/gen/go/go.mod index c396470..825f036 100644 --- a/gen/go/go.mod +++ b/gen/go/go.mod @@ -1,6 +1,6 @@ module github.com/fanscore-ch/optimizer/gen/go -go 1.25.0 +go 1.26.4 require ( google.golang.org/grpc v1.84.0 diff --git a/tests/test_proto.py b/tests/test_proto.py index 0192ece..ac153f3 100644 --- a/tests/test_proto.py +++ b/tests/test_proto.py @@ -1,7 +1,8 @@ +import subprocess +import sys from pathlib import Path from google.protobuf import descriptor_pb2 -from grpc_tools import protoc from ortools.sat import cp_model_pb2, sat_parameters_pb2 @@ -21,17 +22,18 @@ def normalize_descriptor(message): def test_vendored_protos_match_runtime(tmp_path): root = Path(__file__).resolve().parents[1] output = tmp_path / "descriptor.pb" - assert ( - protoc.main( - [ - "protoc", - f"-I{root / 'proto'}", - f"--descriptor_set_out={output}", - "ortools/sat/cp_model.proto", - "ortools/sat/sat_parameters.proto", - ] - ) - == 0 + # Isolate protoc from OR-Tools' native protobuf library to avoid Linux crashes. + subprocess.run( + [ + sys.executable, + "-m", + "grpc_tools.protoc", + f"-I{root / 'proto'}", + f"--descriptor_set_out={output}", + "ortools/sat/cp_model.proto", + "ortools/sat/sat_parameters.proto", + ], + check=True, ) descriptors = descriptor_pb2.FileDescriptorSet.FromString(output.read_bytes()) vendored = {file.name: file for file in descriptors.file}