refactor: extract helpers from create_server() in 4 cloud providers (#423)

Extract wait-for-IP polling loops and JSON body builders from the
largest create_server() functions (ramnode 105->59, netcup 95->50,
cherry 80->57, binarylane 92->70 lines), following the pattern
already established in ionos/lib/common.sh.

Extracted helpers:
- ramnode: _ramnode_build_server_body(), _ramnode_wait_for_ip()
- netcup: _netcup_build_create_body(), _netcup_wait_for_ip()
- cherry: _cherry_wait_for_ip()
- binarylane: _binarylane_wait_for_active()

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 02:59:31 -08:00 committed by GitHub
parent 0181b73506
commit de19996360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 180 additions and 150 deletions

View file

@ -139,6 +139,34 @@ get_server_name() {
# get_cloud_init_userdata is now defined in shared/common.sh
# Poll the BinaryLane API until the server becomes active and has an IP
# Sets BINARYLANE_SERVER_IP on success
_binarylane_wait_for_active() {
log_warn "Waiting for server to become active..."
local max_attempts=60
local attempt=1
while [[ "$attempt" -le "$max_attempts" ]]; do
local status_response
status_response=$(binarylane_api GET "/servers/$BINARYLANE_SERVER_ID")
local status
status=$(echo "$status_response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['server']['status'])")
if [[ "$status" == "active" ]]; then
BINARYLANE_SERVER_IP=$(echo "$status_response" | python3 -c "import json,sys; networks = json.loads(sys.stdin.read())['server']['networks']['v4']; print([n['ip_address'] for n in networks if n['type'] == 'public'][0])")
export BINARYLANE_SERVER_IP
log_info "Server active: IP=$BINARYLANE_SERVER_IP"
return 0
fi
log_warn "Server status: $status ($attempt/$max_attempts)"
sleep "${INSTANCE_STATUS_POLL_DELAY}"
attempt=$((attempt + 1))
done
log_error "Server did not become active in time"
return 1
}
create_server() {
local name="$1"
local size="${BINARYLANE_SIZE:-std-1vcpu}"
@ -205,30 +233,8 @@ print(json.dumps(body))
return 1
fi
# Wait for server to get an IP
log_warn "Waiting for server to become active..."
local max_attempts=60
local attempt=1
while [[ "$attempt" -le "$max_attempts" ]]; do
local status_response
status_response=$(binarylane_api GET "/servers/$BINARYLANE_SERVER_ID")
local status
status=$(echo "$status_response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['server']['status'])")
if [[ "$status" == "active" ]]; then
BINARYLANE_SERVER_IP=$(echo "$status_response" | python3 -c "import json,sys; networks = json.loads(sys.stdin.read())['server']['networks']['v4']; print([n['ip_address'] for n in networks if n['type'] == 'public'][0])")
export BINARYLANE_SERVER_IP
log_info "Server active: IP=$BINARYLANE_SERVER_IP"
return 0
fi
log_warn "Server status: $status ($attempt/$max_attempts)"
sleep "${INSTANCE_STATUS_POLL_DELAY}"
attempt=$((attempt + 1))
done
log_error "Server did not become active in time"
return 1
# Wait for server to become active with IP
_binarylane_wait_for_active
}
verify_server_connectivity() {

View file

@ -166,6 +166,36 @@ get_server_name() {
# Create server
# Sets CHERRY_SERVER_ID and CHERRY_SERVER_IP as exports
# Poll the Cherry Servers API until the server has an IP address
# Sets CHERRY_SERVER_IP on success
_cherry_wait_for_ip() {
local server_id="$1"
log_info "Waiting for IP address assignment..."
local ip_address=""
local attempts=0
local max_attempts=60
while [[ -z "$ip_address" ]] && [[ $attempts -lt $max_attempts ]]; do
sleep "${POLL_INTERVAL}"
local server_info
server_info=$(cherry_api GET "/servers/${server_id}")
ip_address=$(printf '%s' "$server_info" | _cherry_extract_primary_ip)
attempts=$((attempts + 1))
done
if [[ -z "$ip_address" ]]; then
log_error "Failed to get server IP address"
return 1
fi
log_info "Server IP: $ip_address"
CHERRY_SERVER_IP="$ip_address"
export CHERRY_SERVER_IP
}
create_server() {
local hostname="$1"
local plan="${CHERRY_DEFAULT_PLAN}"
@ -215,30 +245,7 @@ print(json.dumps(data))
export CHERRY_SERVER_ID
# Wait for IP assignment
log_info "Waiting for IP address assignment..."
local ip_address=""
local attempts=0
local max_attempts=60
while [[ -z "$ip_address" ]] && [[ $attempts -lt $max_attempts ]]; do
sleep "${POLL_INTERVAL}"
local server_info
server_info=$(cherry_api GET "/servers/${server_id}")
ip_address=$(printf '%s' "$server_info" | _cherry_extract_primary_ip)
attempts=$((attempts + 1))
done
if [[ -z "$ip_address" ]]; then
log_error "Failed to get server IP address"
return 1
fi
log_info "Server IP: $ip_address"
CHERRY_SERVER_IP="$ip_address"
export CHERRY_SERVER_IP
_cherry_wait_for_ip "$server_id"
}
# ============================================================

View file

@ -310,28 +310,11 @@ _pick_datacenter() {
fi
}
# Create a Netcup VPS with cloud-init
create_server() {
local name="$1"
# Interactive selections
local datacenter
datacenter=$(_pick_datacenter)
local product
product=$(_pick_vps_product)
local image="ubuntu-24.04"
log_warn "Creating Netcup VPS '$name' (product: $product, datacenter: $datacenter)..."
# Get cloud-init userdata
local userdata
userdata=$(get_cloud_init_userdata)
# Build request param
local param
param=$(echo "$userdata" | python3 -c "
# Build JSON request body for Netcup VPS creation
# Reads cloud-init userdata from stdin
# Usage: get_cloud_init_userdata | _netcup_build_create_body NAME PRODUCT DATACENTER IMAGE
_netcup_build_create_body() {
python3 -c "
import json, sys
userdata = sys.stdin.read()
name, product, datacenter, image = sys.argv[1:5]
@ -344,37 +327,12 @@ param = {
'userdata': userdata
}
print(json.dumps(param))
" "$name" "$product" "$datacenter" "$image")
" "$@"
}
local response
response=$(netcup_api "createVServer" "$param")
# Check for errors
local status
status=$(echo "$response" | python3 -c "import json, sys; print(json.loads(sys.stdin.read()).get('status', 'error'))")
if [[ "$status" != "success" ]]; then
log_error "Failed to create Netcup VPS"
local error_msg
error_msg=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('longmessage','Unknown error'))" 2>/dev/null || echo "$response")
log_error "API Error: $error_msg"
log_error ""
log_error "Common issues:"
log_error " - Insufficient account balance"
log_error " - Product not available in selected datacenter"
log_error " - Account limits reached"
log_error ""
log_error "Check your account: https://ccp.netcup.net/"
return 1
fi
# Extract server ID
NETCUP_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['responsedata']['vserverid'])")
export NETCUP_SERVER_ID
log_info "VPS created: ID=$NETCUP_SERVER_ID"
# Wait for IP assignment (Netcup assigns IPs asynchronously)
# Poll the Netcup API until the VPS has an IPv4 address
# Sets NETCUP_SERVER_IP on success
_netcup_wait_for_ip() {
log_info "Waiting for IP assignment..."
local ip=""
local attempts=0
@ -401,10 +359,59 @@ except:
NETCUP_SERVER_IP="$ip"
export NETCUP_SERVER_IP
log_info "Server IP: $NETCUP_SERVER_IP"
}
# Create a Netcup VPS with cloud-init
create_server() {
local name="$1"
# Interactive selections
local datacenter
datacenter=$(_pick_datacenter)
local product
product=$(_pick_vps_product)
local image="ubuntu-24.04"
log_warn "Creating Netcup VPS '$name' (product: $product, datacenter: $datacenter)..."
# Get cloud-init userdata and build request body
local param
param=$(get_cloud_init_userdata | _netcup_build_create_body "$name" "$product" "$datacenter" "$image")
local response
response=$(netcup_api "createVServer" "$param")
# Check for errors
local status
status=$(echo "$response" | python3 -c "import json, sys; print(json.loads(sys.stdin.read()).get('status', 'error'))")
if [[ "$status" != "success" ]]; then
log_error "Failed to create Netcup VPS"
local error_msg
error_msg=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('longmessage','Unknown error'))" 2>/dev/null || echo "$response")
log_error "API Error: $error_msg"
log_error ""
log_error "Common issues:"
log_error " - Insufficient account balance"
log_error " - Product not available in selected datacenter"
log_error " - Account limits reached"
log_error ""
log_error "Check your account: https://ccp.netcup.net/"
return 1
fi
# Extract server ID
NETCUP_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['responsedata']['vserverid'])")
export NETCUP_SERVER_ID
log_info "VPS created: ID=$NETCUP_SERVER_ID"
# Wait for IP assignment
_netcup_wait_for_ip
}
# Wait for SSH connectivity
verify_server_connectivity() {
local ip="$1"

View file

@ -348,6 +348,62 @@ if networks:
"
}
# Build JSON request body for RamNode server creation
# Usage: _ramnode_build_server_body NAME FLAVOR IMAGE_ID KEY_NAME USERDATA [NETWORK_ID]
_ramnode_build_server_body() {
python3 -c "
import json, sys
name, flavor, image_id, key_name, userdata, network_id = sys.argv[1:7]
body = {
'server': {
'name': name,
'flavorRef': flavor,
'imageRef': image_id,
'key_name': key_name,
'user_data': userdata
}
}
if network_id:
body['server']['networks'] = [{'uuid': network_id}]
print(json.dumps(body))
" "$@"
}
# Poll the RamNode API until the server has an IPv4 address
# Sets RAMNODE_SERVER_IP on success
_ramnode_wait_for_ip() {
log_info "Waiting for IP address..."
local max_attempts=30
local attempt=0
while [[ $attempt -lt $max_attempts ]]; do
sleep 2
local server_info
server_info=$(ramnode_compute_api GET "/servers/$RAMNODE_SERVER_ID")
RAMNODE_SERVER_IP=$(echo "$server_info" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
addresses = data.get('server', {}).get('addresses', {})
for net_name, addrs in addresses.items():
for addr in addrs:
if addr.get('version') == 4:
print(addr['addr'])
sys.exit(0)
" 2>/dev/null || echo "")
if [[ -n "$RAMNODE_SERVER_IP" ]]; then
export RAMNODE_SERVER_IP
log_info "IP address assigned: $RAMNODE_SERVER_IP"
return 0
fi
attempt=$((attempt + 1))
done
log_error "Timeout waiting for IP address"
return 1
}
# Create a RamNode server
create_server() {
local name="$1"
@ -379,24 +435,8 @@ create_server() {
log_warn "Creating RamNode instance '$name' (flavor: $flavor)..."
# Build request body
local body
body=$(python3 -c "
import json, sys
name, flavor, image_id, key_name, userdata, network_id = sys.argv[1:7]
body = {
'server': {
'name': name,
'flavorRef': flavor,
'imageRef': image_id,
'key_name': key_name,
'user_data': userdata
}
}
if network_id:
body['server']['networks'] = [{'uuid': network_id}]
print(json.dumps(body))
" "$name" "$flavor" "$image_id" "$key_name" "$userdata" "${network_id:-}")
body=$(_ramnode_build_server_body "$name" "$flavor" "$image_id" "$key_name" "$userdata" "${network_id:-}")
local response
response=$(ramnode_compute_api POST "/servers" "$body")
@ -417,40 +457,10 @@ print(json.dumps(body))
# Extract server ID
RAMNODE_SERVER_ID=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['server']['id'])")
export RAMNODE_SERVER_ID
log_info "Server created: ID=$RAMNODE_SERVER_ID"
# Wait for server to get IP address
log_info "Waiting for IP address..."
local max_attempts=30
local attempt=0
while [[ $attempt -lt $max_attempts ]]; do
sleep 2
local server_info
server_info=$(ramnode_compute_api GET "/servers/$RAMNODE_SERVER_ID")
RAMNODE_SERVER_IP=$(echo "$server_info" | python3 -c "
import json, sys
data = json.loads(sys.stdin.read())
addresses = data.get('server', {}).get('addresses', {})
for net_name, addrs in addresses.items():
for addr in addrs:
if addr.get('version') == 4:
print(addr['addr'])
sys.exit(0)
" 2>/dev/null || echo "")
if [[ -n "$RAMNODE_SERVER_IP" ]]; then
export RAMNODE_SERVER_IP
log_info "IP address assigned: $RAMNODE_SERVER_IP"
return 0
fi
attempt=$((attempt + 1))
done
log_error "Timeout waiting for IP address"
return 1
# Wait for IP assignment
_ramnode_wait_for_ip
}
# Wait for SSH connectivity