mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-10 01:02:56 +08:00
fix: address code review issues in cut-release.yml
- security: pass all inputs via env vars (no command injection) - security: validate ref names against safe charset - bug: use robust dry_run check (handle bool and 'true'/'false' string) - robustness: combine branch+tag push into single atomic operation - robustness: add set -euo pipefail to Configure git identity step Amp-Thread-ID: https://ampcode.com/threads/T-019e042d-d972-7559-b462-6e838c2da164 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
parent
08a9245e7f
commit
a8e11936f3
116
.github/workflows/cut-release.yml
vendored
116
.github/workflows/cut-release.yml
vendored
@ -44,27 +44,53 @@ jobs:
|
||||
# write so subsequent automation hooks have it if needed.
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
SOURCE_BRANCH: ${{ inputs.source_branch }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
# All workflow inputs are pulled in via env vars instead of being
|
||||
# interpolated directly into bash, to avoid command injection.
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
INPUT_SOURCE_BRANCH: ${{ inputs.source_branch }}
|
||||
INPUT_RELEASE_BRANCH: ${{ inputs.release_branch }}
|
||||
INPUT_TAG_NAME: ${{ inputs.tag_name }}
|
||||
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||
REPO_FULL: ${{ github.repository }}
|
||||
|
||||
steps:
|
||||
- name: Compute names
|
||||
id: names
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${VERSION#v}"
|
||||
VERSION="${INPUT_VERSION#v}"
|
||||
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Invalid version '$VERSION' — must match X.Y.Z"
|
||||
exit 1
|
||||
fi
|
||||
REL_BRANCH="${{ inputs.release_branch }}"
|
||||
|
||||
REL_BRANCH="$INPUT_RELEASE_BRANCH"
|
||||
[ -z "$REL_BRANCH" ] && REL_BRANCH="release/v${VERSION}"
|
||||
TAG_NAME="${{ inputs.tag_name }}"
|
||||
|
||||
TAG_NAME="$INPUT_TAG_NAME"
|
||||
[ -z "$TAG_NAME" ] && TAG_NAME="v${VERSION}"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "release_branch=$REL_BRANCH" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Validate the overrides too — they're effectively user-controlled
|
||||
# ref names and end up in shell + git invocations.
|
||||
REF_RE='^[A-Za-z0-9._/-]+$'
|
||||
if ! [[ "$REL_BRANCH" =~ $REF_RE ]]; then
|
||||
echo "::error::Invalid release_branch '$REL_BRANCH' — only [A-Za-z0-9._/-] allowed."
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$TAG_NAME" =~ $REF_RE ]]; then
|
||||
echo "::error::Invalid tag_name '$TAG_NAME' — only [A-Za-z0-9._/-] allowed."
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$INPUT_SOURCE_BRANCH" =~ $REF_RE ]]; then
|
||||
echo "::error::Invalid source_branch '$INPUT_SOURCE_BRANCH' — only [A-Za-z0-9._/-] allowed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "version=$VERSION"
|
||||
echo "release_branch=$REL_BRANCH"
|
||||
echo "tag_name=$TAG_NAME"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "Computed: version=$VERSION, release_branch=$REL_BRANCH, tag_name=$TAG_NAME"
|
||||
|
||||
- name: Pre-flight remote checks
|
||||
@ -73,11 +99,11 @@ jobs:
|
||||
TAG_NAME: ${{ steps.names.outputs.tag_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REPO_URL="https://github.com/${{ github.repository }}.git"
|
||||
REPO_URL="https://github.com/${REPO_FULL}.git"
|
||||
|
||||
# Source branch must exist.
|
||||
if ! git ls-remote --heads --exit-code "$REPO_URL" "$SOURCE_BRANCH" >/dev/null; then
|
||||
echo "::error::Source branch '$SOURCE_BRANCH' does not exist on origin."
|
||||
if ! git ls-remote --heads --exit-code "$REPO_URL" "$INPUT_SOURCE_BRANCH" >/dev/null; then
|
||||
echo "::error::Source branch '$INPUT_SOURCE_BRANCH' does not exist on origin."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -104,7 +130,7 @@ jobs:
|
||||
EXPECTED: ${{ steps.names.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PYPROJECT_VERSION=$(python3 -c "import tomllib,sys; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
||||
PYPROJECT_VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
||||
if [ "$PYPROJECT_VERSION" != "$EXPECTED" ]; then
|
||||
echo "::error::pyproject.toml version is '$PYPROJECT_VERSION' but expected '$EXPECTED'."
|
||||
exit 1
|
||||
@ -120,24 +146,27 @@ jobs:
|
||||
id: range
|
||||
env:
|
||||
REL_BRANCH: ${{ steps.names.outputs.release_branch }}
|
||||
TAG_NAME: ${{ steps.names.outputs.tag_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Find latest tag reachable from this commit (the basis backport
|
||||
# was branched from). Falls back to "(none)" gracefully.
|
||||
# was branched from). Falls back to "" gracefully.
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
SOURCE_SHA=$(git rev-parse HEAD)
|
||||
echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "source_sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "prev_tag=$PREV_TAG"
|
||||
echo "source_sha=$SOURCE_SHA"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Cut Release plan"
|
||||
echo ""
|
||||
echo "| Field | Value |"
|
||||
echo "| --- | --- |"
|
||||
echo "| Source branch | \`$SOURCE_BRANCH\` @ \`${SOURCE_SHA:0:12}\` |"
|
||||
echo "| Source branch | \`$INPUT_SOURCE_BRANCH\` @ \`${SOURCE_SHA:0:12}\` |"
|
||||
echo "| Target release branch | \`$REL_BRANCH\` |"
|
||||
echo "| Tag | \`${{ steps.names.outputs.tag_name }}\` |"
|
||||
echo "| Tag | \`$TAG_NAME\` |"
|
||||
echo "| Previous reachable tag | \`${PREV_TAG:-none}\` |"
|
||||
echo "| Dry run | \`$DRY_RUN\` |"
|
||||
echo "| Dry run | \`$INPUT_DRY_RUN\` |"
|
||||
echo ""
|
||||
echo "### Commits to be tagged"
|
||||
echo ""
|
||||
@ -157,16 +186,19 @@ jobs:
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Configure git identity
|
||||
if: ${{ !inputs.dry_run }}
|
||||
if: inputs.dry_run != true && inputs.dry_run != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Push release branch
|
||||
if: ${{ !inputs.dry_run }}
|
||||
- name: Push release branch and tag (atomic)
|
||||
if: inputs.dry_run != true && inputs.dry_run != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN }}
|
||||
REL_BRANCH: ${{ steps.names.outputs.release_branch }}
|
||||
TAG_NAME: ${{ steps.names.outputs.tag_name }}
|
||||
VERSION: ${{ steps.names.outputs.version }}
|
||||
SOURCE_SHA: ${{ steps.range.outputs.source_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@ -174,50 +206,44 @@ jobs:
|
||||
echo "::error::secrets.RELEASE_BOT_TOKEN is not set."
|
||||
exit 1
|
||||
fi
|
||||
AUTH_URL="https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
git push "$AUTH_URL" "${SOURCE_SHA}:refs/heads/${REL_BRANCH}"
|
||||
|
||||
- name: Create and push annotated tag
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN }}
|
||||
TAG_NAME: ${{ steps.names.outputs.tag_name }}
|
||||
VERSION: ${{ steps.names.outputs.version }}
|
||||
SOURCE_SHA: ${{ steps.range.outputs.source_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
AUTH_URL="https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
# Create the annotated tag locally first so we can push branch +
|
||||
# tag in a single atomic operation: either both refs are created
|
||||
# on origin, or neither — no half-promoted state.
|
||||
git tag -a "$TAG_NAME" "$SOURCE_SHA" -m "ComfyUI v${VERSION}"
|
||||
git push "$AUTH_URL" "refs/tags/${TAG_NAME}"
|
||||
|
||||
AUTH_URL="https://x-access-token:${GH_TOKEN}@github.com/${REPO_FULL}.git"
|
||||
git push --atomic "$AUTH_URL" \
|
||||
"${SOURCE_SHA}:refs/heads/${REL_BRANCH}" \
|
||||
"refs/tags/${TAG_NAME}"
|
||||
|
||||
- name: Delete source branch (success only)
|
||||
if: ${{ success() && !inputs.dry_run }}
|
||||
if: success() && inputs.dry_run != true && inputs.dry_run != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
AUTH_URL="https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
git push "$AUTH_URL" --delete "refs/heads/${SOURCE_BRANCH}"
|
||||
AUTH_URL="https://x-access-token:${GH_TOKEN}@github.com/${REPO_FULL}.git"
|
||||
git push "$AUTH_URL" --delete "refs/heads/${INPUT_SOURCE_BRANCH}"
|
||||
|
||||
- name: Final summary
|
||||
if: ${{ always() }}
|
||||
if: always()
|
||||
env:
|
||||
REL_BRANCH: ${{ steps.names.outputs.release_branch }}
|
||||
TAG_NAME: ${{ steps.names.outputs.tag_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REPO="${{ github.repository }}"
|
||||
{
|
||||
echo ""
|
||||
echo "### Result"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
if [ "$INPUT_DRY_RUN" = "true" ]; then
|
||||
echo "🔍 **Dry run** — no branch, tag, or deletion was performed."
|
||||
else
|
||||
echo "✅ Promoted \`$SOURCE_BRANCH\` to \`$REL_BRANCH\` and tagged \`$TAG_NAME\`."
|
||||
echo "✅ Promoted \`$INPUT_SOURCE_BRANCH\` to \`$REL_BRANCH\` and tagged \`$TAG_NAME\`."
|
||||
echo ""
|
||||
echo "- Branch: <https://github.com/${REPO}/tree/${REL_BRANCH}>"
|
||||
echo "- Tag: <https://github.com/${REPO}/releases/tag/${TAG_NAME}>"
|
||||
echo "- Branch: <https://github.com/${REPO_FULL}/tree/${REL_BRANCH}>"
|
||||
echo "- Tag: <https://github.com/${REPO_FULL}/releases/tag/${TAG_NAME}>"
|
||||
echo ""
|
||||
echo "Next: run [release-stable-all.yml](https://github.com/${REPO}/actions/workflows/release-stable-all.yml) with \`git_tag=${TAG_NAME}\`."
|
||||
echo "Next: run [release-stable-all.yml](https://github.com/${REPO_FULL}/actions/workflows/release-stable-all.yml) with \`git_tag=${TAG_NAME}\`."
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user