Skip to content

Watch Upstream Versions #223

Watch Upstream Versions

Watch Upstream Versions #223

name: Watch Upstream Versions
on:
schedule:
- cron: "17 */6 * * *"
workflow_dispatch:
permissions:
actions: write
contents: write
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
sync-upstream:
name: Sync Watched Upstream Versions
runs-on: ubuntu-latest
env:
# source: "dockerhub" (default) | "github"
# dockerhub: repo is Docker Hub path (e.g. library/amazonlinux)
# github: repo is owner/name (e.g. MystenLabs/seal); filter matches release tag_name
# required_assets (github only): optional list of asset names that must exist on the release
WATCH_MATRIX: |
[
{
"package_name": "nitro-cli",
"source": "dockerhub",
"repo": "library/amazonlinux",
"filter": "^(2023[.][0-9]+[.][0-9]+[.][0-9]+)$",
"second_filter": "",
"version_file": "packages/nitro-cli/.current-version",
"publish_input": "deploy-nitro-cli"
},
{
"package_name": "walrus-upload-relay",
"source": "dockerhub",
"repo": "mysten/walrus-upload-relay",
"filter": "^main-v([0-9]+[.][0-9]+[.][0-9]+)$",
"second_filter": "^main-arm64-v{{version}}$",
"version_file": "packages/walrus-upload-relay/.current-version",
"publish_input": "deploy-walrus-upload-relay"
},
{
"package_name": "seal-key-server",
"source": "github",
"repo": "MystenLabs/seal",
"filter": "^seal-v([0-9]+[.][0-9]+[.][0-9]+)$",
"required_assets": [
"key-server-linux-x86_64",
"key-server-linux-aarch64"
],
"version_file": "packages/seal-key-server/.current-version",
"publish_input": "deploy-seal-key-server"
}
]
steps:
- name: Checkout main
uses: actions/checkout@v6
with:
ref: main
- name: Fetch latest upstream versions
id: watch
env:
GITHUB_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
fetch_latest_dockerhub_tag() {
local repo="$1"
local filter="$2"
local max_pages="$3"
local page=1
while (( page <= max_pages )); do
response="$(
curl -fsSL "https://hub.docker.com/v2/repositories/${repo}/tags?page=${page}&page_size=100"
)"
tag="$(
jq -r --arg filter "${filter}" '
.results[]?.name
| match($filter)?
| select(. != null)
| (.captures[0].string // .string)
' <<< "${response}" | head -n1
)"
if [[ -n "${tag}" ]]; then
printf '%s\n' "${tag}"
return 0
fi
page=$((page + 1))
done
echo "No tag matched ${filter} in Docker Hub repo ${repo}" >&2
return 1
}
# Finds the newest non-draft, non-prerelease GitHub release whose tag_name
# matches filter. Prints the first capture group when present, else the full match.
# Optional required_assets_json is a JSON array of asset names that must exist.
fetch_latest_github_release() {
local repo="$1"
local filter="$2"
local max_pages="$3"
local required_assets_json="${4:-[]}"
local auth_args=()
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
auth_args=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
local page=1
while (( page <= max_pages )); do
response="$(
curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${auth_args[@]}" \
"https://api.github.com/repos/${repo}/releases?per_page=30&page=${page}"
)"
# Empty page means we've exhausted releases.
if [[ "$(jq 'length' <<< "${response}")" -eq 0 ]]; then
break
fi
# Walk releases in API order (newest first). Skip drafts/prereleases.
while read -r release; do
tag_name="$(jq -r '.tag_name // empty' <<< "${release}")"
[[ -z "${tag_name}" ]] && continue
version="$(
jq -rn --arg tag "${tag_name}" --arg filter "${filter}" '
($tag | match($filter)?) as $m
| if $m == null then empty
else ($m.captures[0].string // $m.string)
end
'
)"
[[ -z "${version}" ]] && continue
# Ensure required release assets are present when configured.
missing="$(
jq -r --argjson required "${required_assets_json}" '
($required // []) as $need
| .assets // []
| map(.name)
| . as $have
| $need
| map(select(. as $n | ($have | index($n) | not)))
| .[]
' <<< "${release}"
)"
if [[ -n "${missing}" ]]; then
echo "Skipping ${repo}@${tag_name}: missing assets: ${missing//$'\n'/, }" >&2
continue
fi
printf '%s\n' "${version}"
return 0
done < <(jq -c '.[] | select(.draft == false and .prerelease == false)' <<< "${response}")
page=$((page + 1))
done
echo "No release matched ${filter} in GitHub repo ${repo}" >&2
return 1
}
packages="$(jq -cn '{}')"
while read -r package; do
package_name="$(jq -r '.package_name' <<< "${package}")"
source="$(jq -r '.source // "dockerhub"' <<< "${package}")"
repo="$(jq -r '.repo' <<< "${package}")"
filter="$(jq -r '.filter' <<< "${package}")"
second_filter="$(jq -r '.second_filter // empty' <<< "${package}")"
required_assets="$(jq -c '.required_assets // []' <<< "${package}")"
case "${source}" in
dockerhub)
latest_version="$(fetch_latest_dockerhub_tag "${repo}" "${filter}" 3)"
if [[ -n "${second_filter}" ]]; then
resolved_second_filter="${second_filter//\{\{version\}\}/${latest_version}}"
if ! fetch_latest_dockerhub_tag "${repo}" "${resolved_second_filter}" 3 > /dev/null; then
echo "Skipping ${package_name}: secondary tag check failed for ${resolved_second_filter}"
continue
fi
fi
;;
github)
latest_version="$(fetch_latest_github_release "${repo}" "${filter}" 5 "${required_assets}")"
;;
*)
echo "Unknown source '${source}' for package ${package_name}" >&2
exit 1
;;
esac
packages="$(
jq -c \
--arg package_name "${package_name}" \
--arg latest_version "${latest_version}" \
--argjson package "${package}" \
'. + {($package_name): ($package + {latest_version: $latest_version})}' \
<<< "${packages}"
)"
done < <(jq -c '.[]' <<< "${WATCH_MATRIX}")
{
echo "packages<<EOF"
jq -c . <<< "${packages}"
echo "EOF"
} >> "${GITHUB_OUTPUT}"
- name: Patch and commit latest versions
id: sync
env:
WATCHED_PACKAGES: ${{ steps.watch.outputs.packages }}
shell: bash
run: |
set -euo pipefail
update_readme_version() {
local package_name="$1"
local current_version="$2"
local latest_version="$3"
sed -i "/\[\`cmdoss\/${package_name}\`\]/s/\`${current_version}\` 👁️/\`${latest_version}\` 👁️/" README.md
if ! grep -F "[\`cmdoss/${package_name}\`]" README.md | grep -Fq "\`${latest_version}\` 👁️"; then
echo "Failed to update ${package_name} version in README.md" >&2
exit 1
fi
}
updated=false
deploy_inputs="$(
jq -c '
map({(.publish_input): "false"})
| add
| . + {"deploy-auth-proxy": "false"}
' <<< "${WATCH_MATRIX}"
)"
summary_file="$(mktemp)"
while read -r package; do
package_name="$(jq -r '.package_name' <<< "${package}")"
version_file="$(jq -r '.version_file' <<< "${package}")"
publish_input="$(jq -r '.publish_input' <<< "${package}")"
latest_version="$(jq -r '.latest_version' <<< "${package}")"
current_version="$(tr -d '[:space:]' < "${version_file}")"
if [[ -z "${current_version}" ]]; then
echo "No version found in ${version_file}" >&2
exit 1
fi
if [[ "${current_version}" == "${latest_version}" ]]; then
echo "${package_name} is already at ${latest_version}"
continue
fi
printf '%s\n' "${latest_version}" > "${version_file}"
update_readme_version "${package_name}" "${current_version}" "${latest_version}"
deploy_inputs="$(jq -c --arg publish_input "${publish_input}" '.[$publish_input] = "true"' <<< "${deploy_inputs}")"
updated=true
printf '%s\n' "- ${package_name}: ${current_version} -> ${latest_version}" >> "${summary_file}"
done < <(jq -c '.[]' <<< "${WATCHED_PACKAGES}")
if [[ "${updated}" != "true" ]]; then
{
echo "updated=false"
echo "deploy-inputs<<EOF"
jq -c . <<< "${deploy_inputs}"
echo "EOF"
} >> "${GITHUB_OUTPUT}"
echo "No watched upstream updates found."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
mapfile -d '' changed_paths < <(git diff --name-only -z)
git add -- "${changed_paths[@]}"
summary="$(cat "${summary_file}")"
git commit -m "chore: update watched upstream versions" -m "${summary}"
git push origin HEAD:main
{
echo "updated=true"
echo "deploy-inputs<<EOF"
jq -c . <<< "${deploy_inputs}"
echo "EOF"
} >> "${GITHUB_OUTPUT}"
- name: Dispatch publish
if: steps.sync.outputs.updated == 'true'
env:
GH_TOKEN: ${{ github.token }}
DEPLOY_INPUTS: ${{ steps.sync.outputs['deploy-inputs'] }}
shell: bash
run: |
set -euo pipefail
payload="$(
jq -cn \
--argjson inputs "${DEPLOY_INPUTS}" \
'{
ref: "main",
inputs: $inputs
}'
)"
curl -fsSL \
-X POST \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yaml/dispatches" \
-d "${payload}"