diff --git a/scaleway/lib/common.sh b/scaleway/lib/common.sh index 152342be..67f0ed25 100644 --- a/scaleway/lib/common.sh +++ b/scaleway/lib/common.sh @@ -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 diff --git a/shared/common.sh b/shared/common.sh index 93d09107..ade40fe3 100644 --- a/shared/common.sh +++ b/shared/common.sh @@ -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 # ============================================================ diff --git a/upcloud/lib/common.sh b/upcloud/lib/common.sh index 1019358f..45294eb1 100644 --- a/upcloud/lib/common.sh +++ b/upcloud/lib/common.sh @@ -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() {