@@ -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+
29118wait_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-
123161execute_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() {
155192create () {
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