Skip to content

Commit 7ab2dc9

Browse files
committed
error handling and other improvements in proxmox.sh
1 parent a18682e commit 7ab2dc9

File tree

1 file changed

+111
-71
lines changed

1 file changed

+111
-71
lines changed

extensions/Proxmox/proxmox.sh

Lines changed: 111 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,95 @@ urlencode() {
2626
echo "$encoded_data"
2727
}
2828

29+
check_required_fields() {
30+
local missing=()
31+
for varname in "$@"; do
32+
local value="${!varname}"
33+
if [[ -z "$value" ]]; then
34+
missing+=("$varname")
35+
fi
36+
done
37+
38+
if [[ ${#missing[@]} -gt 0 ]]; then
39+
echo "{\"error\":\"Missing required fields: ${missing[*]}\"}"
40+
exit 1
41+
fi
42+
}
43+
44+
cloudstack_vm_internal_name_to_proxmox_vmid() {
45+
local vm_internal_name="$1"
46+
if [[ -z "$vm_internal_name" || ! "$vm_internal_name" =~ ^i-[0-9]+-[0-9]+ ]]; then
47+
echo "{\"error\":\"Invalid VM Internal Name: '$vm_internal_name'\"}"
48+
exit 1
49+
fi
50+
51+
local account id vmid
52+
account=$(echo "$vm_internal_name" | cut -d '-' -f2)
53+
id=$(echo "$vm_internal_name" | cut -d '-' -f3)
54+
id=$(printf "%04d" "$id")
55+
vmid="${account}${id}"
56+
echo "$vmid"
57+
}
58+
59+
validate_vm_name() {
60+
local name="$1"
61+
62+
if [[ ! "$name" =~ ^[a-zA-Z0-9-]+$ ]]; then
63+
echo "{\"error\":\"Invalid VM name '$name'. Only alphanumeric characters and dashes (-) are allowed.\"}"
64+
exit 1
65+
fi
66+
}
67+
68+
parse_json() {
69+
local json_string="$1"
70+
echo "$json_string" | jq '.' > /dev/null || { echo '{"error":"Invalid JSON input"}'; exit 1; }
71+
72+
local -A details
73+
while IFS="=" read -r key value; do
74+
details[$key]="$value"
75+
done < <(echo "$json_string" | jq -r '{
76+
"url": (.externaldetails.extension.url // ""),
77+
"user": (.externaldetails.extension.user // ""),
78+
"token": (.externaldetails.extension.token // ""),
79+
"secret": (.externaldetails.extension.secret // ""),
80+
"node": (.externaldetails.host.node // ""),
81+
"vm_name": (.externaldetails.virtualmachine.vm_name // ""),
82+
"template_id": (.externaldetails.virtualmachine.template_id // ""),
83+
"template_type": (.externaldetails.virtualmachine.template_type // ""),
84+
"iso_path": (.externaldetails.virtualmachine.iso_path // ""),
85+
"vm_internal_name": (."cloudstack.vm.details".name // ""),
86+
"vmmemory": (."cloudstack.vm.details".minRam // ""),
87+
"vmcpus": (."cloudstack.vm.details".cpus // ""),
88+
"vlan": (."cloudstack.vm.details".nics[0].broadcastUri // "" | sub("vlan://"; "")),
89+
"mac_address": (."cloudstack.vm.details".nics[0].mac // "")
90+
} | to_entries | .[] | "\(.key)=\(.value)"')
91+
92+
for key in "${!details[@]}"; do
93+
declare -g "$key=${details[$key]}"
94+
done
95+
96+
check_required_fields vm_internal_name url user token secret node
97+
98+
if [[ -z "$vm_name" ]]; then
99+
vm_name="$vm_internal_name"
100+
fi
101+
validate_vm_name "$vm_name"
102+
vmid=$(cloudstack_vm_internal_name_to_proxmox_vmid "$vm_internal_name")
103+
}
104+
105+
call_proxmox_api() {
106+
local method=$1
107+
local path=$2
108+
local data=$3
109+
110+
echo "curl -sk --fail -X $method -H \"Authorization: PVEAPIToken=${user}!${token}=${secret}\" ${data:+-d \"$data\"} https://${url}:8006/api2/json${path}" >&2
111+
response=$(curl -sk --fail -X "$method" \
112+
-H "Authorization: PVEAPIToken=${user}!${token}=${secret}" \
113+
${data:+-d "$data"} \
114+
"https://${url}:8006/api2/json${path}")
115+
echo "$response"
116+
}
117+
29118
wait_for_proxmox_task() {
30119
local upid="$1"
31120
local timeout="${2:-$wait_time}"
@@ -39,7 +128,7 @@ wait_for_proxmox_task() {
39128
now=$(date +%s)
40129
if (( now - start_time > timeout )); then
41130
echo '{"error":"Timeout while waiting for async task"}'
42-
return 1
131+
exit 1
43132
fi
44133

45134
local status_response
@@ -49,7 +138,7 @@ wait_for_proxmox_task() {
49138
local msg
50139
msg=$(echo "$status_response" | jq -r '.message // "Unknown error"')
51140
echo "{\"error\":\"$msg\"}"
52-
return 1
141+
exit 1
53142
fi
54143

55144
local task_status
@@ -58,81 +147,29 @@ wait_for_proxmox_task() {
58147
if [[ "$task_status" == "stopped" ]]; then
59148
local exit_status
60149
exit_status=$(echo "$status_response" | jq -r '.data.exitstatus')
61-
echo "{\"exitstatus\":\"$exit_status\"}"
150+
if [[ "$exit_status" != "OK" ]]; then
151+
echo "{\"error\":\"Task failed with exit status: $exit_status\"}"
152+
exit 1
153+
fi
62154
return 0
63155
fi
64156

65157
sleep "$interval"
66158
done
67159
}
68160

69-
cloudstack_vmname_to_proxmox_vmid() {
70-
local vmname="$1"
71-
account=$(echo "$vmname" | cut -d '-' -f2)
72-
id=$(echo "$vmname" | cut -d '-' -f3)
73-
id=$(printf "%04d" "$id")
74-
vmid="${account}${id}"
75-
echo "$vmid"
76-
}
77-
78-
parse_json() {
79-
local json_string="$1"
80-
echo "$json_string" | jq '.' > /dev/null || { echo '{"error":"Invalid JSON input"}'; exit 1; }
81-
82-
local -A details
83-
while IFS="=" read -r key value; do
84-
details[$key]="$value"
85-
done < <(echo "$json_string" | jq -r '{
86-
"url": .external.extensionid.url,
87-
"user": .external.extensionid.user,
88-
"token": .external.extensionid.token,
89-
"secret": .external.extensionid.secret,
90-
"node": .external.hostid.node,
91-
"templateid": .external.virtualmachineid.templateid,
92-
"templatetype": .external.virtualmachineid.isiso,
93-
"vmname": ."cloudstack.vm.details".name,
94-
"vmmemory": ."cloudstack.vm.details".minRam,
95-
"vmcpus": ."cloudstack.vm.details".cpus,
96-
"vlan": (."cloudstack.vm.details".nics[0].broadcastUri | sub("vlan://"; "")),
97-
"mac_address": ."cloudstack.vm.details".nics[0].mac
98-
} | to_entries | .[] | "\(.key)=\(.value)"')
99-
100-
for key in "${!details[@]}"; do
101-
declare -g "$key=${details[$key]}"
102-
done
103-
104-
if [[ "$vmname" == "null" || "$url" == "null" || "$user" == "null" || "$token" == "null" || "$secret" == "null" || "$node" == "null" ]]; then
105-
echo '{"error":"Missing required fields in JSON input"}'
106-
exit 1
107-
fi
108-
109-
vmid=$(cloudstack_vmname_to_proxmox_vmid "$vmname")
110-
}
111-
112-
call_proxmox_api() {
113-
local method=$1
114-
local path=$2
115-
local data=$3
116-
117-
curl -sk -X "$method" \
118-
-H "Authorization: PVEAPIToken=${user}!${token}=${secret}" \
119-
${data:+-d "$data"} \
120-
"https://${url}:8006/api2/json${path}"
121-
}
122-
123161
execute_and_wait() {
124162
local method="$1"
125163
local path="$2"
126164
local data="$3"
165+
local response upid msg
127166

128-
local response
129167
response=$(call_proxmox_api "$method" "$path" "$data")
168+
upid=$(echo "$response" | jq -r '.data // ""')
130169

131-
local upid
132-
upid=$(echo "$response" | jq -r '.data')
133-
134-
if [[ -z "$upid" || "$upid" == "null" ]]; then
135-
echo '{"error":"Failed to execute API or retrieve UPID"}'
170+
if [[ -z "$upid" ]]; then
171+
msg=$(echo "$response" | jq -r '.message // "Unknown error"')
172+
echo "{\"error\":\"Failed to execute API or retrieve UPID. Message: $msg\"}"
136173
exit 1
137174
fi
138175

@@ -155,29 +192,32 @@ prepare() {
155192
create() {
156193
parse_json "$1" || exit 1
157194

158-
if [[ "$templatetype" == "ISO" ]]; then
195+
check_required_fields vm_name vlan mac_address
196+
if [[ "${template_type^^}" == "ISO" ]]; then
197+
check_required_fields iso_path vmcpus vmmemory
159198
local data="vmid=$vmid"
160-
data+="&name=$name"
199+
data+="&name=$vm_name"
161200
data+="&ide2=$(urlencode "$iso_path,media=cdrom")"
162201
data+="&ostype=l26"
163202
data+="&scsihw=virtio-scsi-single"
164-
data+="&scsi0=$(urlencode "local-lvm:32,iothread=on")"
203+
data+="&scsi0=$(urlencode "local-lvm:64,iothread=on")"
165204
data+="&sockets=1"
166205
data+="&cores=$vmcpus"
167206
data+="&numa=0"
168207
data+="&cpu=x86-64-v2-AES"
169-
data+="&memory=$vmmemory"
208+
data+="&memory=$((vmmemory / 1024 / 1024))"
170209
execute_and_wait POST "/nodes/${node}/qemu/" "$data"
171210
else
211+
check_required_fields template_id
172212
local data="newid=$vmid"
173-
data+="&name=$name"
174-
execute_and_wait POST "/nodes/${node}/qemu/${templateid}/clone" "$data"
213+
data+="&name=\"$vm_name\""
214+
execute_and_wait POST "/nodes/${node}/qemu/${template_id}/clone" "$data"
175215
fi
176216

177217
network="net0=$(urlencode "virtio=${mac_address},bridge=vmbr0,tag=${vlan},firewall=1")"
178-
call_proxmox_api PUT "/nodes/${node}/qemu/${vmid}/config/" "$network"
218+
call_proxmox_api PUT "/nodes/${node}/qemu/${vmid}/config/" "$network" > /dev/null
179219

180-
start "$1"
220+
execute_and_wait POST "/nodes/${node}/qemu/${vmid}/status/start"
181221

182222
echo '{"status": "success", "message": "Instance created"}'
183223
}

0 commit comments

Comments
 (0)