From f79de27bc7b5e296184159f3b40e109b6ab2b06e Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:20:08 -0800 Subject: [PATCH] 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) --- binarylane/lib/common.sh | 65 +++++++---------------- kamatera/lib/common.sh | 18 +------ northflank/lib/common.sh | 110 +++++---------------------------------- 3 files changed, 36 insertions(+), 157 deletions(-) diff --git a/binarylane/lib/common.sh b/binarylane/lib/common.sh index 6f96b50b..16dbec6e 100644 --- a/binarylane/lib/common.sh +++ b/binarylane/lib/common.sh @@ -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 diff --git a/kamatera/lib/common.sh b/kamatera/lib/common.sh index d4bcda47..bb831b75 100644 --- a/kamatera/lib/common.sh +++ b/kamatera/lib/common.sh @@ -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() { diff --git a/northflank/lib/common.sh b/northflank/lib/common.sh index e206277e..b21a8e03 100644 --- a/northflank/lib/common.sh +++ b/northflank/lib/common.sh @@ -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() {