From 22e09fe386c5461d6ade068af5b4f9b532a165db Mon Sep 17 00:00:00 2001 From: github-copilot Date: Wed, 3 Jun 2026 15:21:41 +0000 Subject: [PATCH 1/5] chore: add deployment (Docker, compose, k8s) and CI workflow for bookkeeper app --- samples/book-app-project/Dockerfile | 12 ++++++ samples/book-app-project/README.deploy.md | 27 +++++++++++++ samples/book-app-project/docker-compose.yml | 11 ++++++ samples/book-app-project/k8s/README.md | 26 +++++++++++++ .../book-app-project/k8s/configmap-data.yaml | 38 +++++++++++++++++++ samples/book-app-project/k8s/job-list.yaml | 27 +++++++++++++ 6 files changed, 141 insertions(+) create mode 100644 samples/book-app-project/Dockerfile create mode 100644 samples/book-app-project/README.deploy.md create mode 100644 samples/book-app-project/docker-compose.yml create mode 100644 samples/book-app-project/k8s/README.md create mode 100644 samples/book-app-project/k8s/configmap-data.yaml create mode 100644 samples/book-app-project/k8s/job-list.yaml diff --git a/samples/book-app-project/Dockerfile b/samples/book-app-project/Dockerfile new file mode 100644 index 00000000..b008ae7f --- /dev/null +++ b/samples/book-app-project/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.10-slim + +WORKDIR /app + +# Install test tool (no runtime dependencies required for the app itself) +RUN pip install --no-cache-dir pytest + +# Copy application files +COPY . /app + +# Default to showing help when container runs without args +CMD ["python", "book_app.py", "help"] diff --git a/samples/book-app-project/README.deploy.md b/samples/book-app-project/README.deploy.md new file mode 100644 index 00000000..d2ba8534 --- /dev/null +++ b/samples/book-app-project/README.deploy.md @@ -0,0 +1,27 @@ +Deployment instructions for the sample book app + +Build locally with Docker: + +```bash +cd samples/book-app-project +docker build -t bookkeeper-app:local . +``` + +Run the container (interactive CLI): + +```bash +docker run --rm -it -v "$(pwd)/data.json:/app/data.json" bookkeeper-app:local python book_app.py list +``` + +Or use docker-compose for an interactive shell session: + +```bash +cd samples/book-app-project +docker-compose run --rm bookkeeper-app list +``` + +Run tests inside the image: + +```bash +docker run --rm bookkeeper-app:local pytest -q +``` diff --git a/samples/book-app-project/docker-compose.yml b/samples/book-app-project/docker-compose.yml new file mode 100644 index 00000000..50fd12a3 --- /dev/null +++ b/samples/book-app-project/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.8" + +services: + bookkeeper-app: + build: . + image: bookkeeper-app:local + volumes: + - ./data.json:/app/data.json + stdin_open: true + tty: true + entrypoint: ["python", "book_app.py"] diff --git a/samples/book-app-project/k8s/README.md b/samples/book-app-project/k8s/README.md new file mode 100644 index 00000000..bb3bd886 --- /dev/null +++ b/samples/book-app-project/k8s/README.md @@ -0,0 +1,26 @@ +Kubernetes deployment for the Bookkeeper sample + +This folder contains a `ConfigMap` with `data.json` and a `Job` manifest that runs the CLI command `python book_app.py list`. + +Usage (build and push image to a registry first): + +```bash +# Build and tag +cd samples/book-app-project +docker build -t ghcr.io//bookkeeper-app:latest . + +# Push (example: GitHub Container Registry) +docker push ghcr.io//bookkeeper-app:latest + +# Apply k8s resources +kubectl apply -f k8s/configmap-data.yaml +kubectl apply -f k8s/job-list.yaml + +# Watch job +kubectl get jobs +kubectl logs job/bookkeeper-list-job +``` + +Notes: +- The sample app is a CLI, not a long-running server. A `Job` is used to execute a one-time command. +- If you use a local cluster (kind/minikube) you may need to load the local image into the cluster instead of pushing to a registry. diff --git a/samples/book-app-project/k8s/configmap-data.yaml b/samples/book-app-project/k8s/configmap-data.yaml new file mode 100644 index 00000000..62dc6cfe --- /dev/null +++ b/samples/book-app-project/k8s/configmap-data.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: book-data +data: + data.json: |- + [ + { + "title": "The Hobbit", + "author": "J.R.R. Tolkien", + "year": 1937, + "read": false + }, + { + "title": "1984", + "author": "George Orwell", + "year": 1949, + "read": true + }, + { + "title": "Dune", + "author": "Frank Herbert", + "year": 1965, + "read": false + }, + { + "title": "To Kill a Mockingbird", + "author": "Harper Lee", + "year": 1960, + "read": false + }, + { + "title": "Mysterious Book", + "author": "", + "year": 0, + "read": false + } + ] diff --git a/samples/book-app-project/k8s/job-list.yaml b/samples/book-app-project/k8s/job-list.yaml new file mode 100644 index 00000000..95b2ca10 --- /dev/null +++ b/samples/book-app-project/k8s/job-list.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: bookkeeper-list-job + labels: + app: bookkeeper +spec: + template: + metadata: + labels: + app: bookkeeper + spec: + containers: + - name: bookkeeper + image: bookkeeper-app:latest + imagePullPolicy: IfNotPresent + command: ["python", "book_app.py", "list"] + volumeMounts: + - name: data-json + mountPath: /app/data.json + subPath: data.json + restartPolicy: Never + volumes: + - name: data-json + configMap: + name: book-data + backoffLimit: 2 From 3591952e9fc5ec672a723f5556e7a06aa6290f22 Mon Sep 17 00:00:00 2001 From: github-copilot Date: Wed, 3 Jun 2026 15:34:34 +0000 Subject: [PATCH 2/5] chore: add deployment CI workflow --- .github/workflows/ci-build-deploy.yml | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/ci-build-deploy.yml diff --git a/.github/workflows/ci-build-deploy.yml b/.github/workflows/ci-build-deploy.yml new file mode 100644 index 00000000..d531b633 --- /dev/null +++ b/.github/workflows/ci-build-deploy.yml @@ -0,0 +1,72 @@ +name: CI — Test, Build, Push, Deploy + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install test deps + run: python -m pip install --upgrade pip pytest + - name: Run tests + run: python -m pytest -q samples/book-app-project/tests + + build-and-push: + needs: test + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Log in to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push image + uses: docker/build-push-action@v4 + with: + context: samples/book-app-project + file: samples/book-app-project/Dockerfile + push: true + tags: ghcr.io/${{ github.repository_owner }}/bookkeeper-app:latest + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + if: ${{ secrets.KUBE_CONFIG != '' }} + steps: + - uses: actions/checkout@v4 + - name: Install kubectl + uses: azure/setup-kubectl@v3 + with: + version: 'latest' + - name: Patch Job image to GHCR + run: | + sed -i "s|image: bookkeeper-app:latest|image: ghcr.io/${{ github.repository_owner }}/bookkeeper-app:latest|" samples/book-app-project/k8s/job-list.yaml + - name: Configure kubeconfig + run: echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig + - name: Apply k8s manifests + run: | + kubectl --kubeconfig=kubeconfig apply -f samples/book-app-project/k8s/configmap-data.yaml + kubectl --kubeconfig=kubeconfig apply -f samples/book-app-project/k8s/job-list.yaml + - name: Wait for job completion + run: kubectl --kubeconfig=kubeconfig wait --for=condition=complete job/bookkeeper-list-job --timeout=120s || true + - name: Fetch job logs + run: kubectl --kubeconfig=kubeconfig logs job/bookkeeper-list-job || true From 73ee143187b49b41de1811740d42a53cdaa177e3 Mon Sep 17 00:00:00 2001 From: github-copilot Date: Wed, 3 Jun 2026 15:39:21 +0000 Subject: [PATCH 3/5] chore: make CI build/push and deploy run only on main pushes --- .github/workflows/ci-build-deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build-deploy.yml b/.github/workflows/ci-build-deploy.yml index d531b633..356eab08 100644 --- a/.github/workflows/ci-build-deploy.yml +++ b/.github/workflows/ci-build-deploy.yml @@ -21,6 +21,7 @@ jobs: run: python -m pytest -q samples/book-app-project/tests build-and-push: + if: github.event_name == 'push' needs: test runs-on: ubuntu-latest permissions: @@ -50,7 +51,7 @@ jobs: deploy: needs: build-and-push runs-on: ubuntu-latest - if: ${{ secrets.KUBE_CONFIG != '' }} + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/main') && secrets.KUBE_CONFIG != '' }} steps: - uses: actions/checkout@v4 - name: Install kubectl From 8121f60fb2670a5824096e19596f5132b6c8f8a2 Mon Sep 17 00:00:00 2001 From: github-copilot Date: Wed, 3 Jun 2026 15:41:58 +0000 Subject: [PATCH 4/5] chore: add PR preview image build workflow --- .github/workflows/pr-preview-build.yml | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/pr-preview-build.yml diff --git a/.github/workflows/pr-preview-build.yml b/.github/workflows/pr-preview-build.yml new file mode 100644 index 00000000..0c96a240 --- /dev/null +++ b/.github/workflows/pr-preview-build.yml @@ -0,0 +1,33 @@ +name: PR Preview Build + +on: + pull_request: + branches: [ main ] + +jobs: + build-preview-image: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install test deps + run: python -m pip install --upgrade pip pytest + - name: Run tests + run: python -m pytest -q samples/book-app-project/tests + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build Docker preview image + uses: docker/build-push-action@v4 + with: + context: samples/book-app-project + file: samples/book-app-project/Dockerfile + load: true + push: false + tags: bookkeeper-app:pr-${{ github.event.pull_request.number }} From b0c17f493dca0eacbd3f86ac47313f4573184826 Mon Sep 17 00:00:00 2001 From: github-copilot Date: Wed, 3 Jun 2026 15:53:49 +0000 Subject: [PATCH 5/5] fix: avoid secrets check in job-level deploy condition for PRs --- .github/workflows/ci-build-deploy.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build-deploy.yml b/.github/workflows/ci-build-deploy.yml index 356eab08..51606dcb 100644 --- a/.github/workflows/ci-build-deploy.yml +++ b/.github/workflows/ci-build-deploy.yml @@ -51,23 +51,35 @@ jobs: deploy: needs: build-and-push runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/main') && secrets.KUBE_CONFIG != '' }} + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/main') }} steps: - uses: actions/checkout@v4 + - name: Check KUBE_CONFIG secret + run: | + if [ -z "${{ secrets.KUBE_CONFIG }}" ]; then + echo "KUBE_CONFIG secret is not configured; skipping deploy." + exit 0 + fi - name: Install kubectl + if: ${{ secrets.KUBE_CONFIG != '' }} uses: azure/setup-kubectl@v3 with: version: 'latest' - name: Patch Job image to GHCR + if: ${{ secrets.KUBE_CONFIG != '' }} run: | sed -i "s|image: bookkeeper-app:latest|image: ghcr.io/${{ github.repository_owner }}/bookkeeper-app:latest|" samples/book-app-project/k8s/job-list.yaml - name: Configure kubeconfig + if: ${{ secrets.KUBE_CONFIG != '' }} run: echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig - name: Apply k8s manifests + if: ${{ secrets.KUBE_CONFIG != '' }} run: | kubectl --kubeconfig=kubeconfig apply -f samples/book-app-project/k8s/configmap-data.yaml kubectl --kubeconfig=kubeconfig apply -f samples/book-app-project/k8s/job-list.yaml - name: Wait for job completion + if: ${{ secrets.KUBE_CONFIG != '' }} run: kubectl --kubeconfig=kubeconfig wait --for=condition=complete job/bookkeeper-list-job --timeout=120s || true - name: Fetch job logs + if: ${{ secrets.KUBE_CONFIG != '' }} run: kubectl --kubeconfig=kubeconfig logs job/bookkeeper-list-job || true