refactor: Deduplicate API retry logic in UpCloud and Scaleway wrappers (#89)

Add generic_cloud_api_custom_auth() to shared/common.sh for cloud
providers that use non-Bearer auth headers. Replace ~120 lines of
duplicated retry logic in upcloud_api() and scaleway_api() with
calls to the new shared function.

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-09 09:33:51 -08:00 committed by GitHub
parent 531a817bfe
commit 66701d3cf9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 110 deletions

View file

@ -30,66 +30,16 @@ readonly SCALEWAY_ACCOUNT_API="https://api.scaleway.com/account/v3"
# Configurable timeout/delay constants
INSTANCE_STATUS_POLL_DELAY=${INSTANCE_STATUS_POLL_DELAY:-5}
# Scaleway API wrapper (uses X-Auth-Token header instead of Bearer)
# Scaleway API wrapper (uses X-Auth-Token header instead of Bearer) with retry logic
# Takes a full URL (not base+endpoint) for flexibility across Scaleway API namespaces
scaleway_api() {
local method="$1"
local url="$2"
local body="${3:-}"
local max_retries="${4:-3}"
local attempt=1
local interval=2
local max_interval=30
while [[ "${attempt}" -le "${max_retries}" ]]; do
local args=(
-s
-w "\n%{http_code}"
-X "${method}"
-H "X-Auth-Token: ${SCW_SECRET_KEY}"
-H "Content-Type: application/json"
)
if [[ -n "${body}" ]]; then
args+=(-d "${body}")
fi
local response
response=$(curl "${args[@]}" "${url}" 2>&1)
local curl_exit_code=$?
local http_code
http_code=$(echo "${response}" | tail -1)
local response_body
response_body=$(echo "${response}" | head -n -1)
if [[ ${curl_exit_code} -ne 0 ]]; then
if ! _api_should_retry_on_error "${attempt}" "${max_retries}" "${interval}" "${max_interval}" "Scaleway API network error"; then
log_error "Scaleway API network error after ${max_retries} attempts"
return 1
fi
_update_retry_interval "interval" "max_interval"
attempt=$((attempt + 1))
continue
fi
if [[ "${http_code}" == "429" ]] || [[ "${http_code}" == "503" ]]; then
if ! _api_should_retry_on_error "${attempt}" "${max_retries}" "${interval}" "${max_interval}" "Scaleway API rate limited (HTTP ${http_code})"; then
log_error "Scaleway API returned HTTP ${http_code} after ${max_retries} attempts"
echo "${response_body}"
return 1
fi
_update_retry_interval "interval" "max_interval"
attempt=$((attempt + 1))
continue
fi
echo "${response_body}"
return 0
done
log_error "Scaleway API retry logic exhausted"
return 1
# Pass empty base_url since url is already complete
generic_cloud_api_custom_auth "" "$method" "$url" "$body" "$max_retries" \
-H "X-Auth-Token: ${SCW_SECRET_KEY}"
}
# Convenience wrapper for instance API

View file

@ -1116,6 +1116,80 @@ generic_cloud_api() {
return 1
}
# Helper to make API request with custom curl auth args (e.g., Basic Auth, custom headers)
# Returns: 0 on curl success, 1 on curl failure
# Sets: API_HTTP_CODE and API_RESPONSE_BODY globals
_make_api_request_custom_auth() {
local url="${1}"
local method="${2}"
local body="${3:-}"
shift 3
# Remaining args are custom curl auth flags (e.g., -u "user:pass" or -H "X-Auth-Token: ...")
local args=(
-s
-w "\n%{http_code}"
-X "${method}"
-H "Content-Type: application/json"
"$@"
)
if [[ -n "${body}" ]]; then
args+=(-d "${body}")
fi
local response
response=$(curl "${args[@]}" "${url}" 2>&1)
local curl_exit_code=$?
_parse_api_response "${response}"
return ${curl_exit_code}
}
# Generic cloud API wrapper with custom curl auth args
# Like generic_cloud_api but accepts arbitrary curl flags for authentication
# Usage: generic_cloud_api_custom_auth BASE_URL METHOD ENDPOINT BODY MAX_RETRIES AUTH_ARGS...
# Example: generic_cloud_api_custom_auth "$API_BASE" GET "/account" "" 3 -H "X-Auth-Token: $TOKEN"
# Example: generic_cloud_api_custom_auth "$API_BASE" POST "/servers" "$body" 3 -u "$USER:$PASS"
generic_cloud_api_custom_auth() {
local base_url="${1}"
local method="${2}"
local endpoint="${3}"
local body="${4:-}"
local max_retries="${5:-3}"
shift 5
# Remaining args are custom curl auth flags
local attempt=1
local interval=2
local max_interval=30
while [[ "${attempt}" -le "${max_retries}" ]]; do
if ! _make_api_request_custom_auth "${base_url}${endpoint}" "${method}" "${body}" "$@"; then
if ! _handle_api_transient_error "network" "${attempt}" "${max_retries}" "interval" "max_interval" ""; then
return 1
fi
attempt=$((attempt + 1))
continue
fi
if [[ "${API_HTTP_CODE}" == "429" ]] || [[ "${API_HTTP_CODE}" == "503" ]]; then
if ! _handle_api_transient_error "${API_HTTP_CODE}" "${attempt}" "${max_retries}" "interval" "max_interval" "${API_RESPONSE_BODY}"; then
return 1
fi
attempt=$((attempt + 1))
continue
fi
echo "${API_RESPONSE_BODY}"
return 0
done
log_error "Cloud API retry logic exhausted"
return 1
}
# ============================================================
# Agent verification helpers
# ============================================================

View file

@ -23,67 +23,15 @@ readonly UPCLOUD_API_BASE="https://api.upcloud.com/1.3"
# Configurable timeout/delay constants
INSTANCE_STATUS_POLL_DELAY=${INSTANCE_STATUS_POLL_DELAY:-5}
# UpCloud API wrapper using Basic Auth
# UpCloud API wrapper using Basic Auth with retry logic
# Usage: upcloud_api METHOD ENDPOINT [BODY] [MAX_RETRIES]
upcloud_api() {
local method="$1"
local endpoint="$2"
local body="${3:-}"
local max_retries="${4:-3}"
local attempt=1
local interval=2
local max_interval=30
while [[ "${attempt}" -le "${max_retries}" ]]; do
local args=(
-s
-w "\n%{http_code}"
-X "${method}"
-u "${UPCLOUD_USERNAME}:${UPCLOUD_PASSWORD}"
-H "Content-Type: application/json"
)
if [[ -n "${body}" ]]; then
args+=(-d "${body}")
fi
local response
response=$(curl "${args[@]}" "${UPCLOUD_API_BASE}${endpoint}" 2>&1)
local curl_exit_code=$?
local http_code
http_code=$(printf '%s' "${response}" | tail -1)
local response_body
response_body=$(printf '%s' "${response}" | head -n -1)
if [[ ${curl_exit_code} -ne 0 ]]; then
if ! _api_should_retry_on_error "${attempt}" "${max_retries}" "${interval}" "${max_interval}" "UpCloud API network error"; then
log_error "UpCloud API network error after ${max_retries} attempts: curl exit code ${curl_exit_code}"
return 1
fi
_update_retry_interval "interval" "max_interval"
attempt=$((attempt + 1))
continue
fi
if [[ "${http_code}" == "429" ]] || [[ "${http_code}" == "503" ]]; then
if ! _api_should_retry_on_error "${attempt}" "${max_retries}" "${interval}" "${max_interval}" "UpCloud API returned HTTP ${http_code}"; then
log_error "UpCloud API returned HTTP ${http_code} after ${max_retries} attempts"
echo "${response_body}"
return 1
fi
_update_retry_interval "interval" "max_interval"
attempt=$((attempt + 1))
continue
fi
echo "${response_body}"
return 0
done
log_error "UpCloud API retry logic exhausted"
return 1
generic_cloud_api_custom_auth "$UPCLOUD_API_BASE" "$method" "$endpoint" "$body" "$max_retries" \
-u "${UPCLOUD_USERNAME}:${UPCLOUD_PASSWORD}"
}
test_upcloud_credentials() {