Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ jobs:
run: |
bash bin/ci/droplet.sh destroy \
"${{ steps.droplet.outputs.DROPLET_ID }}" \
"${{ steps.droplet.outputs.SSH_KEY_ID }}"
"${{ steps.droplet.outputs.SSH_KEY_ID }}" \
"ci-${{ matrix.distro }}-${{ github.run_id }}"
64 changes: 57 additions & 7 deletions bin/ci/droplet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
#
# Usage:
# bin/ci/droplet.sh create <name> <image> <ssh_pub_key_file>
# bin/ci/droplet.sh destroy <droplet_id> [ssh_key_id]
# bin/ci/droplet.sh destroy <droplet_id> [ssh_key_id] [droplet_name]
# bin/ci/droplet.sh wait-ssh <ip> <ssh_private_key_file>
# bin/ci/droplet.sh run <ip> <ssh_private_key_file> <script>
# bin/ci/droplet.sh list
#
# Requires: DO_API_TOKEN env var
#
# create: Registers SSH key with DO, creates droplet, polls until active.
# Outputs: DROPLET_ID=xxx DROPLET_IP=xxx SSH_KEY_ID=xxx
# destroy: Deletes droplet and (optionally) SSH key from DO.
# create: Registers SSH key with DO, creates droplet (tagged baudbot-ci),
# polls until active. Outputs: DROPLET_ID=xxx DROPLET_IP=xxx SSH_KEY_ID=xxx
# destroy: Deletes droplet and (optionally) SSH key from DO. If droplet_id is
# empty but droplet_name is given, looks up the droplet by name.
# This handles cancelled CI runs where the ID was never captured.
# wait-ssh: Polls until SSH is reachable (up to 120s).
# run: Executes a script on the droplet via SSH.
# list: Lists all droplets tagged baudbot-ci.

set -euo pipefail

Expand Down Expand Up @@ -71,7 +75,8 @@ cmd_create() {
\"image\": \"$image\",
\"ssh_keys\": [$ssh_key_id],
\"backups\": false,
\"monitoring\": false
\"monitoring\": false,
\"tags\": [\"baudbot-ci\"]
}")

local droplet_id
Expand Down Expand Up @@ -111,11 +116,33 @@ print(v4[0]['ip_address'] if v4 else 'none')
echo "SSH_KEY_ID=$ssh_key_id"
}

# ── destroy <droplet_id> [ssh_key_id] ────────────────────────────────────────
# ── destroy <droplet_id> [ssh_key_id] [droplet_name] ─────────────────────────
# If droplet_id is empty but droplet_name is provided, looks up the droplet by
# name. This handles the case where a CI run was cancelled before the create
# step wrote the droplet ID to GITHUB_OUTPUT.
cmd_destroy() {
require_token
local droplet_id="${1:-}"
local ssh_key_id="${2:-}"
local droplet_name="${3:-}"

# If no ID but we have a name, look it up
if [ -z "$droplet_id" ] && [ -n "$droplet_name" ]; then
echo " No droplet ID, looking up by name: $droplet_name" >&2
local data
data=$(do_api GET "droplets?per_page=200&tag_name=baudbot-ci")
droplet_id=$(python3 -c "
import json, sys
for d in json.load(sys.stdin).get('droplets', []):
if d['name'] == '$droplet_name':
print(d['id'])
break
" <<< "$data" 2>/dev/null || true)

if [ -z "$droplet_id" ]; then
echo " No droplet found with name $droplet_name" >&2
fi
fi

if [ -n "$droplet_id" ]; then
local http_code
Expand Down Expand Up @@ -165,11 +192,34 @@ cmd_run() {
-i "$key_file" "root@$ip" bash -s < "$script"
}

# ── list ──────────────────────────────────────────────────────────────────────
cmd_list() {
require_token
local data
data=$(do_api GET "droplets?per_page=200&tag_name=baudbot-ci")

python3 -c "
import json, sys
droplets = json.load(sys.stdin).get('droplets', [])
if not droplets:
print(' No CI droplets found', file=sys.stderr)
sys.exit(0)
for d in droplets:
ip = 'no-ip'
for n in d.get('networks', {}).get('v4', []):
if n['type'] == 'public':
ip = n['ip_address']
break
print(f'{d[\"id\"]} {d[\"name\"]} {d[\"created_at\"]} {ip}')
" <<< "$data"
}

# ── Dispatch ──────────────────────────────────────────────────────────────────
case "${1:-}" in
create) shift; cmd_create "$@" ;;
destroy) shift; cmd_destroy "$@" ;;
wait-ssh) shift; cmd_wait_ssh "$@" ;;
run) shift; cmd_run "$@" ;;
*) die "Usage: droplet.sh {create|destroy|wait-ssh|run} ..." ;;
list) shift; cmd_list "$@" ;;
*) die "Usage: droplet.sh {create|destroy|wait-ssh|run|list} ..." ;;
esac