🏷️ ci: Sync Helm Chart Tags (#13446)

* ci: Sync Helm chart tags

* ci: Address Helm tag sync review
This commit is contained in:
Danny Avila 2026-06-01 08:43:32 -04:00 committed by GitHub
parent 566e20b613
commit d5a2d3b26b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 342 additions and 17 deletions

232
.github/scripts/sync-helm-chart-tags.sh vendored Executable file
View file

@ -0,0 +1,232 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_PATH="${CHART_PATH:-helm/librechat/Chart.yaml}"
DEFAULT_BRANCH="${DEFAULT_BRANCH:-main}"
BASE_REF="${BASE_REF:-refs/remotes/origin/${DEFAULT_BRANCH}}"
BACKFILL_FROM_VERSION="${BACKFILL_FROM_VERSION:-1.9.0}"
PUSH_TAGS="${PUSH_TAGS:-false}"
TAG_PREFIX="${TAG_PREFIX:-chart-}"
GITHUB_SERVER_URL="${GITHUB_SERVER_URL:-https://github.com}"
DISPATCH_WORKFLOW="${DISPATCH_WORKFLOW:-}"
RELEASE_EXISTING_TAG="${RELEASE_EXISTING_TAG:-}"
SEMVER_REGEX='^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?([+][0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?$'
fail() {
printf '::error::%s\n' "$1" >&2
exit 1
}
git_with_auth() {
if [ -n "${GITHUB_TOKEN:-}" ]; then
git -c "http.${GITHUB_SERVER_URL}/.extraheader=AUTHORIZATION: bearer ${GITHUB_TOKEN}" "$@"
return
fi
git "$@"
}
dispatch_release() {
tag="$1"
if [ -z "$DISPATCH_WORKFLOW" ]; then
return
fi
if [ -z "${GITHUB_REPOSITORY:-}" ]; then
fail "GITHUB_REPOSITORY is required to dispatch ${DISPATCH_WORKFLOW}"
fi
if [[ ! "$GITHUB_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
fail "Unexpected repository name: ${GITHUB_REPOSITORY}"
fi
if [[ ! "$DISPATCH_WORKFLOW" =~ ^[A-Za-z0-9_.-]+[.]ya?ml$ ]]; then
fail "Unexpected workflow file: ${DISPATCH_WORKFLOW}"
fi
token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
if [ -z "$token" ]; then
fail "GH_TOKEN or GITHUB_TOKEN is required to dispatch ${DISPATCH_WORKFLOW}"
fi
command -v gh >/dev/null ||
fail "GitHub CLI is required to dispatch ${DISPATCH_WORKFLOW}"
GH_TOKEN="$token" gh workflow run "$DISPATCH_WORKFLOW" \
--repo "$GITHUB_REPOSITORY" \
--ref "$DEFAULT_BRANCH" \
-f "chart_tag=${tag}"
}
version_less_than() {
left="${1%%[-+]*}"
right="${2%%[-+]*}"
IFS=. read -r left_major left_minor left_patch <<<"$left"
IFS=. read -r right_major right_minor right_patch <<<"$right"
if (( left_major != right_major )); then
(( left_major < right_major ))
return
fi
if (( left_minor != right_minor )); then
(( left_minor < right_minor ))
return
fi
(( left_patch < right_patch ))
}
validate_chart_tag() {
tag="$1"
version="${tag#${TAG_PREFIX}}"
git check-ref-format "refs/tags/${tag}" >/dev/null ||
fail "Refusing to use invalid tag ${tag}"
if [[ "$tag" != "${TAG_PREFIX}"* || ! "$version" =~ $SEMVER_REGEX ]]; then
fail "Chart tags must use the form ${TAG_PREFIX}<semver>, for example ${TAG_PREFIX}2.0.5"
fi
}
dispatch_existing_tag() {
tag="$1"
if [ -z "$tag" ]; then
return
fi
validate_chart_tag "$tag"
if [ "$PUSH_TAGS" != "true" ]; then
printf 'Would dispatch release workflow for existing %s.\n' "$tag"
return
fi
if ! git_with_auth ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
fail "Remote tag ${tag} does not exist"
fi
printf 'Dispatching release workflow for existing %s.\n' "$tag"
dispatch_release "$tag"
}
chart_version_at() {
git show "${1}:${CHART_PATH}" 2>/dev/null | awk '
/^version:[[:space:]]*/ {
value = $0
sub(/^version:[[:space:]]*/, "", value)
sub(/[[:space:]]*#.*/, "", value)
gsub(/^[[:space:]"'\''"]+|[[:space:]"'\''"]+$/, "", value)
print value
exit
}
'
}
case "$PUSH_TAGS" in
true | false) ;;
*) fail "PUSH_TAGS must be true or false" ;;
esac
if [[ ! "$BACKFILL_FROM_VERSION" =~ $SEMVER_REGEX ]]; then
fail "BACKFILL_FROM_VERSION must be a valid SemVer value"
fi
git rev-parse --verify "${BASE_REF}^{commit}" >/dev/null ||
fail "Unable to resolve ${BASE_REF}; fetch ${DEFAULT_BRANCH} before running this script"
history_file="$(mktemp)"
versions_file="$(mktemp)"
seen_file="$(mktemp)"
missing_file="$(mktemp)"
cleanup() {
rm -f "$history_file" "$versions_file" "$seen_file" "$missing_file"
}
trap cleanup EXIT
git log --first-parent --reverse --format=%H "$BASE_REF" -- "$CHART_PATH" >"$history_file"
if [ ! -s "$history_file" ]; then
fail "No history found for ${CHART_PATH} on ${BASE_REF}"
fi
while IFS= read -r commit; do
version="$(chart_version_at "$commit")"
if [ -z "$version" ]; then
continue
fi
if [[ ! "$version" =~ $SEMVER_REGEX ]]; then
fail "${CHART_PATH} has invalid SemVer '${version}' at ${commit}"
fi
if version_less_than "$version" "$BACKFILL_FROM_VERSION"; then
continue
fi
if grep -Fqx "$version" "$seen_file"; then
continue
fi
printf '%s\n' "$version" >>"$seen_file"
printf '%s\t%s\n' "$version" "$commit" >>"$versions_file"
done <"$history_file"
if [ ! -s "$versions_file" ]; then
fail "No chart versions found in ${CHART_PATH}"
fi
while IFS="$(printf '\t')" read -r version commit; do
tag="${TAG_PREFIX}${version}"
validate_chart_tag "$tag"
if git rev-parse --quiet --verify "refs/tags/${tag}" >/dev/null; then
continue
fi
printf '%s\t%s\n' "$tag" "$commit" >>"$missing_file"
done <"$versions_file"
if [ ! -s "$missing_file" ]; then
printf 'All chart versions on %s already have %s tags.\n' "$BASE_REF" "$TAG_PREFIX"
dispatch_existing_tag "$RELEASE_EXISTING_TAG"
exit 0
fi
while IFS="$(printf '\t')" read -r tag commit; do
short_commit="$(git rev-parse --short "$commit")"
if [ "$PUSH_TAGS" != "true" ]; then
printf 'Would create %s at %s.\n' "$tag" "$short_commit"
continue
fi
if git_with_auth ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
printf 'Remote tag %s already exists; dispatching release workflow.\n' "$tag"
dispatch_release "$tag"
continue
fi
git tag "$tag" "$commit"
if git_with_auth push origin "refs/tags/${tag}"; then
printf 'Created %s at %s.\n' "$tag" "$short_commit"
dispatch_release "$tag"
continue
fi
if git_with_auth ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
printf 'Remote tag %s was created concurrently; dispatching release workflow.\n' "$tag"
dispatch_release "$tag"
continue
fi
fail "Failed to push ${tag}"
done <"$missing_file"
dispatch_existing_tag "$RELEASE_EXISTING_TAG"

View file

@ -5,18 +5,52 @@ on:
push:
tags:
- "chart-*"
workflow_dispatch:
inputs:
chart_tag:
description: "Existing chart tag to release, for example chart-2.0.5"
required: true
type: string
jobs:
release:
permissions:
contents: write
contents: read
packages: write
runs-on: ubuntu-latest
env:
CHART_REPOSITORY: ${{ github.repository_owner }}/librechat-chart
steps:
- name: Resolve chart tag
id: chart-version
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_CHART_TAG: ${{ inputs.chart_tag || '' }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
CHART_TAG="$REF_NAME"
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
CHART_TAG="$INPUT_CHART_TAG"
fi
CHART_VERSION="${CHART_TAG#chart-}"
SEMVER_REGEX='^[0-9]+[.][0-9]+[.][0-9]+(-[0-9A-Za-z.-]+)?([+][0-9A-Za-z.-]+)?$'
if [[ "$CHART_TAG" != chart-* || ! "$CHART_VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error::Chart tags must use the form chart-<semver>, for example chart-2.0.3"
exit 1
fi
printf 'CHART_REF=refs/tags/%s\n' "$CHART_TAG" >> "$GITHUB_OUTPUT"
printf 'CHART_TAG=%s\n' "$CHART_TAG" >> "$GITHUB_OUTPUT"
printf 'CHART_VERSION=%s\n' "$CHART_VERSION" >> "$GITHUB_OUTPUT"
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ steps.chart-version.outputs.CHART_REF }}
- name: Configure Git
run: |
@ -35,20 +69,6 @@ jobs:
cd ../librechat-rag-api
helm dependency build
- name: Get Chart Version
id: chart-version
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
CHART_VERSION="${REF_NAME#chart-}"
SEMVER_REGEX='^[0-9]+[.][0-9]+[.][0-9]+(-[0-9A-Za-z.-]+)?([+][0-9A-Za-z.-]+)?$'
if [[ "$REF_NAME" != chart-* || ! "$CHART_VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error::Chart tags must use the form chart-<semver>, for example chart-2.0.3"
exit 1
fi
printf 'CHART_VERSION=%s\n' "$CHART_VERSION" >> "$GITHUB_OUTPUT"
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
@ -63,7 +83,7 @@ jobs:
uses: appany/helm-oci-chart-releaser@v0.4.2
with:
name: librechat
repository: ${{ github.actor }}/librechat-chart
repository: ${{ env.CHART_REPOSITORY }}
tag: ${{ steps.chart-version.outputs.CHART_VERSION }}
path: helm/librechat
registry: ghcr.io
@ -75,7 +95,7 @@ jobs:
uses: appany/helm-oci-chart-releaser@v0.4.2
with:
name: librechat-rag-api
repository: ${{ github.actor }}/librechat-chart
repository: ${{ env.CHART_REPOSITORY }}
tag: ${{ steps.chart-version.outputs.CHART_VERSION }}
path: helm/librechat-rag-api
registry: ghcr.io

View file

@ -0,0 +1,73 @@
name: Sync Helm Chart Tags
on:
push:
branches:
- main
workflow_dispatch:
inputs:
release_existing_tag:
description: "Existing chart-* tag to dispatch if tag creation succeeded but release dispatch failed"
required: false
type: string
permissions:
actions: write
contents: write
concurrency:
group: sync-helm-chart-tags
cancel-in-progress: false
jobs:
sync:
name: Sync chart tags
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
BASE_REF: refs/remotes/origin/main
BACKFILL_FROM_VERSION: 1.9.0
CHART_PATH: helm/librechat/Chart.yaml
DEFAULT_BRANCH: main
DISPATCH_WORKFLOW: helmcharts.yml
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
RELEASE_EXISTING_TAG: ${{ inputs.release_existing_tag || '' }}
REPO_DIR: ${{ runner.temp }}/librechat
TAG_PREFIX: chart-
steps:
- name: Fetch main and tags
shell: bash
run: |
set -euo pipefail
if [[ ! "$GITHUB_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "::error::Unexpected repository name: $GITHUB_REPOSITORY"
exit 1
fi
if [[ "$GITHUB_SERVER_URL" != "https://github.com" ]]; then
echo "::error::Unexpected GitHub server URL: $GITHUB_SERVER_URL"
exit 1
fi
rm -rf "$REPO_DIR"
git init "$REPO_DIR"
cd "$REPO_DIR"
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git -c "http.${GITHUB_SERVER_URL}/.extraheader=AUTHORIZATION: bearer ${GITHUB_TOKEN}" \
fetch --prune --force --tags origin \
"+refs/heads/${DEFAULT_BRANCH}:refs/remotes/origin/${DEFAULT_BRANCH}"
git checkout --detach "$BASE_REF"
- name: Create missing chart tags
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PUSH_TAGS: "true"
run: |
set -euo pipefail
cd "$REPO_DIR"
.github/scripts/sync-helm-chart-tags.sh