|
| 1 | +name: check-image-versions |
| 2 | + |
| 3 | +# Automatically checks whether any upstream container image has received a new |
| 4 | +# release, and opens a pull-request to update the matching Helm chart when one |
| 5 | +# is found. Mirrors the check-versions workflow used in |
| 6 | +# blinklabs-io/cardano-up-packages. |
| 7 | + |
| 8 | +on: |
| 9 | + schedule: |
| 10 | + # Run every Sunday at midnight UTC |
| 11 | + - cron: '0 0 * * 0' |
| 12 | + workflow_dispatch: |
| 13 | + |
| 14 | +jobs: |
| 15 | + check-image-versions: |
| 16 | + name: Check for new upstream image versions |
| 17 | + runs-on: ubuntu-latest |
| 18 | + permissions: |
| 19 | + contents: write |
| 20 | + pull-requests: write |
| 21 | + steps: |
| 22 | + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 https://github.com/actions/checkout/releases/tag/v7.0.0 |
| 23 | + with: |
| 24 | + fetch-depth: 0 |
| 25 | + |
| 26 | + - name: Check and create PRs for new versions |
| 27 | + env: |
| 28 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 29 | + run: | |
| 30 | + set -euo pipefail |
| 31 | +
|
| 32 | + # ── Git identity ─────────────────────────────────────────────────── |
| 33 | + git config user.name "github-actions[bot]" |
| 34 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 35 | +
|
| 36 | + UPSTREAM_CONFIG="scripts/upstream-versions.json" |
| 37 | +
|
| 38 | + # Iterate over every chart defined in the config |
| 39 | + CHARTS=$(jq -r 'keys[]' "$UPSTREAM_CONFIG") |
| 40 | +
|
| 41 | + for CHART in $CHARTS; do |
| 42 | + echo "==========================================" |
| 43 | + echo "Checking chart: $CHART" |
| 44 | + echo "==========================================" |
| 45 | +
|
| 46 | + # ── Read config fields ───────────────────────────────────────── |
| 47 | + REPO=$(jq -r --arg c "$CHART" '.[$c].repo' "$UPSTREAM_CONFIG") |
| 48 | + TAG_PATTERN=$(jq -r --arg c "$CHART" '.[$c].tag_pattern' "$UPSTREAM_CONFIG") |
| 49 | + VALUES_PREFIX=$(jq -r --arg c "$CHART" '.[$c].values_tag_prefix' "$UPSTREAM_CONFIG") |
| 50 | + APP_PREFIX=$(jq -r --arg c "$CHART" '.[$c].app_version_prefix' "$UPSTREAM_CONFIG") |
| 51 | + DOCKER_IMAGE=$(jq -r --arg c "$CHART" '.[$c].docker_image // ""' "$UPSTREAM_CONFIG") |
| 52 | + GHCR_IMAGE=$(jq -r --arg c "$CHART" '.[$c].ghcr_image // ""' "$UPSTREAM_CONFIG") |
| 53 | +
|
| 54 | + echo " Upstream repo : $REPO" |
| 55 | + echo " Tag pattern : '${TAG_PATTERN}'" |
| 56 | + echo " values_tag_prefix : '${VALUES_PREFIX}'" |
| 57 | + echo " app_version_prefix: '${APP_PREFIX}'" |
| 58 | +
|
| 59 | + # ── Determine upstream version ───────────────────────────────── |
| 60 | +
|
| 61 | + LATEST_TAG="" |
| 62 | + UPSTREAM_VERSION="" |
| 63 | +
|
| 64 | + if [[ -n "$GHCR_IMAGE" ]]; then |
| 65 | + # Use GHCR as source of truth (e.g. hsd – no predictable GH release tag) |
| 66 | + echo " Using GHCR image as version source: $GHCR_IMAGE" |
| 67 | + GHCR_ORG="${GHCR_IMAGE%%/*}" |
| 68 | + GHCR_PKG="${GHCR_IMAGE#*/}" |
| 69 | +
|
| 70 | + UPSTREAM_VERSION=$(gh api "/orgs/${GHCR_ORG}/packages/container/${GHCR_PKG}/versions" \ |
| 71 | + --paginate \ |
| 72 | + --jq '.[].metadata.container.tags[]' 2>/dev/null \ |
| 73 | + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ |
| 74 | + | sort -V | tail -1 || echo "") |
| 75 | +
|
| 76 | + if [[ -z "$UPSTREAM_VERSION" ]]; then |
| 77 | + echo " WARNING: Could not fetch versions from GHCR for $GHCR_IMAGE, skipping" |
| 78 | + continue |
| 79 | + fi |
| 80 | + echo " Latest GHCR version: $UPSTREAM_VERSION" |
| 81 | +
|
| 82 | + else |
| 83 | + # Use GitHub releases |
| 84 | + LATEST_TAG=$(gh api "repos/${REPO}/releases/latest" --jq '.tag_name' 2>/dev/null || echo "") |
| 85 | +
|
| 86 | + if [[ -z "$LATEST_TAG" ]]; then |
| 87 | + echo " WARNING: Could not fetch latest release for $REPO, skipping" |
| 88 | + continue |
| 89 | + fi |
| 90 | + echo " Latest upstream tag: $LATEST_TAG" |
| 91 | +
|
| 92 | + # Strip the tag_pattern prefix to derive the plain version number |
| 93 | + if [[ -n "$TAG_PATTERN" ]]; then |
| 94 | + UPSTREAM_VERSION="${LATEST_TAG#$TAG_PATTERN}" |
| 95 | + else |
| 96 | + UPSTREAM_VERSION="$LATEST_TAG" |
| 97 | + fi |
| 98 | +
|
| 99 | + # If docker_image is configured, look up the Docker Hub tag that |
| 100 | + # corresponds to this release (handles short GH tags like v2.11 vs |
| 101 | + # full image tags like v2.11.0, matching cardano-up-packages logic). |
| 102 | + if [[ -n "$DOCKER_IMAGE" ]]; then |
| 103 | + echo " Looking up Docker Hub tags for: $DOCKER_IMAGE" |
| 104 | + DOCKER_TAG=$(curl -s \ |
| 105 | + "https://hub.docker.com/v2/repositories/${DOCKER_IMAGE}/tags?page_size=100" \ |
| 106 | + | jq -r --arg prefix "${TAG_PATTERN}${UPSTREAM_VERSION}" \ |
| 107 | + '.results[].name | select(startswith($prefix))' \ |
| 108 | + | sort -V | tail -1 || echo "") |
| 109 | +
|
| 110 | + if [[ -n "$DOCKER_TAG" ]]; then |
| 111 | + UPSTREAM_VERSION="${DOCKER_TAG#$TAG_PATTERN}" |
| 112 | + echo " Found Docker Hub tag: $DOCKER_TAG -> version $UPSTREAM_VERSION" |
| 113 | + else |
| 114 | + echo " WARNING: No matching Docker Hub tag for ${TAG_PATTERN}${UPSTREAM_VERSION}, using GitHub tag" |
| 115 | + fi |
| 116 | + fi |
| 117 | + fi |
| 118 | +
|
| 119 | + echo " Upstream version: $UPSTREAM_VERSION" |
| 120 | +
|
| 121 | + # ── Derive the values-tag and app-version strings ────────────── |
| 122 | + NEW_VALUES_TAG="${VALUES_PREFIX}${UPSTREAM_VERSION}" |
| 123 | + NEW_APP_VERSION="${APP_PREFIX}${UPSTREAM_VERSION}" |
| 124 | +
|
| 125 | + # ── Read current image tag from values.yaml ──────────────────── |
| 126 | + VALUES_FILE="charts/${CHART}/values.yaml" |
| 127 | + if [[ ! -f "$VALUES_FILE" ]]; then |
| 128 | + echo " WARNING: values.yaml not found for $CHART, skipping" |
| 129 | + continue |
| 130 | + fi |
| 131 | +
|
| 132 | + CURRENT_TAG=$(grep '^ tag:' "$VALUES_FILE" | head -1 | awk '{print $2}' | tr -d '"') |
| 133 | + echo " Current values.yaml tag: $CURRENT_TAG" |
| 134 | +
|
| 135 | + # ── Skip if already up to date ───────────────────────────────── |
| 136 | + if [[ "$CURRENT_TAG" == "$NEW_VALUES_TAG" ]]; then |
| 137 | + echo " Already up to date" |
| 138 | + continue |
| 139 | + fi |
| 140 | +
|
| 141 | + echo " Update available: $CURRENT_TAG -> $NEW_VALUES_TAG" |
| 142 | +
|
| 143 | + BRANCH_NAME="chore/${CHART}-${UPSTREAM_VERSION}" |
| 144 | +
|
| 145 | + # Skip if an open PR already exists for this update. |
| 146 | + EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number') |
| 147 | + if [[ -n "$EXISTING_PR" ]]; then |
| 148 | + echo " PR #$EXISTING_PR already open for $BRANCH_NAME, skipping" |
| 149 | + continue |
| 150 | + fi |
| 151 | +
|
| 152 | + # ── Apply the update ─────────────────────────────────────────── |
| 153 | + echo " Applying update..." |
| 154 | + ./scripts/update-chart-version.sh "$CHART" "$NEW_VALUES_TAG" "$NEW_APP_VERSION" |
| 155 | +
|
| 156 | + # ── Commit and push ──────────────────────────────────────────── |
| 157 | + git checkout -b "$BRANCH_NAME" |
| 158 | + git add "charts/${CHART}/values.yaml" "charts/${CHART}/Chart.yaml" |
| 159 | + git commit -m "chore: update ${CHART} image to ${NEW_VALUES_TAG}" |
| 160 | +
|
| 161 | + # Remove a stale remote branch left over from a previous failed run |
| 162 | + # (only safe to do when no open PR was found above). |
| 163 | + if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then |
| 164 | + echo " Stale remote branch found with no open PR, deleting" |
| 165 | + git push origin --delete "$BRANCH_NAME" |
| 166 | + fi |
| 167 | +
|
| 168 | + git push -u origin "$BRANCH_NAME" |
| 169 | +
|
| 170 | + # ── Open PR ──────────────────────────────────────────────────── |
| 171 | + if [[ -n "$LATEST_TAG" ]]; then |
| 172 | + PR_BODY="## Summary |
| 173 | + - Updates \`${CHART}\` image tag from \`${CURRENT_TAG}\` to \`${NEW_VALUES_TAG}\` |
| 174 | + - Upstream release: https://github.com/${REPO}/releases/tag/${LATEST_TAG} |
| 175 | +
|
| 176 | + ## Changes |
| 177 | + - \`charts/${CHART}/values.yaml\`: \`image.tag\` → \`${NEW_VALUES_TAG}\` |
| 178 | + - \`charts/${CHART}/Chart.yaml\`: \`appVersion\` → \`${NEW_APP_VERSION}\`, chart \`version\` patch bumped |
| 179 | +
|
| 180 | + ## Test plan |
| 181 | + - [ ] CI lint passes |
| 182 | + - [ ] Manual smoke-test if needed |
| 183 | +
|
| 184 | + 🤖 Generated automatically by check-image-versions workflow" |
| 185 | + else |
| 186 | + PR_BODY="## Summary |
| 187 | + - Updates \`${CHART}\` image tag from \`${CURRENT_TAG}\` to \`${NEW_VALUES_TAG}\` |
| 188 | + - Source: ghcr.io/${GHCR_IMAGE}:${UPSTREAM_VERSION} |
| 189 | +
|
| 190 | + ## Changes |
| 191 | + - \`charts/${CHART}/values.yaml\`: \`image.tag\` → \`${NEW_VALUES_TAG}\` |
| 192 | + - \`charts/${CHART}/Chart.yaml\`: \`appVersion\` → \`${NEW_APP_VERSION}\`, chart \`version\` patch bumped |
| 193 | +
|
| 194 | + ## Test plan |
| 195 | + - [ ] CI lint passes |
| 196 | + - [ ] Manual smoke-test if needed |
| 197 | +
|
| 198 | + 🤖 Generated automatically by check-image-versions workflow" |
| 199 | + fi |
| 200 | +
|
| 201 | + PR_CREATED=false |
| 202 | + if PR_OUTPUT=$(gh pr create \ |
| 203 | + --title "chore: update ${CHART} image to ${NEW_VALUES_TAG}" \ |
| 204 | + --body "$PR_BODY" \ |
| 205 | + --base main \ |
| 206 | + --head "$BRANCH_NAME" 2>&1); then |
| 207 | + echo "$PR_OUTPUT" |
| 208 | + PR_CREATED=true |
| 209 | + else |
| 210 | + if echo "$PR_OUTPUT" | grep -qi 'already exists'; then |
| 211 | + echo " PR already exists for $BRANCH_NAME, skipping create" |
| 212 | + else |
| 213 | + echo "$PR_OUTPUT" |
| 214 | + exit 1 |
| 215 | + fi |
| 216 | + fi |
| 217 | +
|
| 218 | + # Return to main for the next chart |
| 219 | + git checkout main |
| 220 | +
|
| 221 | + if [[ "$PR_CREATED" == "true" ]]; then |
| 222 | + echo " PR created successfully!" |
| 223 | + fi |
| 224 | + done |
| 225 | +
|
| 226 | + echo "" |
| 227 | + echo "==========================================" |
| 228 | + echo "Image version check complete" |
| 229 | + echo "==========================================" |
0 commit comments