mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
262 lines
10 KiB
Bash
262 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
gh_with_retry() {
|
|
local stdout stderr_file stderr_output output status attempt
|
|
for attempt in 1 2 3 4 5; do
|
|
stderr_file="$(mktemp)"
|
|
set +e
|
|
stdout="$(gh "$@" 2>"$stderr_file")"
|
|
status=$?
|
|
set -e
|
|
if [[ "$status" -eq 0 ]]; then
|
|
if [[ -s "$stderr_file" ]]; then
|
|
cat "$stderr_file" >&2
|
|
fi
|
|
rm -f "$stderr_file"
|
|
printf '%s\n' "$stdout"
|
|
return 0
|
|
fi
|
|
stderr_output="$(cat "$stderr_file")"
|
|
rm -f "$stderr_file"
|
|
output="$stdout"
|
|
if [[ -n "$stderr_output" ]]; then
|
|
output+="${output:+$'\n'}${stderr_output}"
|
|
fi
|
|
if [[ "$output" =~ $GH_TRANSIENT_SERVER_OR_NETWORK_PATTERN ]]; then
|
|
echo "::warning::Transient GitHub response from gh $* on attempt ${attempt}; retrying." >&2
|
|
sleep $((attempt * 3))
|
|
continue
|
|
fi
|
|
printf '%s\n' "$output" >&2
|
|
return "$status"
|
|
done
|
|
printf '%s\n' "$output" >&2
|
|
return "$status"
|
|
}
|
|
|
|
candidate_root="${CANDIDATE_ROOT:?}"
|
|
candidate_git_dir="${CANDIDATE_GIT_DIR:-}"
|
|
remote_git_dir="${candidate_git_dir:-.}"
|
|
candidate_sha="$TARGET_SHA"
|
|
if [[ -n "$candidate_git_dir" ]]; then
|
|
[[ "$(git -C "$candidate_git_dir" rev-parse HEAD)" == "$candidate_sha" ]]
|
|
fi
|
|
|
|
normalized_context_ref="${TARGET_CONTEXT_REF:-}"
|
|
normalized_context_ref="${normalized_context_ref#refs/heads/}"
|
|
normalized_context_ref="${normalized_context_ref#refs/tags/}"
|
|
context_release_branch=""
|
|
context_release_tag=""
|
|
frozen_release_branch_pattern=""
|
|
if [[ "$normalized_context_ref" =~ ^release/([0-9]{4}\.[0-9]+\.[0-9]+)$ ]]; then
|
|
release_version="${BASH_REMATCH[1]}"
|
|
release_version_pattern="${release_version//./\.}"
|
|
candidate_version="$(jq -er '.version' "${candidate_root}/package.json")"
|
|
if [[ "$candidate_version" == "$release_version" ]]; then
|
|
context_release_branch="$normalized_context_ref"
|
|
elif [[ "$candidate_version" =~ ^${release_version_pattern}-beta\.[0-9]+$ ]]; then
|
|
context_release_branch="$normalized_context_ref"
|
|
candidate_version_pattern="${candidate_version//./\.}"
|
|
frozen_release_branch_pattern="^release/${candidate_version_pattern}-code-frozen(-r[1-9][0-9]*)?$"
|
|
else
|
|
echo "Telegram candidate version ${candidate_version} does not belong to release ${release_version}." >&2
|
|
exit 1
|
|
fi
|
|
elif [[ "$normalized_context_ref" =~ ^extended-stable/([0-9]{4}\.[0-9]+\.33)$ ]]; then
|
|
context_version="${BASH_REMATCH[1]}"
|
|
candidate_version="$(jq -er '.version' "${candidate_root}/package.json")"
|
|
if [[ "$candidate_version" != "$context_version" ]]; then
|
|
echo "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}." >&2
|
|
exit 1
|
|
fi
|
|
context_release_branch="$normalized_context_ref"
|
|
elif [[ "$normalized_context_ref" =~ ^v([0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?)$ ]]; then
|
|
context_version="${BASH_REMATCH[1]}"
|
|
candidate_version="$(jq -er '.version' "${candidate_root}/package.json")"
|
|
if [[ "$candidate_version" != "$context_version" ]]; then
|
|
echo "Telegram candidate version ${candidate_version} does not match context ${normalized_context_ref}." >&2
|
|
exit 1
|
|
fi
|
|
context_release_tag="$normalized_context_ref"
|
|
fi
|
|
|
|
repository_owner="${GITHUB_REPOSITORY%%/*}"
|
|
repository_name="${GITHUB_REPOSITORY#*/}"
|
|
candidate_metadata_json="$(
|
|
# GraphQL expands these variables server-side, not in the shell.
|
|
# shellcheck disable=SC2016
|
|
gh_with_retry api graphql \
|
|
-f query='query($owner:String!,$name:String!,$oid:GitObjectID!){repository(owner:$owner,name:$name){object(oid:$oid){... on Commit{oid signature{isValid state signer{login}} associatedPullRequests(first:100){nodes{state headRefOid headRepository{nameWithOwner} baseRefName baseRepository{nameWithOwner} mergeCommit{oid} mergedBy{login}}}}}}}' \
|
|
-f owner="$repository_owner" \
|
|
-f name="$repository_name" \
|
|
-f oid="$candidate_sha"
|
|
)"
|
|
pr_head_count="$(
|
|
jq -er \
|
|
--arg repo "$GITHUB_REPOSITORY" \
|
|
--arg sha "$candidate_sha" \
|
|
'[.data.repository.object.associatedPullRequests.nodes[] |
|
|
select(.state == "OPEN" and .headRepository.nameWithOwner == $repo and
|
|
.headRefOid == $sha)] | length' \
|
|
<<<"$candidate_metadata_json"
|
|
)"
|
|
if [[ "$pr_head_count" != "0" ]]; then
|
|
echo "Telegram candidate ${candidate_sha} is an open same-repository PR head." >&2
|
|
exit 1
|
|
fi
|
|
|
|
compare_status="$(
|
|
gh_with_retry api \
|
|
"repos/${GITHUB_REPOSITORY}/compare/${candidate_sha}...main" \
|
|
--jq '.status'
|
|
)"
|
|
trusted_reason=""
|
|
trusted_release_branch=""
|
|
if [[ -n "$context_release_branch" ]]; then
|
|
branch_sha="$(
|
|
git -C "$remote_git_dir" ls-remote --exit-code --refs origin \
|
|
"refs/heads/${context_release_branch}" |
|
|
awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }' ||
|
|
true
|
|
)"
|
|
if [[ "$branch_sha" == "$candidate_sha" ]]; then
|
|
trusted_reason="release-branch-head"
|
|
trusted_release_branch="$context_release_branch"
|
|
fi
|
|
elif [[ -n "$context_release_tag" ]]; then
|
|
tag_refs="$(
|
|
git -C "$remote_git_dir" ls-remote --exit-code origin \
|
|
"refs/tags/${context_release_tag}" "refs/tags/${context_release_tag}^{}"
|
|
)"
|
|
awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \
|
|
<<<"$tag_refs"
|
|
trusted_reason="release-tag"
|
|
elif [[ "$compare_status" == "ahead" || "$compare_status" == "identical" ]]; then
|
|
trusted_reason="main-ancestor"
|
|
else
|
|
normalized_ref="${TARGET_REF#refs/heads/}"
|
|
if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]]; then
|
|
branch_sha="$(
|
|
git -C "$remote_git_dir" ls-remote --exit-code --refs origin \
|
|
"refs/heads/${normalized_ref}" |
|
|
awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }'
|
|
)"
|
|
[[ "$branch_sha" == "$candidate_sha" ]]
|
|
trusted_reason="release-branch-head"
|
|
trusted_release_branch="$normalized_ref"
|
|
elif [[ "$TARGET_REF" =~ ^refs/tags/v ]] || [[ "$TARGET_REF" =~ ^v ]]; then
|
|
normalized_tag="${TARGET_REF#refs/tags/}"
|
|
tag_refs="$(
|
|
git -C "$remote_git_dir" ls-remote --exit-code origin \
|
|
"refs/tags/${normalized_tag}" "refs/tags/${normalized_tag}^{}"
|
|
)"
|
|
awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \
|
|
<<<"$tag_refs"
|
|
trusted_reason="release-tag"
|
|
elif [[ "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then
|
|
matching_release_branches="$(
|
|
gh_with_retry api --paginate \
|
|
"repos/${GITHUB_REPOSITORY}/commits/${candidate_sha}/branches-where-head" \
|
|
--jq '.[].name' |
|
|
awk '$0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ ||
|
|
$0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/ { print }'
|
|
)"
|
|
if [[ "$(wc -l <<<"$matching_release_branches" | tr -d ' ')" == "1" &&
|
|
-n "$matching_release_branches" ]]; then
|
|
trusted_reason="release-branch-head"
|
|
trusted_release_branch="$matching_release_branches"
|
|
else
|
|
matching_release_tags="$(
|
|
git -C "$remote_git_dir" ls-remote origin 'refs/tags/v*' |
|
|
awk -v sha="$candidate_sha" '$1 == sha { sub(/\^\{\}$/, "", $2); print $2 }' |
|
|
sort -u
|
|
)"
|
|
if [[ -n "$matching_release_tags" ]]; then
|
|
trusted_reason="release-tag"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$trusted_reason" && -n "$frozen_release_branch_pattern" &&
|
|
"$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then
|
|
matching_frozen_release_branches="$(
|
|
gh_with_retry api --paginate \
|
|
"repos/${GITHUB_REPOSITORY}/commits/${candidate_sha}/branches-where-head" \
|
|
--jq '.[].name' |
|
|
awk -v frozen="$frozen_release_branch_pattern" '$0 ~ frozen { print }'
|
|
)"
|
|
if [[ "$(wc -l <<<"$matching_frozen_release_branches" | tr -d ' ')" == "1" &&
|
|
-n "$matching_frozen_release_branches" ]]; then
|
|
trusted_reason="frozen-release-branch-head"
|
|
trusted_release_branch="$matching_frozen_release_branches"
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$trusted_reason" ]]; then
|
|
echo "Telegram candidate ${candidate_sha} is not trusted release provenance." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$trusted_reason" != "main-ancestor" ]]; then
|
|
signature_status="$(
|
|
jq -er \
|
|
--arg sha "$candidate_sha" \
|
|
'.data.repository.object |
|
|
select(.oid == $sha) |
|
|
if .signature == null then "missing"
|
|
elif .signature.isValid == true and .signature.state == "VALID" and
|
|
(.signature.signer.login // "") != "" then "valid"
|
|
else "invalid"
|
|
end' \
|
|
<<<"$candidate_metadata_json"
|
|
)"
|
|
if [[ "$signature_status" == "invalid" ]]; then
|
|
echo "Release candidate ${candidate_sha} has an invalid commit signature." >&2
|
|
exit 1
|
|
fi
|
|
signer="$(jq -r '.data.repository.object.signature.signer.login // ""' <<<"$candidate_metadata_json")"
|
|
if [[ "$trusted_reason" == "frozen-release-branch-head" &&
|
|
( "$signature_status" != "valid" || "$signer" == "web-flow" ) ]]; then
|
|
echo "Frozen release candidate ${candidate_sha} requires a valid maintainer signature." >&2
|
|
exit 1
|
|
fi
|
|
permission_actor="$signer"
|
|
if [[ "$signature_status" == "missing" || "$signer" == "web-flow" ]]; then
|
|
if [[ "$trusted_reason" != "release-branch-head" || -z "$trusted_release_branch" ]]; then
|
|
echo "Unsigned or GitHub web-flow candidates require an exact release branch head." >&2
|
|
exit 1
|
|
fi
|
|
matching_merge_prs="$(
|
|
jq -c \
|
|
--arg repo "$GITHUB_REPOSITORY" \
|
|
--arg sha "$candidate_sha" \
|
|
'[.data.repository.object.associatedPullRequests.nodes[] |
|
|
select(.state == "MERGED" and .baseRepository.nameWithOwner == $repo and
|
|
.mergeCommit.oid == $sha)]' \
|
|
<<<"$candidate_metadata_json"
|
|
)"
|
|
if [[ "$(jq 'length' <<<"$matching_merge_prs")" != "1" ]]; then
|
|
echo "Unsigned or GitHub web-flow candidate ${candidate_sha} requires one exact merged same-repository PR." >&2
|
|
exit 1
|
|
fi
|
|
permission_actor="$(
|
|
jq -er '.[0].mergedBy.login | select(type == "string" and length > 0)' \
|
|
<<<"$matching_merge_prs"
|
|
)"
|
|
fi
|
|
permission_json="$(
|
|
gh_with_retry api \
|
|
"repos/${GITHUB_REPOSITORY}/collaborators/${permission_actor}/permission"
|
|
)"
|
|
permission="$(jq -r '.permission // ""' <<<"$permission_json")"
|
|
role_name="$(jq -r '.role_name // ""' <<<"$permission_json")"
|
|
if [[ "$permission" != "admin" && "$role_name" != "maintain" ]]; then
|
|
echo "Release candidate actor ${permission_actor} lacks maintain/admin access." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "Telegram candidate trust reason: ${trusted_reason}"
|