diff --git a/contabo/lib/common.sh b/contabo/lib/common.sh index 2c059e11..a4515e80 100644 --- a/contabo/lib/common.sh +++ b/contabo/lib/common.sh @@ -92,71 +92,14 @@ test_contabo_credentials() { return 0 } -# Prompt for a single Contabo credential if not already set -# Usage: _prompt_contabo_cred VAR_NAME "prompt text" -_prompt_contabo_cred() { - local var_name="$1" - local prompt_text="$2" - - if [[ -n "${!var_name:-}" ]]; then - return 0 - fi - - local value - value=$(safe_read "$prompt_text") || return 1 - export "${var_name}=${value}" -} - # Ensure Contabo credentials are available ensure_contabo_credentials() { - check_python_available || return 1 - - local config_file="$HOME/.config/spawn/contabo.json" - - # Try environment variables first (all 4 must be set) - if [[ -n "${CONTABO_CLIENT_ID:-}" ]] && [[ -n "${CONTABO_CLIENT_SECRET:-}" ]] && \ - [[ -n "${CONTABO_API_USER:-}" ]] && [[ -n "${CONTABO_API_PASSWORD:-}" ]]; then - log_info "Using Contabo credentials from environment" - return 0 - fi - - # Try config file - local creds - if creds=$(_load_json_config_fields "$config_file" client_id client_secret api_user api_password); then - local saved_client_id saved_secret saved_user saved_password - { read -r saved_client_id; read -r saved_secret; read -r saved_user; read -r saved_password; } <<< "${creds}" - if [[ -n "$saved_client_id" ]] && [[ -n "$saved_secret" ]] && [[ -n "$saved_user" ]] && [[ -n "$saved_password" ]]; then - export CONTABO_CLIENT_ID="$saved_client_id" - export CONTABO_CLIENT_SECRET="$saved_secret" - export CONTABO_API_USER="$saved_user" - export CONTABO_API_PASSWORD="$saved_password" - log_info "Using Contabo credentials from $config_file" - return 0 - fi - fi - - # Prompt for missing credentials - echo "" - log_warn "Contabo API Credentials Required" - log_warn "Get your credentials from: https://my.contabo.com/api/details" - echo "" - - _prompt_contabo_cred "CONTABO_CLIENT_ID" "Enter Contabo Client ID: " || return 1 - _prompt_contabo_cred "CONTABO_CLIENT_SECRET" "Enter Contabo Client Secret: " || return 1 - _prompt_contabo_cred "CONTABO_API_USER" "Enter Contabo API User (username/email): " || return 1 - _prompt_contabo_cred "CONTABO_API_PASSWORD" "Enter Contabo API Password: " || return 1 - - # Test credentials - log_info "Testing Contabo credentials..." - if ! test_contabo_credentials; then - return 1 - fi - - _save_json_config "$config_file" \ - client_id "$CONTABO_CLIENT_ID" \ - client_secret "$CONTABO_CLIENT_SECRET" \ - api_user "$CONTABO_API_USER" \ - api_password "$CONTABO_API_PASSWORD" + ensure_multi_credentials "Contabo" "$HOME/.config/spawn/contabo.json" \ + "https://my.contabo.com/api/details" test_contabo_credentials \ + "CONTABO_CLIENT_ID:client_id:Client ID" \ + "CONTABO_CLIENT_SECRET:client_secret:Client Secret" \ + "CONTABO_API_USER:api_user:API User (email)" \ + "CONTABO_API_PASSWORD:api_password:API Password" } # Check if SSH key is registered with Contabo diff --git a/ionos/lib/common.sh b/ionos/lib/common.sh index 1c76af2a..6c3ede91 100644 --- a/ionos/lib/common.sh +++ b/ionos/lib/common.sh @@ -72,60 +72,12 @@ test_ionos_credentials() { return 0 } -# Try loading IONOS credentials from config file -# Returns 0 if loaded, 1 otherwise -_ionos_load_config_credentials() { - local config_file="$1" - local creds - creds=$(_load_json_config_fields "$config_file" username password) || return 1 - - local saved_user saved_pass - { read -r saved_user; read -r saved_pass; } <<< "${creds}" - if [[ -z "$saved_user" ]] || [[ -z "$saved_pass" ]]; then - return 1 - fi - - log_info "Loading IONOS credentials from $config_file" - export IONOS_USERNAME="$saved_user" IONOS_PASSWORD="$saved_pass" -} - -# Prompt user for IONOS credentials interactively -# Returns 0 on success (exports credentials), 1 on failure -_ionos_prompt_credentials() { - log_warn "IONOS Cloud credentials not found" - echo "" - log_info "Get credentials at: https://dcd.ionos.com/ → Management → Users & Keys" - echo "" - - IONOS_USERNAME=$(safe_read "Enter IONOS username (email): ") || return 1 - IONOS_PASSWORD=$(safe_read "Enter IONOS password/API key: ") || return 1 - export IONOS_USERNAME IONOS_PASSWORD -} - -# Ensure IONOS credentials are available (env vars → config file → prompt+save) +# Ensure IONOS credentials are available (env vars -> config file -> prompt+save) ensure_ionos_credentials() { - local config_file="$HOME/.config/spawn/ionos.json" - - # Try env vars, then config file, then prompt - if [[ -z "${IONOS_USERNAME:-}" ]] || [[ -z "${IONOS_PASSWORD:-}" ]]; then - _ionos_load_config_credentials "$config_file" || true - fi - - local need_save=false - if [[ -z "${IONOS_USERNAME:-}" ]] || [[ -z "${IONOS_PASSWORD:-}" ]]; then - _ionos_prompt_credentials || return 1 - need_save=true - fi - - log_info "Testing IONOS credentials..." - if ! test_ionos_credentials; then - log_error "Invalid IONOS credentials. Please check IONOS_USERNAME and IONOS_PASSWORD." - return 1 - fi - - if [[ "$need_save" == "true" ]]; then - _save_json_config "$config_file" username "$IONOS_USERNAME" password "$IONOS_PASSWORD" - fi + ensure_multi_credentials "IONOS" "$HOME/.config/spawn/ionos.json" \ + "https://dcd.ionos.com/ -> Management -> Users & Keys" test_ionos_credentials \ + "IONOS_USERNAME:username:Username (email)" \ + "IONOS_PASSWORD:password:Password/API Key" } # Check if SSH key is registered with IONOS diff --git a/netcup/lib/common.sh b/netcup/lib/common.sh index c84e0b78..0562a86f 100644 --- a/netcup/lib/common.sh +++ b/netcup/lib/common.sh @@ -124,51 +124,11 @@ test_netcup_credentials() { # Ensure Netcup credentials are available ensure_netcup_credentials() { - local config_file="$HOME/.config/spawn/netcup.json" - - # Try loading from env vars first - if [[ -n "${NETCUP_CUSTOMER_NUMBER:-}" && -n "${NETCUP_API_KEY:-}" && -n "${NETCUP_API_PASSWORD:-}" ]]; then - if test_netcup_credentials; then - return 0 - fi - fi - - # Try loading from config file (single python3 call instead of 3) - local creds - if creds=$(_load_json_config_fields "$config_file" customer_number api_key api_password); then - local saved_num saved_key saved_pass - { read -r saved_num; read -r saved_key; read -r saved_pass; } <<< "${creds}" - if [[ -n "$saved_num" ]] && [[ -n "$saved_key" ]] && [[ -n "$saved_pass" ]]; then - log_info "Loading Netcup credentials from $config_file" - export NETCUP_CUSTOMER_NUMBER="$saved_num" NETCUP_API_KEY="$saved_key" NETCUP_API_PASSWORD="$saved_pass" - if test_netcup_credentials; then - return 0 - fi - fi - fi - - # Prompt for credentials - log_info "Netcup credentials not found" - log_info "Get your API credentials at: https://ccp.netcup.net/ → Settings → API" - log_info "" - - NETCUP_CUSTOMER_NUMBER=$(safe_read "Enter Netcup customer number: ") || return 1 - NETCUP_API_KEY=$(safe_read "Enter Netcup API key: ") || return 1 - NETCUP_API_PASSWORD=$(safe_read "Enter Netcup API password: ") || return 1 - export NETCUP_CUSTOMER_NUMBER NETCUP_API_KEY NETCUP_API_PASSWORD - - # Test credentials - if ! test_netcup_credentials; then - log_error "Invalid Netcup credentials" - return 1 - fi - - _save_json_config "$config_file" \ - customer_number "$NETCUP_CUSTOMER_NUMBER" \ - api_key "$NETCUP_API_KEY" \ - api_password "$NETCUP_API_PASSWORD" - - return 0 + ensure_multi_credentials "Netcup" "$HOME/.config/spawn/netcup.json" \ + "https://ccp.netcup.net/ -> Settings -> API" test_netcup_credentials \ + "NETCUP_CUSTOMER_NUMBER:customer_number:Customer Number" \ + "NETCUP_API_KEY:api_key:API Key" \ + "NETCUP_API_PASSWORD:api_password:API Password" } # Check if SSH key is registered with Netcup diff --git a/ramnode/lib/common.sh b/ramnode/lib/common.sh index 8dcc64bd..517f9030 100755 --- a/ramnode/lib/common.sh +++ b/ramnode/lib/common.sh @@ -121,51 +121,11 @@ test_ramnode_credentials() { # Ensure RamNode credentials are available ensure_ramnode_credentials() { - # Check for required environment variables - if [[ -n "${RAMNODE_USERNAME:-}" && -n "${RAMNODE_PASSWORD:-}" && -n "${RAMNODE_PROJECT_ID:-}" ]]; then - if test_ramnode_credentials; then - return 0 - fi - fi - - # Try to load from config file (single python3 call instead of 3) - local config_file="$HOME/.config/spawn/ramnode.json" - local creds - if creds=$(_load_json_config_fields "$config_file" username password project_id); then - local saved_user saved_pass saved_pid - { read -r saved_user; read -r saved_pass; read -r saved_pid; } <<< "${creds}" - if [[ -n "$saved_user" ]] && [[ -n "$saved_pass" ]] && [[ -n "$saved_pid" ]]; then - log_info "Loading RamNode credentials from $config_file..." - export RAMNODE_USERNAME="$saved_user" RAMNODE_PASSWORD="$saved_pass" RAMNODE_PROJECT_ID="$saved_pid" - if test_ramnode_credentials; then - return 0 - fi - fi - fi - - # Prompt for credentials - log_warn "RamNode OpenStack credentials not found" - log_info "" - log_info "Get credentials from: https://manage.ramnode.com/ → Cloud → API Users" - log_info "" - - RAMNODE_USERNAME=$(safe_read "Enter RamNode username: ") || return 1 - RAMNODE_PASSWORD=$(safe_read "Enter RamNode password: ") || return 1 - RAMNODE_PROJECT_ID=$(safe_read "Enter RamNode project ID: ") || return 1 - - export RAMNODE_USERNAME RAMNODE_PASSWORD RAMNODE_PROJECT_ID - - if ! test_ramnode_credentials; then - log_error "Invalid credentials" - return 1 - fi - - _save_json_config "$config_file" \ - username "$RAMNODE_USERNAME" \ - password "$RAMNODE_PASSWORD" \ - project_id "$RAMNODE_PROJECT_ID" - - return 0 + ensure_multi_credentials "RamNode" "$HOME/.config/spawn/ramnode.json" \ + "https://manage.ramnode.com/ -> Cloud -> API Users" test_ramnode_credentials \ + "RAMNODE_USERNAME:username:Username" \ + "RAMNODE_PASSWORD:password:Password" \ + "RAMNODE_PROJECT_ID:project_id:Project ID" } # Check if SSH key is registered with RamNode diff --git a/shared/common.sh b/shared/common.sh index 5435b740..60ce9157 100644 --- a/shared/common.sh +++ b/shared/common.sh @@ -1694,6 +1694,125 @@ _save_json_config() { log_info "Credentials saved to ${config_file}" } +# Generic multi-credential ensure function +# Eliminates duplicated env-var/config/prompt/test/save logic across providers +# that need more than one credential (username+password, client_id+secret, etc.) +# +# Usage: ensure_multi_credentials PROVIDER_NAME CONFIG_FILE HELP_URL TEST_FUNC \ +# "ENV_VAR:config_key:Prompt Label" ... +# +# Arguments: +# PROVIDER_NAME - Display name for logging (e.g., "Contabo") +# CONFIG_FILE - Path to JSON config file (e.g., "$HOME/.config/spawn/contabo.json") +# HELP_URL - URL where users can find their credentials +# TEST_FUNC - Function to validate credentials (returns 0=ok, 1=fail); empty to skip +# ... - One or more credential specs as "ENV_VAR:config_key:Prompt Label" +# +# Each credential spec is a colon-delimited triple: +# ENV_VAR - Environment variable name (e.g., CONTABO_CLIENT_ID) +# config_key - JSON key in the config file (e.g., client_id) +# Prompt Label - Human-readable label for prompting (e.g., "Client ID") +# +# Example: +# ensure_multi_credentials "Contabo" "$HOME/.config/spawn/contabo.json" \ +# "https://my.contabo.com/api/details" test_contabo_credentials \ +# "CONTABO_CLIENT_ID:client_id:Client ID" \ +# "CONTABO_CLIENT_SECRET:client_secret:Client Secret" \ +# "CONTABO_API_USER:api_user:API User (email)" \ +# "CONTABO_API_PASSWORD:api_password:API Password" +ensure_multi_credentials() { + local provider_name="${1}" + local config_file="${2}" + local help_url="${3}" + local test_func="${4:-}" + shift 4 + + check_python_available || return 1 + + # Collect credential specs + local specs=("$@") + local env_vars=() config_keys=() labels=() + local spec + for spec in "${specs[@]}"; do + env_vars+=("${spec%%:*}") + local rest="${spec#*:}" + config_keys+=("${rest%%:*}") + labels+=("${rest#*:}") + done + + # 1. Check if ALL env vars are already set + local all_set=true + local var + for var in "${env_vars[@]}"; do + if [[ -z "${!var:-}" ]]; then + all_set=false + break + fi + done + if [[ "${all_set}" == "true" ]]; then + log_info "Using ${provider_name} credentials from environment" + return 0 + fi + + # 2. Try loading from config file + local creds + if creds=$(_load_json_config_fields "${config_file}" "${config_keys[@]}"); then + local all_loaded=true + local i=0 + while IFS= read -r value; do + if [[ -z "${value}" ]]; then + all_loaded=false + break + fi + export "${env_vars[$i]}=${value}" + i=$((i + 1)) + done <<< "${creds}" + + if [[ "${all_loaded}" == "true" && "${i}" -eq "${#env_vars[@]}" ]]; then + log_info "Using ${provider_name} credentials from ${config_file}" + return 0 + fi + fi + + # 3. Prompt for each credential + echo "" + log_step "${provider_name} API Credentials Required" + log_step "Get your credentials from: ${help_url}" + echo "" + + local idx=0 + for idx in $(seq 0 $((${#env_vars[@]} - 1))); do + local val + val=$(safe_read "Enter ${provider_name} ${labels[$idx]}: ") || return 1 + if [[ -z "${val}" ]]; then + log_error "${labels[$idx]} is required" + return 1 + fi + export "${env_vars[$idx]}=${val}" + done + + # 4. Validate credentials + if [[ -n "${test_func}" ]]; then + log_info "Testing ${provider_name} credentials..." + if ! "${test_func}"; then + log_error "Invalid ${provider_name} credentials" + local v + for v in "${env_vars[@]}"; do + unset "${v}" + done + return 1 + fi + fi + + # 5. Save to config file + local save_args=() + for idx in $(seq 0 $((${#env_vars[@]} - 1))); do + save_args+=("${config_keys[$idx]}" "${!env_vars[$idx]}") + done + _save_json_config "${config_file}" "${save_args[@]}" + return 0 +} + # ============================================================ # Configuration file helpers # ============================================================ diff --git a/upcloud/lib/common.sh b/upcloud/lib/common.sh index 7cee8f26..000b9758 100644 --- a/upcloud/lib/common.sh +++ b/upcloud/lib/common.sh @@ -53,75 +53,12 @@ test_upcloud_credentials() { # Try loading UpCloud credentials from config file # Returns 0 if loaded, 1 otherwise -_upcloud_load_config_credentials() { - local config_file="$1" - local creds - creds=$(_load_json_config_fields "$config_file" username password) || return 1 - - local saved_username saved_password - { read -r saved_username; read -r saved_password; } <<< "${creds}" - if [[ -z "${saved_username}" ]] || [[ -z "${saved_password}" ]]; then - return 1 - fi - - export UPCLOUD_USERNAME="${saved_username}" - export UPCLOUD_PASSWORD="${saved_password}" - log_info "Using UpCloud credentials from ${config_file}" -} - -# Prompt user for UpCloud credentials interactively -# Returns 0 on success (exports credentials), 1 on failure -_upcloud_prompt_credentials() { - echo "" - log_warn "UpCloud API Credentials Required" - log_warn "Create API credentials at: https://hub.upcloud.com/people/account" - echo "" - - local username - username=$(safe_read "Enter your UpCloud API username: ") || return 1 - if [[ -z "${username}" ]]; then - log_error "Username is required" - return 1 - fi - - local password - password=$(safe_read "Enter your UpCloud API password: ") || return 1 - if [[ -z "${password}" ]]; then - log_error "Password is required" - return 1 - fi - - export UPCLOUD_USERNAME="${username}" - export UPCLOUD_PASSWORD="${password}" -} - # Ensure UpCloud credentials are available (env var -> config file -> prompt+save) ensure_upcloud_credentials() { - check_python_available || return 1 - - local config_file="$HOME/.config/spawn/upcloud.json" - - # 1. Check environment variables - if [[ -n "${UPCLOUD_USERNAME:-}" ]] && [[ -n "${UPCLOUD_PASSWORD:-}" ]]; then - log_info "Using UpCloud credentials from environment" - test_upcloud_credentials - return $? - fi - - # 2. Check config file - if _upcloud_load_config_credentials "$config_file"; then - return 0 - fi - - # 3. Prompt and save - _upcloud_prompt_credentials || return 1 - - if ! test_upcloud_credentials; then - unset UPCLOUD_USERNAME UPCLOUD_PASSWORD - return 1 - fi - - _save_json_config "$config_file" username "$UPCLOUD_USERNAME" password "$UPCLOUD_PASSWORD" + ensure_multi_credentials "UpCloud" "$HOME/.config/spawn/upcloud.json" \ + "https://hub.upcloud.com/people/account" test_upcloud_credentials \ + "UPCLOUD_USERNAME:username:API Username" \ + "UPCLOUD_PASSWORD:password:API Password" } # Get server name from env var or prompt