diff --git a/.gitea/workflows/build-on-demand.yml b/.gitea/workflows/build-containers-on-demand.yml similarity index 67% rename from .gitea/workflows/build-on-demand.yml rename to .gitea/workflows/build-containers-on-demand.yml index f040e2a..031fa83 100644 --- a/.gitea/workflows/build-on-demand.yml +++ b/.gitea/workflows/build-containers-on-demand.yml @@ -1,4 +1,4 @@ -name: Build image when workload image tag changes +name: Build containers when image tags change on: push: @@ -7,16 +7,31 @@ on: paths: - "argocd/deployment.yaml" - "Dockerfile" - - ".gitea/workflows/build-on-demand.yml" + - "data-loader/**" + - ".gitea/workflows/build-containers-on-demand.yml" jobs: build-if-image-changed: runs-on: ubuntu-latest + strategy: + matrix: + include: + - container_name: web + expected_repo: git.baumann.gr/adebaumann/vui + build_context: . + container_type: containers + description: main container + - container_name: loader + expected_repo: git.baumann.gr/adebaumann/vui-data-loader + build_context: data-loader + container_type: initContainers + description: init-container env: DEPLOY_FILE: "argocd/deployment.yaml" - CONTAINER_NAME: "web" # we only consider this container - EXPECTED_REPO: "git.baumann.gr/adebaumann/vui" # sanity-check the repo + CONTAINER_NAME: ${{ matrix.container_name }} + EXPECTED_REPO: ${{ matrix.expected_repo }} + CONTAINER_TYPE: ${{ matrix.container_type }} steps: - name: Checkout @@ -43,19 +58,21 @@ jobs: 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 workload image from deployment (old vs new) + + - name: Read ${{ matrix.description }} 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}" + cname="${CONTAINER_NAME}" + expected_repo="${EXPECTED_REPO}" + ctype="${CONTAINER_TYPE}" export cname export expected_repo - + export ctype + # --- functions ------------------------------------------------------ have_yq() { command -v yq >/dev/null 2>&1; } @@ -63,22 +80,31 @@ jobs: yq_extract() { yq -r ' select(.kind == "Deployment") | - .spec.template.spec.containers // [] | + .spec.template.spec.[env(ctype)] // [] | map(select(.name == env(cname))) | .[]?.image ' "$1" 2>/dev/null | tail -n 1 } - # ultra-tolerant fallback: grep around containers: block and pick name==web image - # (still ignores initContainers by staying within the "containers:" block) + # ultra-tolerant fallback: grep around the appropriate block fallback_extract() { - # print the containers block, then associate names with images - awk -v cname="$cname" ' + local block_pattern + local end_pattern + + if [[ "$ctype" == "initContainers" ]]; then + block_pattern="^[[:space:]]*initContainers:" + end_pattern="^[[:space:]]*containers:|^[[:alpha:]][[:alnum:]_:-]*:" + else + block_pattern="^[[:space:]]*containers:" + end_pattern="^[[:alpha:]][[:alnum:]_:-]*:|^[[:space:]]*initContainers:" + fi + + awk -v cname="$cname" -v block="$block_pattern" -v endp="$end_pattern" ' BEGIN{ in_cont=0; name=""; image="" } - /^containers:/ {in_cont=1; next} + $0 ~ block {in_cont=1; next} in_cont { - # end of containers block when dedented to less than two spaces or new top-level key - if ($0 ~ /^[[:alpha:]][[:alnum:]_:-]*:/ || $0 ~ /^ *initContainers:/) { in_cont=0 } + # end of block when we hit the end pattern + if ($0 ~ endp) { in_cont=0 } # capture name and image lines if ($0 ~ /^[[:space:]]*-?[[:space:]]*name:[[:space:]]*/) { name=$0; sub(/^.*name:[[:space:]]*/,"",name); gsub(/^[ "\047]+|[ "\047]+$/,"",name) @@ -92,18 +118,24 @@ jobs: } list_workload_images() { - echo "== workload containers in $1 ==" >&2 + echo "== workload $ctype in $1 ==" >&2 if have_yq; then yq -r ' select(.kind == "Deployment") | - .spec.template.spec.containers // [] | + .spec.template.spec.[env(ctype)] // [] | .[] | "\(.name): \(.image)" ' "$1" 2>/dev/null | nl -ba >&2 || true else # coarse list for visibility - awk ' - /^ *containers:/, /^[^ ]/ { if ($0 ~ /name:|image:/) print } - ' "$1" | nl -ba >&2 || true + if [[ "$ctype" == "initContainers" ]]; then + awk ' + /^ *initContainers:/, /^ *containers:/ { if ($0 ~ /name:|image:/) print } + ' "$1" | nl -ba >&2 || true + else + awk ' + /^ *containers:/, /^[^ ]/ { if ($0 ~ /name:|image:/) print } + ' "$1" | nl -ba >&2 || true + fi fi } # -------------------------------------------------------------------- @@ -141,11 +173,11 @@ jobs: old_image="$(fallback_extract /tmp/old.yaml || true)" fi - echo "Old workload image: ${old_image:-}" - echo "New workload image: ${new_image:-}" + echo "Old $ctype image: ${old_image:-}" + echo "New $ctype image: ${new_image:-}" if [ -z "${new_image:-}" ]; then - echo "ERROR: Could not find containers[].name == \"$cname\" image in $file" + echo "ERROR: Could not find $ctype[].name == \"$cname\" image in $file" exit 1 fi @@ -153,7 +185,7 @@ jobs: new_repo="${new_image%:*}" new_tag="${new_image##*:}" if [[ "$new_repo" != "$expected_repo" ]]; then - echo "ERROR: Found container \"$cname\" image repo is \"$new_repo\" but expected \"$expected_repo\"" + echo "ERROR: Found $ctype \"$cname\" image repo is \"$new_repo\" but expected \"$expected_repo\"" exit 1 fi if [ -n "${old_image:-}" ]; then @@ -171,10 +203,10 @@ jobs: echo "new_tag=$new_tag" echo "registry=$registry" } >> "$GITHUB_OUTPUT" - + - name: Skip if tag unchanged if: steps.img.outputs.changed != 'true' - run: echo "Workload image tag unchanged; skipping build." + run: echo "${{ matrix.description }} image tag unchanged; skipping build." - name: Set up Buildx if: steps.img.outputs.changed == 'true' @@ -188,11 +220,11 @@ jobs: username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_PASSWORD }} - - name: Build and push (exact tag from deployment) + - name: Build and push ${{ matrix.description }} (exact tag from deployment) if: steps.img.outputs.changed == 'true' uses: docker/build-push-action@v6 with: - context: . + context: ${{ matrix.build_context }} push: true tags: | ${{ steps.img.outputs.new_image }} @@ -202,4 +234,3 @@ jobs: org.opencontainers.image.revision=${{ gitea.sha }} cache-from: type=gha cache-to: type=gha,mode=max - diff --git a/.gitea/workflows/build-data-loader-on-demand.yml b/.gitea/workflows/build-data-loader-on-demand.yml deleted file mode 100644 index 65b4cb6..0000000 --- a/.gitea/workflows/build-data-loader-on-demand.yml +++ /dev/null @@ -1,204 +0,0 @@ -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 -