Skip to content

Commit f9ed855

Browse files
authored
chore(ci): clean up account, archive old channel releases and instances (#2629)
* chore(ci): clean up account, archive old instances * f * f * f * f * f * f * f * f * f * f
1 parent 1a41573 commit f9ed855

File tree

3 files changed

+439
-0
lines changed

3 files changed

+439
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Cleanup Resources
2+
3+
on:
4+
schedule:
5+
# Run every 12 hours
6+
- cron: '0 */12 * * *'
7+
workflow_dispatch: # Allow manual triggering
8+
9+
jobs:
10+
archive-instances:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Run archive instances script
18+
env:
19+
REPLICATED_APP: "embedded-cluster-smoke-test-staging-app"
20+
REPLICATED_API_TOKEN: ${{ secrets.STAGING_REPLICATED_API_TOKEN }}
21+
REPLICATED_API_ORIGIN: "https://api.staging.replicated.com"
22+
DRY_RUN: "false"
23+
run: |
24+
# Make script executable and run it
25+
chmod +x ./scripts/archive-instances.sh
26+
./scripts/archive-instances.sh
27+
28+
demote-channel-releases:
29+
runs-on: ubuntu-latest
30+
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
35+
- name: Run demote channel releases script
36+
env:
37+
REPLICATED_APP: "embedded-cluster-smoke-test-staging-app"
38+
REPLICATED_API_TOKEN: ${{ secrets.STAGING_REPLICATED_API_TOKEN }}
39+
REPLICATED_API_ORIGIN: "https://api.staging.replicated.com"
40+
DRY_RUN: "false"
41+
run: |
42+
# Make script executable and run it
43+
chmod +x ./scripts/demote-channel-releases.sh
44+
./scripts/demote-channel-releases.sh

scripts/archive-instances.sh

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
START_PAGE=${START_PAGE:-1}
6+
PAGE_SIZE=${PAGE_SIZE:-100}
7+
DRY_RUN=${DRY_RUN:-true}
8+
REPLICATED_API_ORIGIN=${REPLICATED_API_ORIGIN:-"https://api.staging.replicated.com"}
9+
REPLICATED_APP=${REPLICATED_APP:-"embedded-cluster-smoke-test-staging-app"}
10+
11+
# Trim /vendor suffix if present
12+
REPLICATED_API_ORIGIN="${REPLICATED_API_ORIGIN%/vendor}"
13+
14+
# Calculate timestamp for 1 day ago (in seconds since epoch)
15+
if [[ "$OSTYPE" == "darwin"* ]]; then
16+
# macOS (darwin) syntax
17+
ONE_DAY_AGO=$(date -v-1d +%s)
18+
else
19+
# Linux syntax
20+
ONE_DAY_AGO=$(date -d '1 day ago' +%s)
21+
fi
22+
23+
function archive_instances() {
24+
echo "Fetching instances with lastCheckinAt greater than 1 day ago..."
25+
if [[ "$OSTYPE" == "darwin"* ]]; then
26+
echo "Timestamp for 1 day ago: $(date -v-1d)"
27+
else
28+
echo "Timestamp for 1 day ago: $(date -d '1 day ago')"
29+
fi
30+
echo "---"
31+
32+
local current_page=$START_PAGE
33+
34+
while true; do
35+
echo "Processing page $current_page..."
36+
37+
local response
38+
39+
# Get the JSON response and extract instances for current page
40+
response=$(curl -fsSL -H "Authorization: $REPLICATED_API_TOKEN" "$REPLICATED_API_ORIGIN/v1/instances?appSelector=$REPLICATED_APP&pageSize=$PAGE_SIZE&currentPage=$current_page")
41+
42+
# Check if we have instances on this page
43+
instance_count=$(echo "$response" | jq -r '.instances | length')
44+
if [[ "$instance_count" -eq 0 ]]; then
45+
echo "No instances found on page $current_page, stopping."
46+
break
47+
fi
48+
49+
echo "Found $instance_count instances on page $current_page"
50+
51+
local did_archive=false
52+
53+
# Loop through each instance using jq to extract the array
54+
echo "$response" | jq -c '.instances[]' | while read -r instance; do
55+
local id last_checkin timestamp instance_time id version channel
56+
57+
id=$(echo "$instance" | jq -r '.id')
58+
last_checkin=$(echo "$instance" | jq -r '.lastCheckinAt // empty')
59+
60+
# Skip if lastCheckinAt is null
61+
if [[ -z "$last_checkin" ]]; then
62+
echo "Warning: lastCheckinAt is empty for instance $id"
63+
continue
64+
fi
65+
66+
# Parse the timestamp and compare
67+
# Remove milliseconds and ensure proper UTC format
68+
timestamp=$(echo "$last_checkin" | sed 's/\.[0-9]*//' | sed 's/Z$//' | sed 's/$/Z/')
69+
70+
# Validate timestamp format before parsing
71+
if [[ ! "$timestamp" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then
72+
echo "Warning: Invalid timestamp format for instance $id: $last_checkin"
73+
continue
74+
fi
75+
76+
instance_time=$(parse_date "$timestamp" +%s)
77+
78+
# Skip if parsing failed (returns -1)
79+
if [[ "$instance_time" -eq -1 ]]; then
80+
echo "Warning: Failed to parse timestamp $timestamp for instance $id: $last_checkin"
81+
continue
82+
fi
83+
84+
# Check if instance hasn't checked in for more than 1 day
85+
if [[ "$instance_time" -lt "$ONE_DAY_AGO" ]]; then
86+
# Extract fields from each instance
87+
version=$(echo "$instance" | jq -r '.versionLabel // "N/A"')
88+
channel=$(echo "$instance" | jq -r '._embedded.channel.name // "N/A"')
89+
90+
if archive_instance "$id" "$timestamp" "$version" "$channel"; then
91+
did_archive=true
92+
fi
93+
fi
94+
done
95+
96+
# only increment page if we didn't archive an instance since we filter out instances that are archived
97+
if [[ "$did_archive" != "true" ]]; then
98+
current_page=$((current_page + 1))
99+
fi
100+
done
101+
102+
echo "Finished processing all pages."
103+
}
104+
105+
function archive_instance() {
106+
local id="$1"
107+
local timestamp="$2"
108+
local version="$3"
109+
local channel="$4"
110+
111+
local local_time
112+
113+
# Convert UTC timestamp to local time
114+
local_time=$(parse_date "$timestamp")
115+
116+
echo "Archiving instance: ID: $id, LastCheckin: $local_time, Version: $version, Channel: $channel"
117+
118+
if [[ "$DRY_RUN" == "true" ]]; then
119+
echo " ✓ DRY RUN"
120+
return 0
121+
else
122+
local archive_response http_code response_body
123+
124+
# Archive the instance
125+
archive_response=$(curl -sSL -w "%{http_code}" -X POST -H "Authorization: $REPLICATED_API_TOKEN" -H "Content-Type: application/json" "$REPLICATED_API_ORIGIN/vendor/v3/instance/$id/archive" -d '{}')
126+
http_code="${archive_response: -3}"
127+
response_body="${archive_response%???}"
128+
129+
if [[ "$http_code" -eq 200 ]]; then
130+
echo " ✓ Successfully archived instance $id"
131+
return 0
132+
else
133+
echo " ✗ Failed to archive instance $id (HTTP $http_code): $response_body"
134+
return 1
135+
fi
136+
fi
137+
}
138+
139+
# Cross-platform date parsing function
140+
function parse_date() {
141+
local date_string="$1"
142+
local format="${2:-}"
143+
144+
if [[ "$OSTYPE" == "darwin"* ]]; then
145+
# macOS (darwin) syntax
146+
if [[ -z "$format" ]]; then
147+
# Human readable format - treat as UTC and convert to local
148+
# First convert UTC to epoch, then epoch to local time
149+
epoch=$(date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$date_string" +%s 2>/dev/null)
150+
if [[ -n "$epoch" && "$epoch" != "0" ]]; then
151+
date -r "$epoch" 2>/dev/null || echo "$date_string"
152+
else
153+
echo "$date_string"
154+
fi
155+
else
156+
# Specific format - treat as UTC
157+
date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$date_string" "$format" 2>/dev/null || echo "-1"
158+
fi
159+
else
160+
# Linux syntax
161+
if [[ -z "$format" ]]; then
162+
# Human readable format
163+
date -d "$date_string" 2>/dev/null || echo "$date_string"
164+
else
165+
# Specific format
166+
date -d "$date_string" "$format" 2>/dev/null || echo "-1"
167+
fi
168+
fi
169+
}
170+
171+
function main() {
172+
archive_instances
173+
}
174+
175+
main "$@"

0 commit comments

Comments
 (0)