refactor: decompose GCP and Cherry create_server functions (#965)

GCP create_server was 64 lines (largest function across all cloud libs).
Cherry create_server was 54 lines. Both are now under 30 lines each
by extracting focused helpers:

GCP (64 -> 25 lines):
- _gcp_prepare_instance_files: startup script + SSH key temp files
- _gcp_run_create: gcloud command execution with error diagnostics
- _gcp_get_instance_ip: IP extraction from instance describe

Cherry (54 -> 27 lines):
- _cherry_build_server_body: JSON payload construction
- _cherry_submit_create: API call with error handling

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-02-13 10:15:47 -08:00 committed by GitHub
parent 0f60a2b082
commit f3d2384392
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 75 deletions

View file

@ -152,6 +152,46 @@ _cherry_wait_for_ip() {
CHERRY_SERVER_IP "Server" 60
}
# Build JSON request body for Cherry Servers server creation
# Usage: _cherry_build_server_body PLAN REGION IMAGE HOSTNAME SSH_KEY_ID
_cherry_build_server_body() {
python3 -c "
import json, sys
data = {
'plan': sys.argv[1],
'region': sys.argv[2],
'image': sys.argv[3],
'hostname': sys.argv[4],
'ssh_keys': [int(sys.argv[5])]
}
print(json.dumps(data))
" "$1" "$2" "$3" "$4" "$5"
}
# Submit server creation request and extract server ID
# Prints server ID on success, returns 1 on failure with diagnostics
_cherry_submit_create() {
local project_id="$1" payload="$2"
local response
response=$(cherry_api POST "/projects/${project_id}/servers" "$payload")
local server_id
server_id=$(_extract_json_field "$response" "d.get('id','')")
if [[ -z "$server_id" ]]; then
log_error "Failed to create Cherry Servers server"
log_error "API Error: $(extract_api_error_message "$response" "$response")"
log_warn "Common issues:"
log_warn " - Insufficient account balance"
log_warn " - Plan unavailable in region (try different CHERRY_DEFAULT_PLAN or CHERRY_DEFAULT_REGION)"
log_warn " - Server limit reached for your account"
return 1
fi
printf '%s' "$server_id"
}
create_server() {
local hostname="$1"
local plan="${CHERRY_DEFAULT_PLAN}"
@ -172,40 +212,13 @@ create_server() {
log_info "Plan: $plan, Region: $region, Image: $image"
local payload
payload=$(python3 -c "
import json, sys
data = {
'plan': sys.argv[1],
'region': sys.argv[2],
'image': sys.argv[3],
'hostname': sys.argv[4],
'ssh_keys': [int(sys.argv[5])]
}
print(json.dumps(data))
" "$plan" "$region" "$image" "$hostname" "${CHERRY_SSH_KEY_ID}")
payload=$(_cherry_build_server_body "$plan" "$region" "$image" "$hostname" "${CHERRY_SSH_KEY_ID}")
local response
response=$(cherry_api POST "/projects/${project_id}/servers" "$payload")
local server_id
server_id=$(_extract_json_field "$response" "d.get('id','')")
if [[ -z "$server_id" ]]; then
log_error "Failed to create Cherry Servers server"
log_error "API Error: $(extract_api_error_message "$response" "$response")"
log_warn "Common issues:"
log_warn " - Insufficient account balance"
log_warn " - Plan unavailable in region (try different CHERRY_DEFAULT_PLAN or CHERRY_DEFAULT_REGION)"
log_warn " - Server limit reached for your account"
return 1
fi
log_info "Server created with ID: $server_id"
CHERRY_SERVER_ID="$server_id"
CHERRY_SERVER_ID=$(_cherry_submit_create "$project_id" "$payload") || return 1
export CHERRY_SERVER_ID
log_info "Server created with ID: $CHERRY_SERVER_ID"
# Wait for IP assignment
_cherry_wait_for_ip "$server_id"
_cherry_wait_for_ip "$CHERRY_SERVER_ID"
}
# ============================================================

View file

@ -95,6 +95,63 @@ touch /tmp/.cloud-init-complete
CLOUD_INIT_EOF
}
# Prepare startup script and SSH metadata temp files for gcloud instance creation
# Sets startup_script_file and pub_key variables in caller's scope
_gcp_prepare_instance_files() {
startup_script_file=$(mktemp)
track_temp_file "${startup_script_file}"
get_cloud_init_userdata > "${startup_script_file}"
pub_key=$(cat "${HOME}/.ssh/id_ed25519.pub")
}
# Run gcloud compute instances create and handle errors
# Returns 0 on success, 1 on failure with diagnostic output
_gcp_run_create() {
local name="${1}" zone="${2}" machine_type="${3}"
local image_family="${4}" image_project="${5}" startup_script_file="${6}" pub_key="${7}"
local gcloud_err
gcloud_err=$(mktemp)
track_temp_file "${gcloud_err}"
if gcloud compute instances create "${name}" \
--zone="${zone}" \
--machine-type="${machine_type}" \
--image-family="${image_family}" \
--image-project="${image_project}" \
--metadata-from-file="startup-script=${startup_script_file}" \
--metadata="ssh-keys=${GCP_USERNAME}:${pub_key}" \
--project="${GCP_PROJECT}" \
--quiet \
>/dev/null 2>"${gcloud_err}"; then
return 0
fi
log_error "Failed to create GCP instance"
local err_output
err_output=$(cat "${gcloud_err}" 2>/dev/null)
if [[ -n "${err_output}" ]]; then
log_error "gcloud error: ${err_output}"
fi
log_warn "Common issues:"
log_warn " - Billing not enabled for the project (enable at https://console.cloud.google.com/billing)"
log_warn " - Compute Engine API not enabled (enable at https://console.cloud.google.com/apis)"
log_warn " - Instance quota exceeded in zone (try different GCP_ZONE)"
log_warn " - Machine type unavailable in zone (try different GCP_MACHINE_TYPE or GCP_ZONE)"
return 1
}
# Get the external IP of a GCP instance
# Usage: _gcp_get_instance_ip NAME ZONE
_gcp_get_instance_ip() {
local name="${1}" zone="${2}"
gcloud compute instances describe "${name}" \
--zone="${zone}" \
--project="${GCP_PROJECT}" \
--format='get(networkInterfaces[0].accessConfigs[0].natIP)' 2>/dev/null
}
create_server() {
local name="${1}"
local machine_type="${GCP_MACHINE_TYPE:-e2-medium}"
@ -108,55 +165,16 @@ create_server() {
log_step "Creating GCP instance '${name}' (type: ${machine_type}, zone: ${zone})..."
local pub_key
pub_key=$(cat "${HOME}/.ssh/id_ed25519.pub")
local startup_script_file pub_key
_gcp_prepare_instance_files
# Write startup script to a temp file to avoid --metadata comma delimiter issues
local startup_script_file
startup_script_file=$(mktemp)
track_temp_file "${startup_script_file}"
get_cloud_init_userdata > "${startup_script_file}"
_gcp_run_create "${name}" "${zone}" "${machine_type}" \
"${image_family}" "${image_project}" "${startup_script_file}" "${pub_key}" || return 1
local gcloud_err
gcloud_err=$(mktemp)
track_temp_file "${gcloud_err}"
if ! gcloud compute instances create "${name}" \
--zone="${zone}" \
--machine-type="${machine_type}" \
--image-family="${image_family}" \
--image-project="${image_project}" \
--metadata-from-file="startup-script=${startup_script_file}" \
--metadata="ssh-keys=${GCP_USERNAME}:${pub_key}" \
--project="${GCP_PROJECT}" \
--quiet \
>/dev/null 2>"${gcloud_err}"; then
log_error "Failed to create GCP instance"
local err_output
err_output=$(cat "${gcloud_err}" 2>/dev/null)
if [[ -n "${err_output}" ]]; then
log_error "gcloud error: ${err_output}"
fi
log_warn "Common issues:"
log_warn " - Billing not enabled for the project (enable at https://console.cloud.google.com/billing)"
log_warn " - Compute Engine API not enabled (enable at https://console.cloud.google.com/apis)"
log_warn " - Instance quota exceeded in zone (try different GCP_ZONE)"
log_warn " - Machine type unavailable in zone (try different GCP_MACHINE_TYPE or GCP_ZONE)"
return 1
fi
# Export instance metadata for use by calling script
# shellcheck disable=SC2034 # Variables exported for use by sourcing scripts
export GCP_INSTANCE_NAME_ACTUAL="${name}"
export GCP_ZONE="${zone}"
# Get external IP
local server_ip
server_ip=$(gcloud compute instances describe "${name}" \
--zone="${zone}" \
--project="${GCP_PROJECT}" \
--format='get(networkInterfaces[0].accessConfigs[0].natIP)' 2>/dev/null)
export GCP_SERVER_IP="${server_ip}"
export GCP_SERVER_IP="$(_gcp_get_instance_ip "${name}" "${zone}")"
log_info "Instance created: IP=${GCP_SERVER_IP}"
}