Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
212 changes: 163 additions & 49 deletions .github/workflows/build-push.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
name: Build and Push

on:
# 1) Trigger on pull request (e.g., to main branch) for a simple build test
pull_request:
branches:
- main
# 2) Trigger on push to main (and optionally only when certain files change),
# plus allow manual runs
push:
branches:
- main
Expand All @@ -17,6 +14,15 @@ on:
- ".devcontainer/**"
- ".github/workflows/build-push.yaml"
workflow_dispatch:
inputs:
build_amd64:
description: "Release: push linux/amd64 (uncheck to skip). PRs always test both arches in parallel."
type: boolean
default: true
build_arm64:
description: "Release: push linux/arm64 (uncheck to skip). PRs always test both arches in parallel."
type: boolean
default: true

permissions:
contents: "read"
Expand All @@ -33,87 +39,195 @@ env:

jobs:
# ------------------------------------------------------------------------------
# JOB 1: Build Test for Pull Requests
# PR: parallel build tests (native amd64; arm64 via QEMU)
# ------------------------------------------------------------------------------
build-test:
# Only run this job if the event is a Pull Request
build-test-amd64:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build test (linux/amd64)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: false
load: true
tags: codecollection-devtools:pr-test-amd64

- name: Build Test
run: |
echo "Running a quick Docker build test..."
docker build -f Dockerfile .
echo "Build test completed."
build-test-arm64:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Build test (linux/arm64)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/arm64
push: false
tags: codecollection-devtools:pr-test-arm64

# ------------------------------------------------------------------------------
# JOB 2: Build and Push on Push to Main (or manual trigger)
# Release: shared VERSION for per-arch pushes + manifest merge
# ------------------------------------------------------------------------------
build-and-push:
# Only run this job if the event is a 'push' (incl. workflow_dispatch).
# That way PRs won't push images.
prepare-release:
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v2

- id: 'auth-runwhen'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v0.4.0'
with:
workload_identity_provider: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_PROVIDER }}
service_account: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_SA }}

- name: Set tag and version
run: |-
- uses: actions/checkout@v4
- id: version
run: |
if [[ -s VERSION ]]; then
VERSION=$(cat VERSION)
echo "VERSION=$VERSION" >> $GITHUB_ENV
VERSION=$(cat VERSION | tr -d '[:space:]')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "VERSION extracted: $VERSION"
else
echo "VERSION file is missing or empty" >&2
exit 1
fi
# Create a 'TAG' environment variable from the branch name + short SHA
echo "TAG=$(echo $GITHUB_REF_NAME | sed 's/[^a-zA-Z0-9]/-/g')-${GITHUB_SHA::8}" >> $GITHUB_ENV

build-push-amd64:
needs: [prepare-release]
if: |
(github.event_name == 'push' || github.event_name == 'workflow_dispatch') &&
(github.event_name == 'push' || github.event.inputs.build_amd64 == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- id: auth-runwhen
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v0.4.0
with:
workload_identity_provider: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_PROVIDER }}
service_account: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_SA }}
- name: Configure docker for GCP
run: gcloud --quiet auth configure-docker us-docker.pkg.dev
- name: Login to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
docker login --username ${{ github.actor }} --password-stdin ghcr.io
- name: Build & push (linux/amd64)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:${{ needs.prepare-release.outputs.version }}-amd64
ghcr.io/${{ env.GHCR_ORG }}/${{ env.IMAGE }}:${{ needs.prepare-release.outputs.version }}-amd64
build-args: |
GITHUB_SHA=${{ github.sha }}
GITHUB_REF=${{ github.ref }}

build-push-arm64:
needs: [prepare-release]
if: |
(github.event_name == 'push' || github.event_name == 'workflow_dispatch') &&
(github.event_name == 'push' || github.event.inputs.build_arm64 == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- id: auth-runwhen
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v0.4.0
with:
workload_identity_provider: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_PROVIDER }}
service_account: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_SA }}
- name: Configure docker for GCP
run: gcloud --quiet auth configure-docker us-docker.pkg.dev
- name: Login to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
docker login --username ${{ github.actor }} --password-stdin ghcr.io
- name: Build & push (linux/arm64)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
platforms: linux/arm64
push: true
tags: |
${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:${{ needs.prepare-release.outputs.version }}-arm64
ghcr.io/${{ env.GHCR_ORG }}/${{ env.IMAGE }}:${{ needs.prepare-release.outputs.version }}-arm64
build-args: |
GITHUB_SHA=${{ github.sha }}
GITHUB_REF=${{ github.ref }}

- name: Build & Push
run: |-
docker buildx create --use --name=mybuilder
docker buildx inspect --bootstrap
docker buildx build \
--push \
--platform linux/amd64,linux/arm64 \
--tag "${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:${{ env.VERSION }}" \
--tag "${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:latest" \
--tag "ghcr.io/${{ env.GHCR_ORG }}/${{ env.IMAGE }}:${{ env.VERSION }}" \
--tag "ghcr.io/${{ env.GHCR_ORG }}/${{ env.IMAGE }}:latest" \
--build-arg GITHUB_SHA="${GITHUB_SHA}" \
--build-arg GITHUB_REF="${GITHUB_REF}" \
-f Dockerfile .
merge-manifests:
needs: [prepare-release, build-push-amd64, build-push-arm64]
if: |
always() && !cancelled() &&
needs.prepare-release.result == 'success' &&
(
(github.event_name == 'push' &&
needs.build-push-amd64.result == 'success' &&
needs.build-push-arm64.result == 'success') ||
(github.event_name == 'workflow_dispatch' &&
(needs.build-push-amd64.result == 'success' || needs.build-push-arm64.result == 'success'))
)
runs-on: ubuntu-latest
steps:
- id: auth-runwhen
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v0.4.0
with:
workload_identity_provider: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_PROVIDER }}
service_account: ${{ secrets.RUNWHEN_NONPROD_SHARED_WI_SA }}
- name: Configure docker for GCP
run: gcloud --quiet auth configure-docker us-docker.pkg.dev
- name: Login to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
docker login --username ${{ github.actor }} --password-stdin ghcr.io
- uses: docker/setup-buildx-action@v3
- name: Merge multi-arch manifests (GCP + GHCR)
env:
V: ${{ needs.prepare-release.outputs.version }}
GCP_BASE: ${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}
GHCR_BASE: ghcr.io/${{ env.GHCR_ORG }}/${{ env.IMAGE }}
AMD64_OK: ${{ needs.build-push-amd64.result }}
ARM64_OK: ${{ needs.build-push-arm64.result }}
run: |
set -euo pipefail
GCP_REF=()
GHCR_REF=()
if [[ "$AMD64_OK" == "success" ]]; then
GCP_REF+=("${GCP_BASE}:${V}-amd64")
GHCR_REF+=("${GHCR_BASE}:${V}-amd64")
fi
if [[ "$ARM64_OK" == "success" ]]; then
GCP_REF+=("${GCP_BASE}:${V}-arm64")
GHCR_REF+=("${GHCR_BASE}:${V}-arm64")
fi
docker buildx imagetools create \
-t "${GCP_BASE}:${V}" -t "${GCP_BASE}:latest" \
"${GCP_REF[@]}"
docker buildx imagetools create \
-t "${GHCR_BASE}:${V}" -t "${GHCR_BASE}:latest" \
"${GHCR_REF[@]}"

