|
3 | 3 | # |
4 | 4 | # Usage: |
5 | 5 | # bin/ci/droplet.sh create <name> <image> <ssh_pub_key_file> |
6 | | -# bin/ci/droplet.sh destroy <droplet_id> [ssh_key_id] |
| 6 | +# bin/ci/droplet.sh destroy <droplet_id> [ssh_key_id] [droplet_name] |
7 | 7 | # bin/ci/droplet.sh wait-ssh <ip> <ssh_private_key_file> |
8 | 8 | # bin/ci/droplet.sh run <ip> <ssh_private_key_file> <script> |
| 9 | +# bin/ci/droplet.sh list |
9 | 10 | # |
10 | 11 | # Requires: DO_API_TOKEN env var |
11 | 12 | # |
12 | | -# create: Registers SSH key with DO, creates droplet, polls until active. |
13 | | -# Outputs: DROPLET_ID=xxx DROPLET_IP=xxx SSH_KEY_ID=xxx |
14 | | -# destroy: Deletes droplet and (optionally) SSH key from DO. |
| 13 | +# create: Registers SSH key with DO, creates droplet (tagged baudbot-ci), |
| 14 | +# polls until active. Outputs: DROPLET_ID=xxx DROPLET_IP=xxx SSH_KEY_ID=xxx |
| 15 | +# destroy: Deletes droplet and (optionally) SSH key from DO. If droplet_id is |
| 16 | +# empty but droplet_name is given, looks up the droplet by name. |
| 17 | +# This handles cancelled CI runs where the ID was never captured. |
15 | 18 | # wait-ssh: Polls until SSH is reachable (up to 120s). |
16 | 19 | # run: Executes a script on the droplet via SSH. |
| 20 | +# list: Lists all droplets tagged baudbot-ci. |
17 | 21 |
|
18 | 22 | set -euo pipefail |
19 | 23 |
|
@@ -71,7 +75,8 @@ cmd_create() { |
71 | 75 | \"image\": \"$image\", |
72 | 76 | \"ssh_keys\": [$ssh_key_id], |
73 | 77 | \"backups\": false, |
74 | | - \"monitoring\": false |
| 78 | + \"monitoring\": false, |
| 79 | + \"tags\": [\"baudbot-ci\"] |
75 | 80 | }") |
76 | 81 |
|
77 | 82 | local droplet_id |
@@ -111,11 +116,33 @@ print(v4[0]['ip_address'] if v4 else 'none') |
111 | 116 | echo "SSH_KEY_ID=$ssh_key_id" |
112 | 117 | } |
113 | 118 |
|
114 | | -# ── destroy <droplet_id> [ssh_key_id] ──────────────────────────────────────── |
| 119 | +# ── destroy <droplet_id> [ssh_key_id] [droplet_name] ───────────────────────── |
| 120 | +# If droplet_id is empty but droplet_name is provided, looks up the droplet by |
| 121 | +# name. This handles the case where a CI run was cancelled before the create |
| 122 | +# step wrote the droplet ID to GITHUB_OUTPUT. |
115 | 123 | cmd_destroy() { |
116 | 124 | require_token |
117 | 125 | local droplet_id="${1:-}" |
118 | 126 | local ssh_key_id="${2:-}" |
| 127 | + local droplet_name="${3:-}" |
| 128 | + |
| 129 | + # If no ID but we have a name, look it up |
| 130 | + if [ -z "$droplet_id" ] && [ -n "$droplet_name" ]; then |
| 131 | + echo " No droplet ID, looking up by name: $droplet_name" >&2 |
| 132 | + local data |
| 133 | + data=$(do_api GET "droplets?per_page=200&tag_name=baudbot-ci") |
| 134 | + droplet_id=$(python3 -c " |
| 135 | +import json, sys |
| 136 | +for d in json.load(sys.stdin).get('droplets', []): |
| 137 | + if d['name'] == '$droplet_name': |
| 138 | + print(d['id']) |
| 139 | + break |
| 140 | +" <<< "$data" 2>/dev/null || true) |
| 141 | + |
| 142 | + if [ -z "$droplet_id" ]; then |
| 143 | + echo " No droplet found with name $droplet_name" >&2 |
| 144 | + fi |
| 145 | + fi |
119 | 146 |
|
120 | 147 | if [ -n "$droplet_id" ]; then |
121 | 148 | local http_code |
@@ -165,11 +192,34 @@ cmd_run() { |
165 | 192 | -i "$key_file" "root@$ip" bash -s < "$script" |
166 | 193 | } |
167 | 194 |
|
| 195 | +# ── list ────────────────────────────────────────────────────────────────────── |
| 196 | +cmd_list() { |
| 197 | + require_token |
| 198 | + local data |
| 199 | + data=$(do_api GET "droplets?per_page=200&tag_name=baudbot-ci") |
| 200 | + |
| 201 | + python3 -c " |
| 202 | +import json, sys |
| 203 | +droplets = json.load(sys.stdin).get('droplets', []) |
| 204 | +if not droplets: |
| 205 | + print(' No CI droplets found', file=sys.stderr) |
| 206 | + sys.exit(0) |
| 207 | +for d in droplets: |
| 208 | + ip = 'no-ip' |
| 209 | + for n in d.get('networks', {}).get('v4', []): |
| 210 | + if n['type'] == 'public': |
| 211 | + ip = n['ip_address'] |
| 212 | + break |
| 213 | + print(f'{d[\"id\"]} {d[\"name\"]} {d[\"created_at\"]} {ip}') |
| 214 | +" <<< "$data" |
| 215 | +} |
| 216 | + |
168 | 217 | # ── Dispatch ────────────────────────────────────────────────────────────────── |
169 | 218 | case "${1:-}" in |
170 | 219 | create) shift; cmd_create "$@" ;; |
171 | 220 | destroy) shift; cmd_destroy "$@" ;; |
172 | 221 | wait-ssh) shift; cmd_wait_ssh "$@" ;; |
173 | 222 | run) shift; cmd_run "$@" ;; |
174 | | - *) die "Usage: droplet.sh {create|destroy|wait-ssh|run} ..." ;; |
| 223 | + list) shift; cmd_list "$@" ;; |
| 224 | + *) die "Usage: droplet.sh {create|destroy|wait-ssh|run|list} ..." ;; |
175 | 225 | esac |
0 commit comments