diff --git a/.gitea/workflows/build-data-loader-on-demand.yml b/.gitea/workflows/build-data-loader-on-demand.yml new file mode 100644 index 0000000..65b4cb6 --- /dev/null +++ b/.gitea/workflows/build-data-loader-on-demand.yml @@ -0,0 +1,204 @@ +name: Build data-loader when init-container image tag changes + +on: + push: + # Uncomment/adjust once it's working: + # branches: [ main ] + paths: + - "argocd/deployment.yaml" + - "data-loader/**" + - ".gitea/workflows/build-data-loader-on-demand.yml" + +jobs: + build-if-image-changed: + runs-on: ubuntu-latest + + env: + DEPLOY_FILE: "argocd/deployment.yaml" + CONTAINER_NAME: "loader" # we only consider this init-container + EXPECTED_REPO: "git.baumann.gr/adebaumann/vui-data-loader" # sanity-check the repo + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Determine base commit + id: base + shell: bash + run: | + set -euo pipefail + if git rev-parse --verify -q HEAD~1 >/dev/null; then + echo "base=$(git rev-parse HEAD~1)" >> "$GITHUB_OUTPUT" + else + echo "base=$(git hash-object -t tree /dev/null)" >> "$GITHUB_OUTPUT" + fi + + - name: Install yq + shell: bash + run: | + set -euo pipefail + YQ_VER=v4.44.3 + curl -sL "https://github.com/mikefarah/yq/releases/download/${YQ_VER}/yq_linux_amd64" -o /usr/local/bin/yq + chmod +x /usr/local/bin/yq + yq --version + + - name: Read init-container image from deployment (old vs new) + id: img + shell: bash + run: | + set -euo pipefail + file="${DEPLOY_FILE:-argocd/deployment.yaml}" + cname="${CONTAINER_NAME:-web}" + expected_repo="${EXPECTED_REPO:-git.baumann.gr/adebaumann/vui}" + + export cname + export expected_repo + + # --- functions ------------------------------------------------------ + have_yq() { command -v yq >/dev/null 2>&1; } + + # yq-based extractor (multi-doc aware; Deployment only; init-container name match) + yq_extract() { + yq -r ' + select(.kind == "Deployment") | + .spec.template.spec.initContainers // [] | + map(select(.name == env(cname))) | + .[]?.image + ' "$1" 2>/dev/null | tail -n 1 + } + + # ultra-tolerant fallback: grep around initContainers: block and pick name==loader image + fallback_extract() { + # print the initContainers block, then associate names with images + awk -v cname="$cname" ' + BEGIN{ in_cont=0; name=""; image="" } + /^[[:space:]]*initContainers:/ {in_cont=1; next} + in_cont { + # end of initContainers block when dedented to less than two spaces or new top-level key + if ($0 ~ /^[[:space:]]*containers:/ || $0 ~ /^[[:alpha:]][[:alnum:]_:-]*:/) { in_cont=0 } + # capture name and image lines + if ($0 ~ /^[[:space:]]*-?[[:space:]]*name:[[:space:]]*/) { + name=$0; sub(/^.*name:[[:space:]]*/,"",name); gsub(/^[ "\047]+|[ "\047]+$/,"",name) + } + if ($0 ~ /^[[:space:]]*image:[[:space:]]*/) { + image=$0; sub(/^.*image:[[:space:]]*/,"",image); gsub(/^[ "\047]+|[ "\047]+$/,"",image) + if (name==cname) { print image; exit } + } + } + ' "$1" + } + + list_workload_images() { + echo "== workload initContainers in $1 ==" >&2 + if have_yq; then + yq -r ' + select(.kind == "Deployment") | + .spec.template.spec.initContainers // [] | + .[] | "\(.name): \(.image)" + ' "$1" 2>/dev/null | nl -ba >&2 || true + else + # coarse list for visibility + awk ' + /^ *initContainers:/, /^ *containers:/ { if ($0 ~ /name:|image:/) print } + ' "$1" | nl -ba >&2 || true + fi + } + # -------------------------------------------------------------------- + + # Ensure yq is present; if install failed earlier, try once more + if ! have_yq; then + echo "yq missing; attempting quick install..." >&2 + curl -fsSL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -o /usr/local/bin/yq || true + chmod +x /usr/local/bin/yq || true + fi + + # Prepare old file (previous commit) if exists + if git cat-file -e "${{ steps.base.outputs.base }}":"$file" 2>/dev/null; then + git show "${{ steps.base.outputs.base }}:$file" > /tmp/old.yaml + else + : > /tmp/old.yaml + fi + + list_workload_images /tmp/old.yaml || true + list_workload_images "$file" || true + + if have_yq; then + old_image="$(yq_extract /tmp/old.yaml || true)" + new_image="$(yq_extract "$file" || true)" + else + old_image="$(fallback_extract /tmp/old.yaml || true)" + new_image="$(fallback_extract "$file" || true)" + fi + + # If yq path failed to find it, try fallback once more as safety + if [ -z "${new_image:-}" ]; then + new_image="$(fallback_extract "$file" || true)" + fi + if [ -z "${old_image:-}" ]; then + old_image="$(fallback_extract /tmp/old.yaml || true)" + fi + + echo "Old workload image: ${old_image:-}" + echo "New workload image: ${new_image:-}" + + if [ -z "${new_image:-}" ]; then + echo "ERROR: Could not find initContainers[].name == \"$cname\" image in $file" + exit 1 + fi + + # Split repo and tag + new_repo="${new_image%:*}" + new_tag="${new_image##*:}" + if [[ "$new_repo" != "$expected_repo" ]]; then + echo "ERROR: Found init-container \"$cname\" image repo is \"$new_repo\" but expected \"$expected_repo\"" + exit 1 + fi + if [ -n "${old_image:-}" ]; then + old_tag="${old_image##*:}" + else + old_tag="" + fi + + registry="$(echo "$new_repo" | awk -F/ '{print $1}')" + + { + echo "changed=$([ "$old_tag" != "$new_tag" ] && echo true || echo false)" + echo "new_image=$new_image" + echo "new_repo=$new_repo" + echo "new_tag=$new_tag" + echo "registry=$registry" + } >> "$GITHUB_OUTPUT" + + - name: Skip if tag unchanged + if: steps.img.outputs.changed != 'true' + run: echo "Init-container image tag unchanged; skipping build." + + - name: Set up Buildx + if: steps.img.outputs.changed == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + if: steps.img.outputs.changed == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ steps.img.outputs.registry }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build and push (exact tag from deployment) + if: steps.img.outputs.changed == 'true' + uses: docker/build-push-action@v6 + with: + context: data-loader + push: true + tags: | + ${{ steps.img.outputs.new_image }} + ${{ steps.img.outputs.new_repo }}:latest + labels: | + org.opencontainers.image.source=${{ gitea.repository }} + org.opencontainers.image.revision=${{ gitea.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max +