- name: Notify Slack of Container Build
id: slack-publish-nonprod-shared-artifact-repo
uses: slackapi/slack-github-action@v1.19.0
with:
channel-id: "#notifications"
slack-message: "Just Pushed to ${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:${{ env.VERSION }}"
slack-message: "Just Pushed to ${{ env.SHARED_ARTIFACT_REPOSITORY_PATH }}/${{ env.IMAGE }}:${{ needs.prepare-release.outputs.version }}"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

- name: Notify Slack of GHCR Push
id: slack-deploy-to-ghcr
uses: slackapi/slack-github-action@v1.19.0
with:
channel-id: "#codecollections"
Expand Down
32 changes: 25 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,28 @@ RUN usermod -u 1000 runwhen && \
groupmod -g 1000 runwhen

###############################################################################
# Dev tools: sudo, lnav, go-task, terraform
# Dev tools: sudo, gpg/apt deps (bookworm+ has HTTPS in apt; skip apt-transport-https)
# Base image (robot-runtime-base) already ships: curl, ca-certificates, wget, unzip.
#
# Official python:3.14-slim-bookworm can ship sid (or other suites) alongside bookworm.
# Mixing sid + bookworm breaks dpkg (openssl 3.6 vs libssl3 3.0, libgcrypt, etc.).
# Drop all list.d entries and pin /etc/apt/sources.list to bookworm only.
###############################################################################
RUN apt-get update && \
apt-get install -y --no-install-recommends \
sudo apt-transport-https gnupg2 lsb-release && \
apt-get clean && \
COPY docker/apt-ci.conf /etc/apt/apt.conf.d/99docker-ci
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
rm -rf /var/lib/apt/lists/*; \
for f in /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list; do \
if [ -f "$f" ]; then rm -f "$f"; fi; \
done; \
printf '%s\n' \
'deb http://deb.debian.org/debian bookworm main' \
'deb http://deb.debian.org/debian bookworm-updates main' \
'deb http://deb.debian.org/debian-security bookworm-security main' \
> /etc/apt/sources.list; \
apt-get update; \
apt-get install -y --no-install-recommends gnupg lsb-release sudo; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*

RUN echo "runwhen ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
Expand Down Expand Up @@ -154,7 +170,9 @@ ENV USER="runwhen"
RUN pip install --user --no-cache-dir -r requirements.txt

ENV PATH="${PATH}:/usr/local/bin:${RUNWHEN_HOME}/.local/bin:${RUNWHEN_HOME}"
ENV PYTHONPATH="$PYTHONPATH:.:${RUNWHEN_HOME}/codecollection/libraries:${RUNWHEN_HOME}/codecollection/codebundles"
# Do not reference $PYTHONPATH here — it is often unset in the base image (BuildKit UndefinedVar).
ENV PYTHONPATH=".:${RUNWHEN_HOME}/codecollection/libraries:${RUNWHEN_HOME}/codecollection/codebundles"

EXPOSE 3000
CMD python -m http.server --bind 0.0.0.0 --directory $ROBOT_LOG_DIR 3000
# JSON form for clean signals; use literal log dir (same as ENV ROBOT_LOG_DIR).
CMD ["python", "-m", "http.server", "--bind", "0.0.0.0", "--directory", "/robot_logs", "3000"]
6 changes: 6 additions & 0 deletions docker/apt-ci.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Acquire::http::Pipeline-Depth "0";
Acquire::https::Pipeline-Depth "0";
Acquire::Retries "5";
Acquire::http::Timeout "120";
Acquire::https::Timeout "120";
Acquire::Check-Valid-Until "false";
Loading