Skip to content

Commit 5625adc

Browse files
fix: handle missing release tags safely (#182)
* fix: handle missing release tags safely * fix: inspect raw release-tag lookup responses
1 parent 9eb2fa2 commit 5625adc

2 files changed

Lines changed: 114 additions & 6 deletions

File tree

.github/workflows/node-release-cut.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,35 @@ jobs:
109109
run: |
110110
set -euo pipefail
111111
112-
existing_ref="$(
113-
gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG" \
114-
--jq '[.object.type, .object.sha] | @tsv' 2>/dev/null || true
112+
query_status=0
113+
tag_response="$(
114+
gh api --include \
115+
"repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG" 2>/dev/null
116+
)" || query_status=$?
117+
118+
IFS=' ' read -r _ response_status _ <<< "$tag_response" || true
119+
response_body="$(
120+
awk 'body { print } /^\r?$/ { body = 1 }' <<< "$tag_response"
115121
)"
116122
123+
if [[ "$query_status" == 0 && "$response_status" =~ ^2[0-9]{2}$ ]]; then
124+
if ! existing_ref="$(
125+
jq -er '[.object.type, .object.sha] | @tsv' <<< "$response_body"
126+
)"; then
127+
echo "Unable to query release tag $RELEASE_TAG." >&2
128+
exit 1
129+
fi
130+
elif [[ "$query_status" != 0 && "$response_status" == 404 &&
131+
"$(
132+
jq -r '.status // empty' <<< "$response_body" \
133+
2>/dev/null || true
134+
)" == 404 ]]; then
135+
existing_ref=""
136+
else
137+
echo "Unable to query release tag $RELEASE_TAG." >&2
138+
exit 1
139+
fi
140+
117141
if [[ -n "$existing_ref" ]]; then
118142
IFS=$'\t' read -r tag_type tag_object <<< "$existing_ref"
119143
if [[ ! "$tag_object" =~ ^[0-9a-f]{40}$ ]]; then

sdk/typescript/tests-ts/release-automation.test.ts

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,83 @@ describe("GitHub release workflow safeguards", () => {
15661566
},
15671567
);
15681568

1569+
test.each([
1570+
{
1571+
kind: "missing tag with GitHub's 404 error body",
1572+
lookupResponse: JSON.stringify({ message: "Not Found", status: "404" }),
1573+
lookupHttpStatus: "404",
1574+
status: 0,
1575+
},
1576+
{
1577+
kind: "forbidden tag lookup",
1578+
lookupResponse: JSON.stringify({ message: "Forbidden", status: "403" }),
1579+
lookupHttpStatus: "403",
1580+
status: 1,
1581+
},
1582+
{
1583+
kind: "malformed missing-tag response",
1584+
lookupResponse: "not JSON",
1585+
lookupHttpStatus: "404",
1586+
status: 1,
1587+
},
1588+
{
1589+
kind: "unavailable tag lookup",
1590+
lookupResponse: "",
1591+
lookupHttpStatus: "",
1592+
status: 1,
1593+
},
1594+
])(
1595+
"handles $kind safely before cutting a release",
1596+
({ lookupResponse, lookupHttpStatus, status }) => {
1597+
const script = workflowStepShell(
1598+
releaseCutWorkflow,
1599+
"Create the exact merged release tag",
1600+
);
1601+
const mock = [
1602+
"gh() {",
1603+
' if [[ "$1" != "api" ]]; then return 64; fi',
1604+
" shift",
1605+
' if [[ "$1" == "--include" &&',
1606+
' "$2" == "repos/test/codex-security/git/ref/tags/npm-v0.1.2" ]]; then',
1607+
' if [[ -n "$MOCK_LOOKUP_HTTP_STATUS" ]]; then',
1608+
" printf 'HTTP/2.0 %s Mock\\nContent-Type: application/json\\n\\n%s\\n' \\",
1609+
' "$MOCK_LOOKUP_HTTP_STATUS" "$MOCK_LOOKUP_RESPONSE"',
1610+
" fi",
1611+
" return 1",
1612+
" fi",
1613+
' if [[ "$1" != "--method" || "$2" != "POST" ||',
1614+
' "$3" != "repos/test/codex-security/git/refs" ||',
1615+
' "$4" != "-f" || "$5" != "ref=refs/tags/npm-v0.1.2" ||',
1616+
' "$6" != "-f" || "$7" != "sha=$GITHUB_SHA" ||',
1617+
' "$8" != "--silent" ]]; then return 65; fi',
1618+
" printf 'created exact release tag\\n'",
1619+
"}",
1620+
].join("\n");
1621+
const result = spawnSync("bash", ["-c", `${mock}\n${script}`], {
1622+
encoding: "utf8",
1623+
env: {
1624+
...process.env,
1625+
GITHUB_REPOSITORY: "test/codex-security",
1626+
GITHUB_SHA: releaseCommit,
1627+
MOCK_LOOKUP_HTTP_STATUS: lookupHttpStatus,
1628+
MOCK_LOOKUP_RESPONSE: lookupResponse,
1629+
RELEASE_TAG: "npm-v0.1.2",
1630+
},
1631+
timeout: 10_000,
1632+
});
1633+
1634+
expect(result.status).toBe(status);
1635+
if (status === 0) {
1636+
expect(result.stdout).toContain("created exact release tag");
1637+
} else {
1638+
expect(result.stderr).toContain(
1639+
"Unable to query release tag npm-v0.1.2.",
1640+
);
1641+
expect(result.stdout).not.toContain("created exact release tag");
1642+
}
1643+
},
1644+
);
1645+
15691646
test.each([
15701647
{
15711648
kind: "existing lightweight tag",
@@ -1599,10 +1676,17 @@ describe("GitHub release workflow safeguards", () => {
15991676
"gh() {",
16001677
' if [[ "$1" != "api" ]]; then return 64; fi',
16011678
" shift",
1679+
' if [[ "$1" == "--include" ]]; then',
1680+
" shift",
1681+
' if [[ "$1" != "repos/test/codex-security/git/ref/tags/npm-v0.1.2" ]]; then',
1682+
" return 65",
1683+
" fi",
1684+
" printf 'HTTP/2.0 200 OK\\nContent-Type: application/json\\n\\n'",
1685+
' printf \'{"object":{"type":"%s","sha":"%s"}}\\n\' \\',
1686+
' "$MOCK_TAG_TYPE" "$MOCK_TAG_OBJECT"',
1687+
" return 0",
1688+
" fi",
16021689
' case "$1" in',
1603-
' "repos/test/codex-security/git/ref/tags/npm-v0.1.2")',
1604-
' printf \'%s\\t%s\\n\' "$MOCK_TAG_TYPE" "$MOCK_TAG_OBJECT"',
1605-
" ;;",
16061690
" repos/test/codex-security/git/tags/*)",
16071691
" printf '%s\\n' \"$MOCK_PEELED_COMMIT\"",
16081692
" ;;",

0 commit comments

Comments
 (0)