33 Commits

Author SHA1 Message Date
7066e237db chore: remove gitea workflow mirror 2026-03-24 14:29:48 +01:00
1410f4ff43 chore: add gitea workflow mirror for node ci 2026-03-24 14:28:33 +01:00
ebccfaa6fc feat: add install and build commands to node ci workflow 2026-03-24 14:26:35 +01:00
0cc1e2fb62 fix: install pnpm before setup-node 2026-03-23 15:52:20 +01:00
a347242a24 Fix pnpm cache setup for Node CI 2026-03-23 13:52:05 +01:00
estebanthi
583ae62c3b validate images 2026-01-18 11:25:34 +01:00
estebanthi
c5563d9dc1 Split python UV workflow with DB service 2026-01-05 14:43:07 +01:00
estebanthi
4ebffa8c94 Normalize dockerfile paths for build and bake 2026-01-05 13:41:20 +01:00
estebanthi
4f00812999 Allow ssh entitlement for bake 2026-01-05 13:02:11 +01:00
estebanthi
91ab1aaac0 Fix ssh usage for bake builds 2026-01-05 12:56:52 +01:00
estebanthi
65e2336237 Cleanup leftover postgres containers 2026-01-05 12:07:21 +01:00
estebanthi
4170b17ea6 Use unique db container and cleanup 2026-01-05 11:56:40 +01:00
estebanthi
133731d493 Improve db readiness and build args parsing 2026-01-05 11:47:40 +01:00
estebanthi
bfd6822394 Add optional database for tests 2026-01-05 11:34:51 +01:00
estebanthi
012da683f6 pre-commit workflow 2026-01-05 09:12:57 +01:00
estebanthi
50e0f562a3 Fix bake jq conditionals 2026-01-04 18:47:44 +01:00
estebanthi
cbb6163cc2 Optimize docker build workflow 2026-01-04 18:42:08 +01:00
estebanthi
982f09e161 Add env input to workflows 2026-01-04 17:46:06 +01:00
estebanthi
757fc65c40 Support env input as KEY=VALUE lines 2026-01-04 17:10:00 +01:00
estebanthi
6bf910dff1 Make docker build target optional 2026-01-04 17:03:17 +01:00
estebanthi
c001f91b5e Add SSH support to reusable workflows 2026-01-04 16:50:30 +01:00
estebanthi
74bd39fcd3 Add python uv ci workflow 2026-01-04 16:07:16 +01:00
estebanthi
f481260d7e Expand node ci workflow steps 2026-01-04 15:44:00 +01:00
estebanthi
bb980c9263 node ci 2026-01-04 13:16:54 +01:00
estebanthi
7bfe90d49b Add latest tag on release builds 2026-01-04 13:05:07 +01:00
estebanthi
c3aa42a612 Fix docker build workflow tagging and scanning 2026-01-04 13:01:56 +01:00
estebanthi
81a34e348c updated wf 2026-01-04 12:54:04 +01:00
estebanthi
1440e4796c tags 2026-01-04 12:50:37 +01:00
estebanthi
a8c9119010 wf 2026-01-04 12:48:40 +01:00
estebanthi
1053d1271c wf 2026-01-04 12:41:57 +01:00
estebanthi
0c21f9b27b updated gh 2026-01-04 12:32:25 +01:00
estebanthi
c22cb9a8b0 updated wf 2026-01-04 12:30:57 +01:00
estebanthi
901e2110e1 updated workflow location 2026-01-04 12:29:00 +01:00
7 changed files with 808 additions and 204 deletions

View File

