11#! /usr/bin/env bash
22
33# DigitalOcean NS8-CI cleanup candidate lister (and optional deleter)
4- # Requirements: doctl, jq
4+ # Requirements: curl, jq
5+ # This script does not use doctl because it does not run well inside GitHub Actions due
6+ # to lack of a TTY. Instead, it uses direct API calls with curl.
57#
68# Usage:
7- # ./doctl- ns8-ci.sh # just list
8- # ./doctl- ns8-ci.sh --delete # list and delete
9+ # ./ns8-ci.sh # just list
10+ # ./ns8-ci.sh --delete # list and delete
911
1012DO_DOMAIN=" ci.nethserver.net"
1113TAG_PREFIX=" NS8-CI-"
1214DELETE=0
15+ DO_API_BASE=" https://api.digitalocean.com/v2"
16+
17+
18+ # Function to make authenticated API calls to DigitalOcean
19+ do_api () {
20+ local method=" ${1:- GET} "
21+ local endpoint=" $2 "
22+ local data=" $3 "
23+
24+ local curl_args=(
25+ -s
26+ -H " Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN "
27+ -H " Content-Type: application/json"
28+ -X " $method "
29+ )
30+
31+ if [[ -n " $data " ]]; then
32+ curl_args+=(-d " $data " )
33+ fi
34+
35+ # Check if the endpoint already contains a '?'
36+ if [[ " $endpoint " == * \? * ]]; then
37+ curl " ${curl_args[@]} " " $DO_API_BASE$endpoint &page=1&per_page=200"
38+ else
39+ curl " ${curl_args[@]} " " $DO_API_BASE$endpoint ?page=1&per_page=200"
40+ fi
41+ }
1342
1443if [[ " $1 " == " --delete" ]]; then
1544 DELETE=1
1645fi
1746
1847set -e
19- # Default $doctl_cmd context (can be overridden by exporting DOCTL_CONTEXT)
20- DOCTL_CONTEXT=" ${DOCTL_CONTEXT:- sviluppo} "
21- doctl_cmd=" doctl --context $DOCTL_CONTEXT "
2248
23- # Check if doctl is installed
24- if ! command -v doctl & > /dev/null; then
25- echo " doctl could not be found. Please install doctl and configure it with access token."
49+ # Get the DigitalOcean access token from environment
50+ if [[ -z " $DIGITALOCEAN_ACCESS_TOKEN " ]]; then
51+ echo " DigitalOcean access token not found. Please set DIGITALOCEAN_ACCESS_TOKEN environment variable."
52+ exit 1
53+ fi
54+
55+ # Check if curl is installed
56+ if ! command -v curl & > /dev/null; then
57+ echo " curl could not be found. Please install curl."
2658 exit 1
2759fi
2860
@@ -32,50 +64,44 @@ if ! command -v jq &> /dev/null; then
3264 exit 1
3365fi
3466
35-
36- # Check if doctl can access DigitalOcean
37- if ! $doctl_cmd account get & > /dev/null; then
38- if [[ -z " $DIGITALOCEAN_ACCESS_TOKEN " ]]; then
39- echo " doctl could not access DigitalOcean. Please use 'doctl auth init' to configure it or set the DIGITALOCEAN_ACCESS_TOKEN environment variable."
40- exit 1
41- fi
42- echo $DIGITALOCEAN_ACCESS_TOKEN | $doctl_cmd auth init --interactive false
43- if ! $doctl_cmd account get & > /dev/null; then
44- echo " Auth failed."
45- exit 1
46- fi
67+ # Test API access by getting account info
68+ if ! do_api GET " /tags" | jq -e ' .tags' & > /dev/null; then
69+ echo " Failed to authenticate with DigitalOcean API. Please check your token."
70+ exit 1
4771fi
4872
4973echo " == 1. Unused tags starting with $TAG_PREFIX =="
50- mapfile -t ns8_tags < <( $doctl_cmd compute tag list --format Name --no-header | grep " ^$TAG_PREFIX " || true)
74+ # Get all tags and filter for NS8-CI prefix
75+ mapfile -t ns8_tags < <( do_api GET " /tags" | jq -r ' .tags[] | select(.name | startswith("' $TAG_PREFIX ' ")) | .name' )
76+
5177# Remove unused tags (no droplets attached) as a first step
5278for tag in " ${ns8_tags[@]} " ; do
53- mapfile -t tag_droplets_check < <( $doctl_cmd compute droplet list --tag-name " $tag " --format ID --no-header || true)
79+ # Get droplets with this tag
80+ mapfile -t tag_droplets_check < <( do_api GET " /droplets?tag_name=$tag " | jq -r ' .droplets[] | .id' )
5481 if [[ ${# tag_droplets_check[@]} -eq 0 ]]; then
5582 echo " Unused tag: $tag "
5683 if [[ $DELETE -eq 1 ]]; then
5784 echo " -> Deleting tag $tag "
58- $doctl_cmd compute tag delete " $tag " -f || true
85+ do_api DELETE " /tags/ $tag " || true
5986 fi
6087 fi
6188done
6289
6390echo " == 2. Droplets with tags starting with $TAG_PREFIX (only 'active' and running > 3h) =="
64- mapfile -t ns8_tags < <( $doctl_cmd compute tag list --format Name --no-header | grep " ^$TAG_PREFIX " || true)
91+ # Get all tags with NS8-CI prefix again (in case some were deleted)
92+ mapfile -t ns8_tags < <( do_api GET " /tags" | jq -r ' .tags[] | select(.name | startswith("' $TAG_PREFIX ' ")) | .name' )
6593droplet_ids=()
6694droplet_names=()
6795# threshold in seconds (3 hours)
6896THRESHOLD_SECONDS=10800
6997for tag in " ${ns8_tags[@]} " ; do
70- # Request fields via JSON so we reliably get created_at; parse with jq to: ID Status CreatedAt Name
71- mapfile -t tag_droplets < <( $doctl_cmd compute droplet list --tag-name " $tag " -o json | jq -r ' .[] | "\(.id) \(.status) \(.created_at) \(.name)"' )
98+ # Get droplets with this tag
99+ mapfile -t tag_droplets < <( do_api GET " /droplets?tag_name= $tag " | jq -r ' .droplets [] | "\(.id) \(.status) \(.created_at) \(.name)"' )
72100 for entry in " ${tag_droplets[@]} " ; do
73101 id=$( echo " $entry " | awk ' {print $1}' )
74102 status=$( echo " $entry " | awk ' {print $2}' )
75103 created_at=$( echo " $entry " | awk ' {print $3}' )
76104 name=$( echo " $entry " | cut -d' ' -f4-)
77- echo " $entry "
78- echo " id=$id status=$status created_at=$created_at name=$name "
79105
80106 # Skip if we couldn't parse fields
81107 if [[ -z " $id " || -z " $created_at " ]]; then
@@ -104,15 +130,16 @@ for tag in "${ns8_tags[@]}"; do
104130 echo " Droplet: $name ($id ) [tag: $tag ] status=$status age=${age_hours} h"
105131 if [[ $DELETE -eq 1 ]]; then
106132 echo " -> Deleting droplet $name ($id )"
107- $doctl_cmd compute droplet delete " $id " -f
133+ do_api DELETE " /droplets/ $id "
108134 fi
109135 fi
110136 done
111137done
112138
113139echo " "
114140echo " == 3. DNS records in $DO_DOMAIN without a running droplet =="
115- mapfile -t records < <( $doctl_cmd compute domain records list " $DO_DOMAIN " --format ID,Type,Name --no-header)
141+ # Get all DNS records for the domain
142+ mapfile -t records < <( do_api GET " /domains/$DO_DOMAIN /records" | jq -r ' .domain_records[] | "\(.id) \(.type) \(.name)"' )
116143for record in " ${records[@]} " ; do
117144 id=$( echo " $record " | awk ' {print $1}' )
118145 type=$( echo " $record " | awk ' {print $2}' )
@@ -129,24 +156,26 @@ for record in "${records[@]}"; do
129156 echo " Orphan DNS $type record: $name .$DO_DOMAIN (record id: $id )"
130157 if [[ $DELETE -eq 1 ]]; then
131158 echo " -> Deleting DNS record $id ($name .$DO_DOMAIN )"
132- $doctl_cmd compute domain records delete " $DO_DOMAIN " " $id " -f
159+ do_api DELETE " /domains/ $DO_DOMAIN /records/ $id "
133160 fi
134161 fi
135162 fi
136163done
137164
138165echo " "
139- echo " == 3. SSH keys with names matching '^*ci.nethserver.net-deploy' not used by any droplet =="
140- # Collect SSH keys matching '.ci.nethserver.net' using the same template you provided
141- mapfile -t ssh_keys_raw < <( $doctl_cmd compute ssh-key list --format ID,Name --no-header | grep ' \.ci\.nethserver\.net' || true)
142- mapfile -t all_droplet_ids < <( $doctl_cmd compute droplet list --format ID --no-header)
166+ echo " == 4. SSH keys with names matching '*.ci.nethserver.net' not used by any droplet =="
167+ # Get SSH keys matching '.ci.nethserver.net'
168+ mapfile -t ssh_keys_raw < <( do_api GET " /account/keys" | jq -r ' .ssh_keys[] | select(.name | contains(".ci.nethserver.net")) | "\(.id) \(.name)"' )
169+ # Get all droplet IDs for checking SSH key usage
170+ mapfile -t all_droplet_ids < <( do_api GET " /droplets" | jq -r ' .droplets[] | .id' )
143171
144172for ssh_entry in " ${ssh_keys_raw[@]} " ; do
145173 ssh_id=$( echo " $ssh_entry " | awk ' {print $1}' )
146174 ssh_name=$( echo " $ssh_entry " | cut -d' ' -f2-)
147175 ssh_used=0
148176 for droplet_id in " ${all_droplet_ids[@]} " ; do
149- mapfile -t droplet_keys < <( $doctl_cmd compute droplet get " $droplet_id " --format SSHKeys --no-header | tr ' ,' ' \n' | awk ' {print $1}' )
177+ # Get droplet details to check SSH keys
178+ mapfile -t droplet_keys < <( do_api GET " /droplets/$droplet_id " | jq -r ' .droplet.ssh_keys[]? | .id' )
150179 for dkey in " ${droplet_keys[@]} " ; do
151180 if [[ " $dkey " == " $ssh_id " ]]; then
152181 ssh_used=1
@@ -158,7 +187,7 @@ for ssh_entry in "${ssh_keys_raw[@]}"; do
158187 echo " Unused SSH key: $ssh_name ($ssh_id )"
159188 if [[ $DELETE -eq 1 ]]; then
160189 echo " -> Deleting SSH key $ssh_name ($ssh_id )"
161- $doctl_cmd compute ssh-key delete " $ssh_id " -f
190+ do_api DELETE " /account/keys/ $ssh_id "
162191 fi
163192 fi
164193done
0 commit comments