refactor: replace custom config loaders with shared helpers in 3 cloud libs (#445)

Migrate binarylane, northflank, and kamatera to use the shared
ensure_api_token_with_provider, _load_json_config_fields, and
_save_json_config helpers, removing ~120 lines of duplicated
token loading/saving/validation logic.

- binarylane: replace 50-line ensure_binarylane_token with
  ensure_api_token_with_provider + test_binarylane_token
- northflank: remove _load_northflank_config, _save_northflank_token,
  _northflank_login; consolidate into ensure_api_token_with_provider
  with test_northflank_token doing login + validation
- kamatera: replace inline python3 config loader with
  _load_json_config_fields, replace manual JSON save with
  _save_json_config

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-11 05:20:08 -08:00 committed by GitHub
parent c1e009a66e
commit f79de27bc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 157 deletions

View file

@ -35,55 +35,30 @@ binarylane_api() {
generic_cloud_api "$BINARYLANE_API_BASE" "$BINARYLANE_API_TOKEN" "$method" "$endpoint" "$body"
}
ensure_binarylane_token() {
# Check Python 3 is available (required for JSON parsing)
check_python_available || return 1
if [[ -n "${BINARYLANE_API_TOKEN:-}" ]]; then
log_info "Using BinaryLane API token from environment"
return 0
fi
local config_dir="$HOME/.config/spawn"
local config_file="$config_dir/binarylane.json"
if [[ -f "$config_file" ]]; then
local saved_key
saved_key=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('api_token',''))" "$config_file" 2>/dev/null)
if [[ -n "$saved_key" ]]; then
export BINARYLANE_API_TOKEN="$saved_key"
log_info "Using BinaryLane API token from $config_file"
return 0
fi
fi
echo ""
log_warn "BinaryLane API Token Required"
log_warn "Get your API token from: https://home.binarylane.com.au/api-info"
echo ""
local api_token
api_token=$(validated_read "Enter your BinaryLane API token: " validate_api_token) || return 1
export BINARYLANE_API_TOKEN="$api_token"
test_binarylane_token() {
local response
response=$(binarylane_api GET "/account")
if echo "$response" | grep -q '"account"'; then
log_info "API token validated"
else
log_error "Authentication failed: Invalid BinaryLane API token"
# Parse error details
local error_msg
error_msg=$(echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('message','No details available'))" 2>/dev/null || echo "Unable to parse error")
log_error "API Error: $error_msg"
log_warn "Remediation steps:"
log_warn " 1. Verify API token at: https://home.binarylane.com.au/api-info"
log_warn " 2. Ensure the token has read/write permissions"
log_warn " 3. Check token hasn't been revoked"
unset BINARYLANE_API_TOKEN
return 1
return 0
fi
mkdir -p "$config_dir"
printf '{\n "api_token": %s\n}\n' "$(json_escape "$api_token")" > "$config_file"
chmod 600 "$config_file"
log_info "API token saved to $config_file"
local error_msg
error_msg=$(echo "$response" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('message','No details available'))" 2>/dev/null || echo "Unable to parse error")
log_error "API Error: $error_msg"
log_error ""
log_error "How to fix:"
log_error " 1. Verify API token at: https://home.binarylane.com.au/api-info"
log_error " 2. Ensure the token has read/write permissions"
log_error " 3. Check token hasn't been revoked"
return 1
}
ensure_binarylane_token() {
ensure_api_token_with_provider \
"BinaryLane" \
"BINARYLANE_API_TOKEN" \
"$HOME/.config/spawn/binarylane.json" \
"https://home.binarylane.com.au/api-info" \
"test_binarylane_token"
}
# Check if SSH key is registered with BinaryLane

View file

@ -44,17 +44,8 @@ kamatera_api() {
# Returns 0 if loaded, 1 otherwise
_load_kamatera_config() {
local config_file="$1"
[[ -f "$config_file" ]] || return 1
local creds
creds=$(python3 -c "
import json, sys
d = json.load(open(sys.argv[1]))
print(d.get('api_client_id', ''))
print(d.get('api_secret', ''))
" "$config_file" 2>/dev/null) || return 1
[[ -n "${creds}" ]] || return 1
creds=$(_load_json_config_fields "$config_file" api_client_id api_secret) || return 1
local saved_client_id saved_secret
{ read -r saved_client_id; read -r saved_secret; } <<< "${creds}"
@ -115,12 +106,7 @@ ensure_kamatera_token() {
_validate_kamatera_credentials || return 1
log_info "API credentials validated"
local config_dir
config_dir=$(dirname "$config_file")
mkdir -p "$config_dir"
printf '{\n "api_client_id": %s,\n "api_secret": %s\n}\n' "$(json_escape "$client_id")" "$(json_escape "$secret")" > "$config_file"
chmod 600 "$config_file"
log_info "API credentials saved to $config_file"
_save_json_config "$config_file" api_client_id "$client_id" api_secret "$secret"
}
get_server_name() {

View file

@ -37,108 +37,26 @@ ensure_northflank_cli() {
}
test_northflank_token() {
local test_response
# Test token by listing projects (lightweight API call)
test_response=$(northflank list projects 2>&1)
local exit_code=$?
if [[ ${exit_code} -ne 0 ]]; then
if echo "${test_response}" | grep -qi "unauthorized\|invalid.*token\|authentication"; then
log_error "Invalid API token"
log_warn "Remediation steps:"
log_warn " 1. Verify API token at: https://northflank.com/account/settings/api/tokens"
log_warn " 2. Ensure the token has appropriate permissions"
log_warn " 3. Check token hasn't expired (90 day limit)"
return 1
fi
fi
return 0
}
# Authenticate with Northflank CLI using a token
# Returns 0 on success, 1 on invalid token
_northflank_login() {
local token="$1"
if ! northflank login -t "${token}" &>/dev/null; then
# Login with the token and validate by listing projects
if ! northflank login -t "${NORTHFLANK_TOKEN}" &>/dev/null; then
log_error "Failed to authenticate with Northflank CLI"
log_error ""
log_error "How to fix:"
log_error " 1. Verify API token at: https://northflank.com/account/settings/api/tokens"
log_error " 2. Ensure the token has appropriate permissions"
log_error " 3. Check token hasn't expired (90 day limit)"
return 1
fi
return 0
}
# Try to load Northflank token from config file and authenticate
# Returns 0 if loaded and valid, 1 otherwise
_load_northflank_config() {
local config_file="$1"
[[ -f "${config_file}" ]] || return 1
local saved_token
saved_token=$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('token',''))" "${config_file}" 2>/dev/null)
if [[ -n "${saved_token}" ]]; then
export NORTHFLANK_TOKEN="${saved_token}"
log_info "Using Northflank token from ${config_file}"
if _northflank_login "${saved_token}"; then
return 0
fi
log_warn "Saved Northflank token is invalid, prompting for new one"
unset NORTHFLANK_TOKEN
fi
return 1
}
# Save Northflank token to config file
_save_northflank_token() {
local token="$1"
local config_file="$2"
local config_dir
config_dir=$(dirname "${config_file}")
mkdir -p "${config_dir}"
printf '{\n "token": "%s"\n}\n' "$(json_escape "${token}")" > "${config_file}"
chmod 600 "${config_file}"
log_info "Northflank token saved to ${config_file}"
}
ensure_northflank_token() {
check_python_available || return 1
# 1. Check environment variable
if [[ -n "${NORTHFLANK_TOKEN:-}" ]]; then
log_info "Using Northflank token from environment"
if ! _northflank_login "${NORTHFLANK_TOKEN}"; then
log_error "Northflank token in environment is invalid"
return 1
fi
return 0
fi
local config_file="${HOME}/.config/spawn/northflank.json"
# 2. Check config file
if _load_northflank_config "${config_file}"; then
return 0
fi
# 3. Prompt and validate
echo ""
log_warn "Northflank API Token Required"
printf '%b\n' "${YELLOW}Get your token at: https://northflank.com/account/settings/api/tokens${NC}"
echo ""
local token
token=$(safe_read "Enter your Northflank token: ") || return 1
if [[ -z "${token}" ]]; then
log_error "Northflank token cannot be empty"
log_warn "For non-interactive usage, set: NORTHFLANK_TOKEN=your-token"
return 1
fi
export NORTHFLANK_TOKEN="${token}"
if ! _northflank_login "${token}"; then
log_error "Invalid Northflank token"
unset NORTHFLANK_TOKEN
return 1
fi
_save_northflank_token "${token}" "${config_file}"
ensure_api_token_with_provider \
"Northflank" \
"NORTHFLANK_TOKEN" \
"$HOME/.config/spawn/northflank.json" \
"https://northflank.com/account/settings/api/tokens" \
"test_northflank_token"
}
get_server_name() {