@@ -0,0 +1,314 @@
name: Docker Build, Scan & Publish
on:
workflow_call:
inputs:
images:
description: >
JSON array of images to build.
Each item: { name, context, dockerfile, target, cache_ref }
required: true
type: string
registry_host:
required: true
type: string
build_args:
required: false
type: string
default: ""
description: >
Multiline build args, one per line: KEY=VALUE (values may include spaces)
env:
description: >
Multiline env vars, one per line: KEY=VALUE
required: false
type: string
default: ""
trivy_severity:
required: false
type: string
default: "CRITICAL"
secrets:
registry_user:
required: true
registry_password:
required: true
ci_token:
required: true
ssh_private_key:
required: false
ssh_known_hosts:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Load env vars
if: ${{ inputs.env != '' }}
run: |
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in \#*) continue;; esac
if [[ "$line" != *=* ]]; then
echo "Invalid env line: $line" >&2
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done <<< "${{ inputs.env }}"
- name: Start ssh-agent
if: ${{ secrets.ssh_private_key != '' }}
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.ssh_private_key }}
- name: Add SSH known hosts
if: ${{ secrets.ssh_known_hosts != '' }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.ssh_known_hosts }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install Trivy
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin
trivy --version
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry_host }}
username: ${{ secrets.registry_user }}
password: ${{ secrets.registry_password }}
- name: Build, scan and push images
env:
IMAGES: ${{ inputs.images }}
BUILD_ARGS: ${{ inputs.build_args }}
CI_TOKEN: ${{ secrets.ci_token }}
TRIVY_SEVERITY: ${{ inputs.trivy_severity }}
run: |
set -euo pipefail
if ! echo "$IMAGES" | jq -e . >/dev/null; then
echo "inputs.images must be valid JSON" >&2
exit 1
fi
if ! echo "$IMAGES" | jq -e 'type == "array"' >/dev/null; then
echo "inputs.images must be a JSON array" >&2
exit 1
fi
SSH_FLAGS=()
if [ -n "${SSH_AUTH_SOCK:-}" ]; then
SSH_FLAGS+=(--ssh default)
fi
SSH_BAKE_JSON="null"
if [ -n "${SSH_AUTH_SOCK:-}" ]; then
SSH_BAKE_JSON='["default"]'
fi
BAKE_ALLOW_FLAGS=()
if [ -n "${SSH_AUTH_SOCK:-}" ]; then
BAKE_ALLOW_FLAGS+=(--allow=ssh)
fi
RAW_REF="${{ github.ref }}"
SHA_FULL="${{ github.sha }}"
SHA_SHORT="${SHA_FULL:0:12}"
VERSION_TAGS=()
if [[ "$RAW_REF" =~ ^refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
VERSION_TAGS+=("v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}")
VERSION_TAGS+=("v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}")
VERSION_TAGS+=("v${BASH_REMATCH[1]}")
VERSION_TAGS+=("latest")
fi
BUILD_ARG_FLAGS=()
BUILD_ARGS_JSON="{}"
if [ -n "$BUILD_ARGS" ]; then
while IFS= read -r line; do
trimmed="${line#"${line%%[![:space:]]*}"}"
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
[ -z "$trimmed" ] && continue
case "$trimmed" in \#*) continue;; esac
if [[ "$trimmed" != *=* ]]; then
echo "Invalid build arg: $trimmed" >&2
exit 1
fi
BUILD_ARG_FLAGS+=(--build-arg "$trimmed")
key="${trimmed%%=*}"
val="${trimmed#*=}"
BUILD_ARGS_JSON=$(jq --arg k "$key" --arg v "$val" '. + {($k): $v}' <<<"$BUILD_ARGS_JSON")
done <<< "$BUILD_ARGS"
fi
normalize_path() {
local p="$1"
while [[ "$p" == ./* ]]; do
p="${p#./}"
done
if [ "$p" != "/" ]; then
p="${p%/}"
fi
printf '%s' "$p"
}
while read -r group; do
GROUP_COUNT=$(echo "$group" | jq 'length')
CONTEXT=$(echo "$group" | jq -r '.[0].context')
DOCKERFILE=$(echo "$group" | jq -r '.[0].dockerfile')
context_norm="$(normalize_path "$CONTEXT")"
dockerfile_norm="$(normalize_path "$DOCKERFILE")"
DOCKERFILE_FOR_BAKE="$DOCKERFILE"
DOCKERFILE_FOR_BUILD="$DOCKERFILE"
if [ -n "$context_norm" ] && [ "$context_norm" != "." ]; then
if [[ "$dockerfile_norm" == "$context_norm/"* ]]; then
DOCKERFILE_FOR_BAKE="${dockerfile_norm#$context_norm/}"
DOCKERFILE_FOR_BUILD="$dockerfile_norm"
elif [[ "$dockerfile_norm" != /* ]]; then
DOCKERFILE_FOR_BUILD="${context_norm}/${dockerfile_norm}"
fi
fi
if [ "$GROUP_COUNT" -gt 1 ]; then
echo "==== Building $GROUP_COUNT images from $DOCKERFILE (bake) ===="
CACHE_REF_SUFFIXES=$(echo "$group" | jq -c 'map(.cache_ref // empty) | map(select(length > 0)) | unique')
CACHE_REF_SUFFIX=$(echo "$CACHE_REF_SUFFIXES" | jq -r '.[0] // empty')
UNIQUE_CACHE_COUNT=$(echo "$CACHE_REF_SUFFIXES" | jq -r 'length')
if [ "$UNIQUE_CACHE_COUNT" -gt 1 ]; then
echo "Warning: multiple cache_ref values for the same dockerfile. Using $CACHE_REF_SUFFIX." >&2
fi
CACHE_REF=""
if [ -n "$CACHE_REF_SUFFIX" ]; then
CACHE_REF="${{ inputs.registry_host }}/$CACHE_REF_SUFFIX"
fi
TARGETS_JSON="{}"
TARGET_NAMES=()
while read -r entry; do
IDX=$(echo "$entry" | jq -r '.key')
IMG=$(echo "$entry" | jq -c '.value')
IMAGE_NAME=$(echo "$IMG" | jq -r '.name')
TARGET=$(echo "$IMG" | jq -r '.target // empty')
FULL_IMAGE="${{ inputs.registry_host }}/${IMAGE_NAME}"
TAGS=()
TAGS+=("$FULL_IMAGE:sha-$SHA_SHORT")
for ver in "${VERSION_TAGS[@]}"; do
TAGS+=("$FULL_IMAGE:$ver")
done
TAGS_JSON=$(printf '%s\n' "${TAGS[@]}" | jq -R . | jq -s .)
TARGET_NAME="img_${IDX}"
TARGET_NAMES+=("$TARGET_NAME")
TARGET_OBJ=$(jq -n \
--arg context "$CONTEXT" \
--arg dockerfile "$DOCKERFILE_FOR_BAKE" \
--arg target "$TARGET" \
--argjson tags "$TAGS_JSON" \
--argjson args "$BUILD_ARGS_JSON" \
--argjson ssh "$SSH_BAKE_JSON" \
--arg cache_ref "$CACHE_REF" \
'{
context: $context,
dockerfile: $dockerfile,
tags: $tags,
args: $args
}
+ (if ($ssh != null) then {ssh: $ssh} else {} end)
+ (if ($target != "" and $target != "null") then {target: $target} else {} end)
+ (if ($cache_ref != "") then {"cache-from": ["type=registry,ref=" + $cache_ref], "cache-to": ["type=registry,ref=" + $cache_ref + ",mode=max"]} else {} end)')
TARGETS_JSON=$(jq -n --arg name "$TARGET_NAME" --argjson target "$TARGET_OBJ" --argjson targets "$TARGETS_JSON" '$targets + {($name): $target}')
done < <(echo "$group" | jq -c 'to_entries[]')
GROUP_TARGETS_JSON=$(printf '%s\n' "${TARGET_NAMES[@]}" | jq -R . | jq -s .)
BAKE_JSON=$(jq -n --argjson targets "$TARGETS_JSON" --argjson group_targets "$GROUP_TARGETS_JSON" '{target:$targets, group:{default:{targets:$group_targets}}}')
BAKE_FILE=$(mktemp)
echo "$BAKE_JSON" > "$BAKE_FILE"
docker buildx bake --file "$BAKE_FILE" --push "${BAKE_ALLOW_FLAGS[@]}"
rm -f "$BAKE_FILE"
while read -r img; do
IMAGE_NAME=$(echo "$img" | jq -r '.name')
FULL_IMAGE="${{ inputs.registry_host }}/${IMAGE_NAME}"
echo "==== Trivy scan for $FULL_IMAGE ===="
trivy image \
--severity "$TRIVY_SEVERITY" \
--exit-code 1 \
"$FULL_IMAGE:sha-$SHA_SHORT"
done < <(echo "$group" | jq -c '.[]')
else
img=$(echo "$group" | jq -c '.[0]')
IMAGE_NAME=$(echo "$img" | jq -r '.name')
FULL_IMAGE="${{ inputs.registry_host }}/${IMAGE_NAME}"
CACHE_REF_SUFFIX=$(echo "$img" | jq -r '.cache_ref // empty')
CONTEXT=$(echo "$img" | jq -r '.context')
DOCKERFILE=$(echo "$img" | jq -r '.dockerfile')
TARGET=$(echo "$img" | jq -r '.target // empty')
TAGS=()
TAGS+=("$FULL_IMAGE:sha-$SHA_SHORT")
for ver in "${VERSION_TAGS[@]}"; do
TAGS+=("$FULL_IMAGE:$ver")
done
TAG_ARGS=()
for tag in "${TAGS[@]}"; do
TAG_ARGS+=(--tag "$tag")
done
TARGET_FLAGS=()
if [ -n "$TARGET" ] && [ "$TARGET" != "null" ]; then
TARGET_FLAGS+=(--target "$TARGET")
fi
CACHE_FLAGS=()
if [ -n "$CACHE_REF_SUFFIX" ]; then
CACHE_REF="${{ inputs.registry_host }}/${CACHE_REF_SUFFIX}"
CACHE_FLAGS+=(--cache-from "type=registry,ref=$CACHE_REF")
CACHE_FLAGS+=(--cache-to "type=registry,ref=$CACHE_REF,mode=max")
fi
echo "==== Building $FULL_IMAGE ===="
docker buildx build \
--file "$DOCKERFILE_FOR_BUILD" \
"${TARGET_FLAGS[@]}" \
"${CACHE_FLAGS[@]}" \
"${SSH_FLAGS[@]}" \
--push \
"${TAG_ARGS[@]}" \
"${BUILD_ARG_FLAGS[@]}" \
"$CONTEXT"
echo "==== Trivy scan for $FULL_IMAGE ===="
trivy image \
--severity "$TRIVY_SEVERITY" \
--exit-code 1 \
"${TAGS[0]}"
fi
done < <(echo "$IMAGES" | jq -c 'sort_by(.context, .dockerfile) | group_by([.context, .dockerfile])[]')

135
.github/workflows/node-ci.yml vendored Normal file
View File

@@ -0,0 +1,135 @@
name: Node CI
on:
workflow_call:
inputs:
node_version:
type: string
default: "20"
pnpm_version:
type: string
default: "10.23.0"
working_directory:
type: string
default: "."
cache_dependency_path:
type: string
default: "pnpm-lock.yaml"
env:
description: >
Multiline env vars, one per line: KEY=VALUE
required: false
type: string
default: ""
install_command:
type: string
default: "pnpm install --frozen-lockfile"
format_command:
type: string
default: "pnpm format:check"
lint_command:
type: string
default: "pnpm lint"
typecheck_command:
type: string
default: "pnpm typecheck"
test_command:
type: string
default: "pnpm test"
build_command:
type: string
default: ""
secrets:
ssh_private_key:
required: false
ssh_known_hosts:
required: false
jobs:
quality:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Load env vars
if: ${{ inputs.env != '' }}
run: |
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in \#*) continue;; esac
if [[ "$line" != *=* ]]; then
echo "Invalid env line: $line" >&2
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done <<< "${{ inputs.env }}"
- name: Start ssh-agent
if: ${{ secrets.ssh_private_key != '' }}
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.ssh_private_key }}
- name: Add SSH known hosts
if: ${{ secrets.ssh_known_hosts != '' }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.ssh_known_hosts }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm_version }}
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
- name: Get pnpm store path
id: pnpm-store
run: echo "store_path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
working-directory: ${{ inputs.working_directory }}
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.store_path }}
key: ${{ runner.os }}-pnpm-${{ inputs.pnpm_version }}-${{ hashFiles(inputs.cache_dependency_path) }}
restore-keys: |
${{ runner.os }}-pnpm-${{ inputs.pnpm_version }}-
${{ runner.os }}-pnpm-
- name: Install dependencies
if: ${{ inputs.install_command != '' }}
run: ${{ inputs.install_command }}
working-directory: ${{ inputs.working_directory }}
- name: Run format check
if: ${{ inputs.format_command != '' }}
run: ${{ inputs.format_command }}
working-directory: ${{ inputs.working_directory }}
- name: Run lint
if: ${{ inputs.lint_command != '' }}
run: ${{ inputs.lint_command }}
working-directory: ${{ inputs.working_directory }}
- name: Run typecheck
if: ${{ inputs.typecheck_command != '' }}
run: ${{ inputs.typecheck_command }}
working-directory: ${{ inputs.working_directory }}
- name: Run tests
if: ${{ inputs.test_command != '' }}
run: ${{ inputs.test_command }}
working-directory: ${{ inputs.working_directory }}
- name: Run build
if: ${{ inputs.build_command != '' }}
run: ${{ inputs.build_command }}
working-directory: ${{ inputs.working_directory }}

97
.github/workflows/pre-commit.yml vendored Normal file
View File

@@ -0,0 +1,97 @@
name: Pre-commit
on:
workflow_call:
inputs:
python_version:
type: string
default: "3.13"
pre_commit_version:
type: string
default: "latest"
working_directory:
type: string
default: "."
cache_dependency_path:
type: string
default: ".pre-commit-config.yaml"
env:
description: >
Multiline env vars, one per line: KEY=VALUE
required: false
type: string
default: ""
pre_commit_command:
type: string
default: "pre-commit run --all-files --show-diff-on-failure"
secrets:
ssh_private_key:
required: false
ssh_known_hosts:
required: false
jobs:
pre-commit:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: ${{ inputs.working_directory }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Load env vars
if: ${{ inputs.env != '' }}
run: |
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in \#*) continue;; esac
if [[ "$line" != *=* ]]; then
echo "Invalid env line: $line" >&2
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done <<< "${{ inputs.env }}"
- name: Start ssh-agent
if: ${{ secrets.ssh_private_key != '' }}
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.ssh_private_key }}
- name: Add SSH known hosts
if: ${{ secrets.ssh_known_hosts != '' }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.ssh_known_hosts }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
- name: Install pre-commit
env:
PRE_COMMIT_VERSION: ${{ inputs.pre_commit_version }}
run: |
python -m pip install --upgrade pip
if [ -z "$PRE_COMMIT_VERSION" ] || [ "$PRE_COMMIT_VERSION" = "latest" ]; then
python -m pip install pre-commit
else
python -m pip install "pre-commit==$PRE_COMMIT_VERSION"
fi
- name: Cache pre-commit
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles(inputs.cache_dependency_path) }}
restore-keys: |
pre-commit-${{ runner.os }}-
- name: Run pre-commit
if: ${{ inputs.pre_commit_command != '' }}
run: ${{ inputs.pre_commit_command }}

View File

@@ -0,0 +1,138 @@
name: Python UV Quality (with DB)
on:
workflow_call:
inputs:
python_version:
type: string
default: "3.13"
uv_version:
type: string
default: "latest"
working_directory:
type: string
default: "."
cache_dependency_path:
type: string
default: "uv.lock"
env:
description: >
Multiline env vars, one per line: KEY=VALUE
required: false
type: string
default: ""
uv_sync_args:
type: string
default: "--frozen --dev"
format_command:
type: string
default: "uv run ruff format --check ."
lint_command:
type: string
default: "uv run ruff check ."
typecheck_command:
type: string
default: "uv run mypy ."
test_command:
type: string
default: "uv run pytest"
secrets:
ssh_private_key:
required: false
ssh_known_hosts:
required: false
jobs:
quality:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=10s
--health-timeout=5s
--health-retries=5
permissions:
contents: read
defaults:
run:
working-directory: ${{ inputs.working_directory }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Load env vars
if: ${{ inputs.env != '' }}
run: |
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in \#*) continue;; esac
if [[ "$line" != *=* ]]; then
echo "Invalid env line: $line" >&2
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done <<< "${{ inputs.env }}"
- name: Start ssh-agent
if: ${{ secrets.ssh_private_key != '' }}
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.ssh_private_key }}
- name: Add SSH known hosts
if: ${{ secrets.ssh_known_hosts != '' }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.ssh_known_hosts }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
- name: Install uv
env:
UV_VERSION: ${{ inputs.uv_version }}
run: |
python -m pip install --upgrade pip
if [ -z "$UV_VERSION" ] || [ "$UV_VERSION" = "latest" ]; then
python -m pip install uv
else
python -m pip install "uv==$UV_VERSION"
fi
- name: Cache uv downloads
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-${{ hashFiles(inputs.cache_dependency_path) }}
restore-keys: |
uv-${{ runner.os }}-
- name: Sync dependencies
run: uv sync ${{ inputs.uv_sync_args }}
- name: Run format check
if: ${{ inputs.format_command != '' }}
run: ${{ inputs.format_command }}
- name: Run lint
if: ${{ inputs.lint_command != '' }}
run: ${{ inputs.lint_command }}
- name: Run typecheck
if: ${{ inputs.typecheck_command != '' }}
run: ${{ inputs.typecheck_command }}
- name: Run tests
if: ${{ inputs.test_command != '' }}
run: ${{ inputs.test_command }}

124
.github/workflows/python-uv-ci.yml vendored Normal file
View File

@@ -0,0 +1,124 @@
name: Python UV Quality
on:
workflow_call:
inputs:
python_version:
type: string
default: "3.13"
uv_version:
type: string
default: "latest"
working_directory:
type: string
default: "."
cache_dependency_path:
type: string
default: "uv.lock"
env:
description: >
Multiline env vars, one per line: KEY=VALUE
required: false
type: string
default: ""
uv_sync_args:
type: string
default: "--frozen --dev"
format_command:
type: string
default: "uv run ruff format --check ."
lint_command:
type: string
default: "uv run ruff check ."
typecheck_command:
type: string
default: "uv run mypy ."
test_command:
type: string
default: "uv run pytest"
secrets:
ssh_private_key:
required: false
ssh_known_hosts:
required: false
jobs:
quality:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: ${{ inputs.working_directory }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Load env vars
if: ${{ inputs.env != '' }}
run: |
while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in \#*) continue;; esac
if [[ "$line" != *=* ]]; then
echo "Invalid env line: $line" >&2
exit 1
fi
echo "$line" >> "$GITHUB_ENV"
done <<< "${{ inputs.env }}"
- name: Start ssh-agent
if: ${{ secrets.ssh_private_key != '' }}
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.ssh_private_key }}
- name: Add SSH known hosts
if: ${{ secrets.ssh_known_hosts != '' }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.ssh_known_hosts }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
- name: Install uv
env:
UV_VERSION: ${{ inputs.uv_version }}
run: |
python -m pip install --upgrade pip
if [ -z "$UV_VERSION" ] || [ "$UV_VERSION" = "latest" ]; then
python -m pip install uv
else
python -m pip install "uv==$UV_VERSION"
fi
- name: Cache uv downloads
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-${{ hashFiles(inputs.cache_dependency_path) }}
restore-keys: |
uv-${{ runner.os }}-
- name: Sync dependencies
run: uv sync ${{ inputs.uv_sync_args }}
- name: Run format check
if: ${{ inputs.format_command != '' }}
run: ${{ inputs.format_command }}
- name: Run lint
if: ${{ inputs.lint_command != '' }}
run: ${{ inputs.lint_command }}
- name: Run typecheck
if: ${{ inputs.typecheck_command != '' }}
run: ${{ inputs.typecheck_command }}
- name: Run tests
if: ${{ inputs.test_command != '' }}
run: ${{ inputs.test_command }}

View File

@@ -1,90 +0,0 @@
# docker-build-push
This GitHub Actions workflow builds and pushes Docker images to a container registry.
It serves as a base workflow and is usable this way, but it may be customized depending on the exact use case.
## Use cases
### Build and push Docker images for CI/CD
This workflow can be used in CI/CD pipelines to automate the process of building and pushing Docker images whenever code is pushed to the repository or a pull request is created.
I use it with [watchtower](https://github.com/containrrr/watchtower) to automatically update running containers with the latest images.
### Build an upstream
You may want to build an upstream image from another repository and push it to your own container registry.
You can do this this by modifying the checkout step to pull from the external repository and pass the correct build context to the Docker build step.
```yaml
- name: Checkout external repository to ./external-src
uses: actions/checkout@v5
with:
repository: owner/repo-name
ref: main
server-url: ${{ github.server_url }}
path: external-src
fetch-depth: 0 # Fetch all history for all branches and tags
# ...
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./external-src
# ...
```
### When SSH access is needed during build
If your Docker build process requires SSH access (for example, to clone private repositories), you can enable SSH agent, and configure the Docker build step to use it.
You will also need to change the Dockerfile to use the SSH mount.
```yaml
- name: Start ssh-agent
uses: https://github.com/webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.CI_SSH_PRIVATE_KEY }}
# ...
- name: Build & push
uses: docker/build-push-action@v5
with:
ssh: default
build-args: |
GITEA_HOSTKEY=${{ secrets.SSH_GITEA_HOSTKEY }} # Pass host key as build-arg
```
And modify your Dockerfile like this:
```Dockerfile
# Install dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
openssh-client \
ca-certificates \
libnss3 \
nss-plugin-pem \
libbrotli1 && \
rm -rf /var/lib/apt/lists/*
# Add Gitea host key to known_hosts
ARG GITEA_HOSTKEY
RUN set -eux; \
mkdir -p /etc/ssh; \
printf '%s\n' "$GITEA_HOSTKEY" > /etc/ssh/ssh_known_hosts; \
chmod 644 /etc/ssh/ssh_known_hosts; \
ssh-keygen -l -E sha256 -f /etc/ssh/ssh_known_hosts
# Clone private repository using SSH during build
RUN --mount=type=ssh git clone git@your-gitea-server:your-repo.git /path/to/destination
# You can do whatever you need with SSH by using the --mount=type=ssh flag
# RUN --mount=type=ssh \
# GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts' \
# pip install --no-cache-dir -r requirements.txt
```

View File

@@ -1,114 +0,0 @@
name: Docker Build, Scan & Publish
on:
workflow_call:
inputs:
images:
description: >
JSON array of images to build.
Each item: { name, context, dockerfile, target, cache_ref }
required: true
type: string
registry_host:
required: true
type: string
default_branch:
required: true
type: string
build_args:
required: false
type: string
default: ""
trivy_severity:
required: false
type: string
default: "CRITICAL"
secrets:
registry_user:
required: true
registry_password:
required: true
ci_token:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Determine branch context
id: branch
run: |
if [ "${{ github.ref_name }}" = "${{ inputs.default_branch }}" ]; then
echo "is_default=true" >> "$GITHUB_OUTPUT"
else
echo "is_default=false" >> "$GITHUB_OUTPUT"
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry_host }}
username: ${{ secrets.registry_user }}
password: ${{ secrets.registry_password }}
- name: Build, scan and push images
env:
IMAGES: ${{ inputs.images }}
BUILD_ARGS: ${{ inputs.build_args }}
CI_TOKEN: ${{ secrets.ci_token }}
TRIVY_SEVERITY: ${{ inputs.trivy_severity }}
run: |
set -euo pipefail
echo "$IMAGES" | jq -c '.[]' | while read -r img; do
NAME=$(echo "$img" | jq -r '.name')
CONTEXT=$(echo "$img" | jq -r '.context')
DOCKERFILE=$(echo "$img" | jq -r '.dockerfile')
TARGET=$(echo "$img" | jq -r '.target')
CACHE_REF=$(echo "$img" | jq -r '.cache_ref')
echo "==== Building $NAME ===="
TAGS=()
TAGS+=("$NAME:${{ github.ref_name }}")
TAGS+=("$NAME:${{ github.sha }}")
if [ "${{ steps.branch.outputs.is_default }}" = "true" ]; then
TAGS+=("$NAME:latest")
fi
TAG_ARGS=$(printf -- "--tag %s " "${TAGS[@]}")
docker buildx build \
--file "$DOCKERFILE" \
--target "$TARGET" \
--cache-from "type=registry,ref=$CACHE_REF" \
--cache-to "type=registry,ref=$CACHE_REF,mode=max" \
--load \
$TAG_ARGS \
$(printf -- "--build-arg %s " $BUILD_ARGS) \
"$CONTEXT"
echo "==== Trivy scan for $NAME ===="
trivy image \
--severity "$TRIVY_SEVERITY" \
--exit-code 1 \
"${TAGS[0]}"
echo "==== Pushing $NAME ===="
for tag in "${TAGS[@]}"; do
docker push "$tag"
done
done