From 6b2e425d49b3c8d6a3bd80e2547913029f1b878d Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Fri, 9 Jun 2023 20:34:43 +1000 Subject: [PATCH 1/8] Add link to repo & remove (c) year Helps as the class gets added to code-bases, easier to track it --- pve2_api.class.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index 2e206b1..21fac2d 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -3,8 +3,9 @@ /* Proxmox VE APIv2 (PVE2) Client - PHP Class +https://github.com/CpuID/pve2-api-php-client/ -Copyright (c) 2012-2014 Nathan Sullivan +Copyright (c) Nathan Sullivan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From ee84c98ad0dee8b627fcdfcb3c98a33c6ddee276 Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Wed, 14 Jun 2023 17:50:57 +1000 Subject: [PATCH 2/8] Improve tagging of error_log usage --- pve2_api.class.php | 70 +++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index 21fac2d..fda7284 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -42,19 +42,19 @@ class PVE2_API { public function __construct ($hostname, $username, $realm, $password, $port = 8006, $verify_ssl = false) { if (empty($hostname) || empty($username) || empty($realm) || empty($password) || empty($port)) { - throw new PVE2_Exception("Hostname/Username/Realm/Password/Port required for PVE2_API object constructor.", 1); + throw new PVE2_Exception("PVE2 API: Hostname/Username/Realm/Password/Port required for PVE2_API object constructor.", 1); } // Check hostname resolves. if (gethostbyname($hostname) == $hostname && !filter_var($hostname, FILTER_VALIDATE_IP)) { - throw new PVE2_Exception("Cannot resolve {$hostname}.", 2); + throw new PVE2_Exception("PVE2 API: Cannot resolve {$hostname}.", 2); } // Check port is between 1 and 65535. if (!filter_var($port, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 65535]])) { - throw new PVE2_Exception("Port must be an integer between 1 and 65535.", 6); + throw new PVE2_Exception("PVE2 API: Port must be an integer between 1 and 65535.", 6); } // Check that verify_ssl is boolean. if (!is_bool($verify_ssl)) { - throw new PVE2_Exception("verify_ssl must be boolean.", 7); + throw new PVE2_Exception("PVE2 API: verify_ssl must be boolean.", 7); } $this->hostname = $hostname; @@ -107,7 +107,7 @@ public function login () { // Just to be safe, set this to null again. $this->login_ticket_timestamp = null; if ($login_request_info['ssl_verify_result'] == 1) { - throw new PVE2_Exception("Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); + throw new PVE2_Exception("PVE2 API: Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); } return false; } else { @@ -127,7 +127,7 @@ public function login () { # Use with care, and DO NOT use with root, it may harm your system public function setCookie() { if (!$this->check_login_ticket()) { - throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3); + throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); } setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/"); @@ -166,7 +166,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null } if (!$this->check_login_ticket()) { - throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3); + throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); } // Prepare cURL resource. @@ -210,7 +210,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); break; default: - throw new PVE2_Exception("Error - Invalid HTTP Method specified.", 5); + throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Method specified.", 5); return false; } @@ -252,20 +252,20 @@ private function action ($action_path, $http_method, $put_post_parameters = null return $action_response_array['data']; } } else { - error_log("This API Request Failed.\n" . + error_log("PVE2 API: This API Request Failed.\n" . "HTTP Response - {$split_http_response_line[1]}\n" . "HTTP Error - {$split_headers[0]}"); return false; } } else { - error_log("Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); + error_log("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); return false; } if (!empty($action_response_array['data'])) { return $action_response_array['data']; } else { - error_log("\$action_response_array['data'] is empty. Returning false.\n" . + error_log("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . var_export($action_response_array['data'], true)); return false; } @@ -287,7 +287,7 @@ public function reload_node_list () { $this->cluster_node_list = $nodes_array; return true; } else { - error_log(" Empty list of nodes returned in this cluster."); + error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); return false; } } @@ -343,12 +343,12 @@ public function get_vms () { $this->$cluster_vms_list = $result; return $this->$cluster_vms_list; } else { - error_log(" Empty list of vms returned in this cluster."); + error_log("PVE2 API: Empty list of VMs returned in this Cluster."); return false; } } } else { - error_log(" Empty list of nodes returned in this cluster."); + error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); return false; } } @@ -366,14 +366,14 @@ public function start_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/start"; $post = $this->post($url,$parameters); if ($post) { - error_log("Started vm " . $vmid . ""); + error_log("PVE2 API: Started VM " . $vmid . ""); return true; } else { - error_log("Error starting vm " . $vmid . ""); + error_log("PVE2 API: Error starting VM " . $vmid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } @@ -392,14 +392,14 @@ public function shutdown_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/shutdown"; $post = $this->post($url,$parameters); if ($post) { - error_log("Shutdown vm " . $vmid . ""); + error_log("PVE2 API: Shutdown VM " . $vmid . ""); return true; } else { - error_log("Error shutting down vm " . $vmid . ""); + error_log("PVE2 API: Error shutting down VM " . $vmid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } @@ -418,14 +418,14 @@ public function stop_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/stop"; $post = $this->post($url,$parameters); if ($post) { - error_log("Stopped vm " . $vmid . ""); + error_log("PVE2 API: Stopped VM " . $vmid . ""); return true; } else { - error_log("Error stopping vm " . $vmid . ""); + error_log("PVE2 API: Error stopping VM " . $vmid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } @@ -443,14 +443,14 @@ public function resume_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/resume"; $post = $this->post($url,$parameters); if ($post) { - error_log("Resumed vm " . $vmid . ""); + error_log("PVE2 API: Resumed VM " . $vmid . ""); return true; } else { - error_log("Error resuming vm " . $vmid . ""); + error_log("PVE2 API: Error resuming VM " . $vmid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } @@ -468,14 +468,14 @@ public function suspend_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/suspend"; $post = $this->post($url,$parameters); if ($post) { - error_log("Suspended vm " . $vmid . ""); + error_log("PVE2 API: Suspended VM " . $vmid . ""); return true; } else { - error_log("Error suspending vm " . $vmid . ""); + error_log("PVE2 API: Error suspending VM " . $vmid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } @@ -496,14 +496,14 @@ public function clone_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/clone"; $post = $this->post($url,$parameters); if ($post) { - error_log("Cloned vm " . $vmid . " to " . $lastid . ""); + error_log("PVE2 API: Cloned VM " . $vmid . " to " . $lastid . ""); return true; } else { - error_log("Error cloning vm " . $vmid . " to " . $lastid . ""); + error_log("PVE2 API: Error cloning VM " . $vmid . " to " . $lastid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } @@ -532,14 +532,14 @@ public function snapshot_vm ($node,$vmid,$snapname = NULL) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/snapshot"; $post = $this->post($url,$parameters); if ($post) { - error_log("Cloned vm " . $vmid . " to " . $lastid . ""); + error_log("PVE2 API: Snapshotted VM " . $vmid . " to " . $lastid . ""); return true; } else { - error_log("Error cloning vm " . $vmid . " to " . $lastid . ""); + error_log("PVE2 API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); return false; } } else { - error_log("no vm or node valid"); + error_log("PVE2 API: No VM or Node valid"); return false; } } From d4a2dedffb398afb03170b6bcb6bfaf0c90cab30 Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:01:16 +1000 Subject: [PATCH 3/8] Add getTicket(); comment out verbose logging; error pass-back detail --- pve2_api.class.php | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index fda7284..7e66f4f 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -133,6 +133,16 @@ public function setCookie() { setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/"); } + # Gets the PVE Access Ticket + # ie. for VNC tunnel access + public function getTicket() { + if ($this->login_ticket['ticket']) { + return $this->login_ticket; + } else { + return false; + } + } + /* * bool check_login_ticket () * Checks if the login ticket is valid still, returns false if not. @@ -231,12 +241,12 @@ private function action ($action_path, $http_method, $put_post_parameters = null $action_response_array = json_decode($body_response, true); $action_response_export = var_export($action_response_array, true); - error_log("----------------------------------------------\n" . - "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . - "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" . - "Data:\n\n{$body_response}\n\nEnd Data\n\n" . - "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . - "----------------------------------------------"); + // error_log("----------------------------------------------\n" . + // "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . + // "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" . + // "Data:\n\n{$body_response}\n\nEnd Data\n\n" . + // "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . + // "----------------------------------------------"); unset($action_response); unset($action_response_export); @@ -252,20 +262,21 @@ private function action ($action_path, $http_method, $put_post_parameters = null return $action_response_array['data']; } } else { - error_log("PVE2 API: This API Request Failed.\n" . - "HTTP Response - {$split_http_response_line[1]}\n" . - "HTTP Error - {$split_headers[0]}"); + throw new PVE2_Exception("PVE2 API: This API Request Failed.\n" . + "HTTP CODE: {$split_http_response_line[1]},\n" . + "HTTP ERROR: {$split_headers[0]},\n" . + "REPLY INFO: {$body_response}"); return false; } } else { - error_log("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); + throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); return false; } if (!empty($action_response_array['data'])) { return $action_response_array['data']; } else { - error_log("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . + throw new PVE2_Exception("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . var_export($action_response_array['data'], true)); return false; } From 8268aba64cce2401d2c385a42db7cc3d0f6d29cd Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:58:46 +1000 Subject: [PATCH 4/8] getTicket() return only ticket, not CSRF etc --- pve2_api.class.php | 623 ++------------------------------------------- 1 file changed, 24 insertions(+), 599 deletions(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index 7e66f4f..29c54d9 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -1,602 +1,27 @@ ['min_range' => 1, 'max_range' => 65535]])) { - throw new PVE2_Exception("PVE2 API: Port must be an integer between 1 and 65535.", 6); - } - // Check that verify_ssl is boolean. - if (!is_bool($verify_ssl)) { - throw new PVE2_Exception("PVE2 API: verify_ssl must be boolean.", 7); - } - - $this->hostname = $hostname; - $this->username = $username; - $this->realm = $realm; - $this->password = $password; - $this->port = $port; - $this->verify_ssl = $verify_ssl; - } - - /* - * bool login () - * Performs login to PVE Server using JSON API, and obtains Access Ticket. - */ - public function login () { - // Prepare login variables. - $login_postfields = array(); - $login_postfields['username'] = $this->username; - $login_postfields['password'] = $this->password; - $login_postfields['realm'] = $this->realm; - - $login_postfields_string = http_build_query($login_postfields); - unset($login_postfields); - - // Perform login request. - $prox_ch = curl_init(); - curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket"); - curl_setopt($prox_ch, CURLOPT_POST, true); - curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string); - curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl); - curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl); - - $login_ticket = curl_exec($prox_ch); - $login_request_info = curl_getinfo($prox_ch); - - curl_close($prox_ch); - unset($prox_ch); - unset($login_postfields_string); - - if (!$login_ticket) { - // SSL negotiation failed or connection timed out - $this->login_ticket_timestamp = null; - return false; - } - - $login_ticket_data = json_decode($login_ticket, true); - if ($login_ticket_data == null || $login_ticket_data['data'] == null) { - // Login failed. - // Just to be safe, set this to null again. - $this->login_ticket_timestamp = null; - if ($login_request_info['ssl_verify_result'] == 1) { - throw new PVE2_Exception("PVE2 API: Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); - } - return false; - } else { - // Login success. - $this->login_ticket = $login_ticket_data['data']; - // We store a UNIX timestamp of when the ticket was generated here, - // so we can identify when we need a new one expiration-wise later - // on... - $this->login_ticket_timestamp = time(); - $this->reload_node_list(); - return true; - } - } - - # Sets the PVEAuthCookie - # Attetion, after using this the user is logged into the web interface aswell! - # Use with care, and DO NOT use with root, it may harm your system - public function setCookie() { - if (!$this->check_login_ticket()) { - throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); - } - - setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/"); - } - - # Gets the PVE Access Ticket - # ie. for VNC tunnel access - public function getTicket() { - if ($this->login_ticket['ticket']) { - return $this->login_ticket; - } else { - return false; - } - } - - /* - * bool check_login_ticket () - * Checks if the login ticket is valid still, returns false if not. - * Method of checking is purely by age of ticket right now... - */ - protected function check_login_ticket () { - if ($this->login_ticket == null) { - // Just to be safe, set this to null again. - $this->login_ticket_timestamp = null; - return false; - } - if ($this->login_ticket_timestamp >= (time() + 7200)) { - // Reset login ticket object values. - $this->login_ticket = null; - $this->login_ticket_timestamp = null; - return false; - } else { - return true; - } - } - - /* - * object action (string action_path, string http_method[, array put_post_parameters]) - * This method is responsible for the general cURL requests to the JSON API, - * and sits behind the abstraction layer methods get/put/post/delete etc. - */ - private function action ($action_path, $http_method, $put_post_parameters = null) { - // Check if we have a prefixed / on the path, if not add one. - if (substr($action_path, 0, 1) != "/") { - $action_path = "/".$action_path; - } - - if (!$this->check_login_ticket()) { - throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); - } - - // Prepare cURL resource. - $prox_ch = curl_init(); - curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json{$action_path}"); - - $put_post_http_headers = array(); - $put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}"; - // Lets decide what type of action we are taking... - switch ($http_method) { - case "GET": - // Nothing extra to do. - break; - case "PUT": - curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "PUT"); - - // Set "POST" data. - $action_postfields_string = http_build_query($put_post_parameters); - curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); - unset($action_postfields_string); - - // Add required HTTP headers. - curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); - break; - case "POST": - curl_setopt($prox_ch, CURLOPT_POST, true); - - // Set POST data. - $action_postfields_string = http_build_query($put_post_parameters); - curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); - unset($action_postfields_string); - - // Add required HTTP headers. - curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); - break; - case "DELETE": - curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "DELETE"); - // No "POST" data required, the delete destination is specified in the URL. - - // Add required HTTP headers. - curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); - break; - default: - throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Method specified.", 5); - return false; - } - - curl_setopt($prox_ch, CURLOPT_HEADER, true); - curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']); - curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false); - - $action_response = curl_exec($prox_ch); - - curl_close($prox_ch); - unset($prox_ch); - - $split_action_response = explode("\r\n\r\n", $action_response, 2); - $header_response = $split_action_response[0]; - $body_response = $split_action_response[1]; - $action_response_array = json_decode($body_response, true); - - $action_response_export = var_export($action_response_array, true); - // error_log("----------------------------------------------\n" . - // "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . - // "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" . - // "Data:\n\n{$body_response}\n\nEnd Data\n\n" . - // "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . - // "----------------------------------------------"); - - unset($action_response); - unset($action_response_export); - - // Parse response, confirm HTTP response code etc. - $split_headers = explode("\r\n", $header_response); - if (substr($split_headers[0], 0, 9) == "HTTP/1.1 ") { - $split_http_response_line = explode(" ", $split_headers[0]); - if ($split_http_response_line[1] == "200") { - if ($http_method == "PUT") { - return true; - } else { - return $action_response_array['data']; - } - } else { - throw new PVE2_Exception("PVE2 API: This API Request Failed.\n" . - "HTTP CODE: {$split_http_response_line[1]},\n" . - "HTTP ERROR: {$split_headers[0]},\n" . - "REPLY INFO: {$body_response}"); - return false; - } - } else { - throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); - return false; - } - - if (!empty($action_response_array['data'])) { - return $action_response_array['data']; - } else { - throw new PVE2_Exception("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . - var_export($action_response_array['data'], true)); - return false; - } - } - - /* - * array reload_node_list () - * Returns the list of node names as provided by /api2/json/nodes. - * We need this for future get/post/put/delete calls. - * ie. $this->get("nodes/XXX/status"); where XXX is one of the values from this return array. - */ - public function reload_node_list () { - $node_list = $this->get("/nodes"); - if (count($node_list) > 0) { - $nodes_array = array(); - foreach ($node_list as $node) { - $nodes_array[] = $node['node']; - } - $this->cluster_node_list = $nodes_array; - return true; - } else { - error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); - return false; - } - } - - /* - * array get_node_list () - * - */ - public function get_node_list () { - // We run this if we haven't queried for cluster nodes as yet, and cache it in the object. - if ($this->cluster_node_list == null) { - if ($this->reload_node_list() === false) { - return false; - } - } - - return $this->cluster_node_list; - } - - /* - * bool|int get_next_vmid () - * Get Last VMID from a Cluster or a Node - * returns a VMID, or false if not found. - */ - public function get_next_vmid () { - $vmid = $this->get("/cluster/nextid"); - if ($vmid == null) { - return false; - } else { - return $vmid; - } - } - - /* - * array get_vms () - * Get List of all vms - */ - public function get_vms () { - $node_list = $this->get_node_list(); - $result=[]; - if (count($node_list) > 0) { - foreach ($node_list as $node_name) { - $vms_list = $this->get("nodes/" . $node_name . "/qemu/"); - if (count($vms_list) > 0) { - $key_values = array_column($vms_list, 'vmid'); - array_multisort($key_values, SORT_ASC, $vms_list); - foreach($vms_list as &$row) { - $row[node] = $node_name; - } - $result = array_merge($result, $vms_list); - } - if (count($result) > 0) { - $this->$cluster_vms_list = $result; - return $this->$cluster_vms_list; - } else { - error_log("PVE2 API: Empty list of VMs returned in this Cluster."); - return false; - } - } - } else { - error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); - return false; - } - } - - /* - * bool|int start_vm ($node,$vmid) - * Start specific vm - */ - public function start_vm ($node,$vmid) { - if(isset($vmid) && isset($node)){ - $parameters = array( - "vmid" => $vmid, - "node" => $node, - ); - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/start"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Started VM " . $vmid . ""); - return true; - } else { - error_log("PVE2 API: Error starting VM " . $vmid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|int shutdown_vm ($node,$vmid) - * Gracefully shutdown specific vm - */ - public function shutdown_vm ($node,$vmid) { - if(isset($vmid) && isset($node)){ - $parameters = array( - "vmid" => $vmid, - "node" => $node, - "timeout" => 60, - ); - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/shutdown"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Shutdown VM " . $vmid . ""); - return true; - } else { - error_log("PVE2 API: Error shutting down VM " . $vmid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|int stop_vm ($node,$vmid) - * Force stop specific vm - */ - public function stop_vm ($node,$vmid) { - if(isset($vmid) && isset($node)){ - $parameters = array( - "vmid" => $vmid, - "node" => $node, - "timeout" => 60, - ); - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/stop"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Stopped VM " . $vmid . ""); - return true; - } else { - error_log("PVE2 API: Error stopping VM " . $vmid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|int resume_vm ($node,$vmid) - * Resume from suspend specific vm - */ - public function resume_vm ($node,$vmid) { - if(isset($vmid) && isset($node)){ - $parameters = array( - "vmid" => $vmid, - "node" => $node, - ); - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/resume"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Resumed VM " . $vmid . ""); - return true; - } else { - error_log("PVE2 API: Error resuming VM " . $vmid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|int suspend_vm ($node,$vmid) - * Suspend specific vm - */ - public function suspend_vm ($node,$vmid) { - if(isset($vmid) && isset($node)){ - $parameters = array( - "vmid" => $vmid, - "node" => $node, - ); - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/suspend"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Suspended VM " . $vmid . ""); - return true; - } else { - error_log("PVE2 API: Error suspending VM " . $vmid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|int clone_vm ($node,$vmid) - * Create fullclone of vm - */ - public function clone_vm ($node,$vmid) { - if(isset($vmid) && isset($node)){ - $lastid = $this->get_next_vmid(); - $parameters = array( - "vmid" => $vmid, - "node" => $node, - "newid" => $lastid, - "full" => true, - ); - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/clone"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Cloned VM " . $vmid . " to " . $lastid . ""); - return true; - } else { - error_log("PVE2 API: Error cloning VM " . $vmid . " to " . $lastid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|int snapshot_vm ($node,$vmid,$snapname = NULL) - * Create snapshot of vm - */ - public function snapshot_vm ($node,$vmid,$snapname = NULL) { - if(isset($vmid) && isset($node)){ - $lastid = $this->get_next_vmid(); - if (is_null($snapname)){ - $parameters = array( - "vmid" => $vmid, - "node" => $node, - "vmstate" => true, - ); - } else { - $parameters = array( - "vmid" => $vmid, - "node" => $node, - "vmstate" => true, - "snapname" => $snapname, - ); - } - $url = "/nodes/" . $node . "/qemu/" . $vmid . "/snapshot"; - $post = $this->post($url,$parameters); - if ($post) { - error_log("PVE2 API: Snapshotted VM " . $vmid . " to " . $lastid . ""); - return true; - } else { - error_log("PVE2 API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); - return false; - } - } else { - error_log("PVE2 API: No VM or Node valid"); - return false; - } - } - - /* - * bool|string get_version () - * Return the version and minor revision of Proxmox Server - */ - public function get_version () { - $version = $this->get("/version"); - if ($version == null) { - return false; - } else { - return $version['version']; - } - } - - /* - * object/array? get (string action_path) - */ - public function get ($action_path) { - return $this->action($action_path, "GET"); - } - - /* - * bool put (string action_path, array parameters) - */ - public function put ($action_path, $parameters) { - return $this->action($action_path, "PUT", $parameters); - } - - /* - * bool post (string action_path, array parameters) - */ - public function post ($action_path, $parameters) { - return $this->action($action_path, "POST", $parameters); - } - - /* - * bool delete (string action_path) - */ - public function delete ($action_path) { - return $this->action($action_path, "DELETE"); - } - - // Logout not required, PVEAuthCookie tokens have a 2 hour lifetime. +// Take noVNC request from WHMCS Client Area, +// assign PVE Ticket as Browser Cookie, then +// redirect to the noVNC page inc. VNC path. +if (isset($_GET['pveticket']) && isset($_GET['host']) && isset($_GET['path'])) { + $pveticket = $_GET['pveticket']; + $host = $_GET['host']; + $path = $_GET['path']; + + // Get the requesting hostname/domain from request + $whmcsdomain = parse_url($_SERVER['HTTP_HOST']); + $domainonly = preg_replace("/^(.*?)\.(.*)$/","$2",$whmcsdomain['path']); + + // Set browser cookie with PVE Auth ticket (vnc user) + setrawcookie('PVEAuthCookie', rawurldecode($pveticket), 0, '/', $domainonly); + + // $path includes the VNC Ticket, so dual auth is handled + $hostname = gethostbyaddr($host); + $redirect_url = '/modules/servers/pvewhmcs/novnc/vnc.html?host=' . $hostname . '&port=8006&path=' . urlencode($path); + + // Redirect the user to the noVNC system with query strings inc. + header('Location: ' . $redirect_url); + exit; +} else { + echo 'Error: Missing required info to route your request. Please try again.'; } - ?> From 8b1ad23f89c877438ac84f4169cd8d7d83435446 Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:59:22 +1000 Subject: [PATCH 5/8] Wrong paste; redo of previous commit (ticket only) --- pve2_api.class.php | 622 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 598 insertions(+), 24 deletions(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index 29c54d9..a06d845 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -1,27 +1,601 @@ ['min_range' => 1, 'max_range' => 65535]])) { + throw new PVE2_Exception("PVE2 API: Port must be an integer between 1 and 65535.", 6); + } + // Check that verify_ssl is boolean. + if (!is_bool($verify_ssl)) { + throw new PVE2_Exception("PVE2 API: verify_ssl must be boolean.", 7); + } + + $this->hostname = $hostname; + $this->username = $username; + $this->realm = $realm; + $this->password = $password; + $this->port = $port; + $this->verify_ssl = $verify_ssl; + } + + /* + * bool login () + * Performs login to PVE Server using JSON API, and obtains Access Ticket. + */ + public function login () { + // Prepare login variables. + $login_postfields = array(); + $login_postfields['username'] = $this->username; + $login_postfields['password'] = $this->password; + $login_postfields['realm'] = $this->realm; + + $login_postfields_string = http_build_query($login_postfields); + unset($login_postfields); + + // Perform login request. + $prox_ch = curl_init(); + curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket"); + curl_setopt($prox_ch, CURLOPT_POST, true); + curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl); + + $login_ticket = curl_exec($prox_ch); + $login_request_info = curl_getinfo($prox_ch); + + curl_close($prox_ch); + unset($prox_ch); + unset($login_postfields_string); + + if (!$login_ticket) { + // SSL negotiation failed or connection timed out + $this->login_ticket_timestamp = null; + return false; + } + + $login_ticket_data = json_decode($login_ticket, true); + if ($login_ticket_data == null || $login_ticket_data['data'] == null) { + // Login failed. + // Just to be safe, set this to null again. + $this->login_ticket_timestamp = null; + if ($login_request_info['ssl_verify_result'] == 1) { + throw new PVE2_Exception("PVE2 API: Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); + } + return false; + } else { + // Login success. + $this->login_ticket = $login_ticket_data['data']; + // We store a UNIX timestamp of when the ticket was generated here, + // so we can identify when we need a new one expiration-wise later + // on... + $this->login_ticket_timestamp = time(); + $this->reload_node_list(); + return true; + } + } + + # Sets the PVEAuthCookie + # Attetion, after using this the user is logged into the web interface aswell! + # Use with care, and DO NOT use with root, it may harm your system + public function setCookie() { + if (!$this->check_login_ticket()) { + throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); + } + + setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/"); + } + + # Gets the PVE Access Ticket + public function getTicket() { + if ($this->login_ticket['ticket']) { + return $this->login_ticket['ticket']; + } else { + return false; + } + } + + /* + * bool check_login_ticket () + * Checks if the login ticket is valid still, returns false if not. + * Method of checking is purely by age of ticket right now... + */ + protected function check_login_ticket () { + if ($this->login_ticket == null) { + // Just to be safe, set this to null again. + $this->login_ticket_timestamp = null; + return false; + } + if ($this->login_ticket_timestamp >= (time() + 7200)) { + // Reset login ticket object values. + $this->login_ticket = null; + $this->login_ticket_timestamp = null; + return false; + } else { + return true; + } + } + + /* + * object action (string action_path, string http_method[, array put_post_parameters]) + * This method is responsible for the general cURL requests to the JSON API, + * and sits behind the abstraction layer methods get/put/post/delete etc. + */ + private function action ($action_path, $http_method, $put_post_parameters = null) { + // Check if we have a prefixed / on the path, if not add one. + if (substr($action_path, 0, 1) != "/") { + $action_path = "/".$action_path; + } + + if (!$this->check_login_ticket()) { + throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); + } + + // Prepare cURL resource. + $prox_ch = curl_init(); + curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json{$action_path}"); + + $put_post_http_headers = array(); + $put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}"; + // Lets decide what type of action we are taking... + switch ($http_method) { + case "GET": + // Nothing extra to do. + break; + case "PUT": + curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "PUT"); + + // Set "POST" data. + $action_postfields_string = http_build_query($put_post_parameters); + curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); + unset($action_postfields_string); + + // Add required HTTP headers. + curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); + break; + case "POST": + curl_setopt($prox_ch, CURLOPT_POST, true); + + // Set POST data. + $action_postfields_string = http_build_query($put_post_parameters); + curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string); + unset($action_postfields_string); + + // Add required HTTP headers. + curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); + break; + case "DELETE": + curl_setopt($prox_ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + // No "POST" data required, the delete destination is specified in the URL. + + // Add required HTTP headers. + curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); + break; + default: + throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Method specified.", 5); + return false; + } + + curl_setopt($prox_ch, CURLOPT_HEADER, true); + curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false); + + $action_response = curl_exec($prox_ch); + + curl_close($prox_ch); + unset($prox_ch); + + $split_action_response = explode("\r\n\r\n", $action_response, 2); + $header_response = $split_action_response[0]; + $body_response = $split_action_response[1]; + $action_response_array = json_decode($body_response, true); + + $action_response_export = var_export($action_response_array, true); + // error_log("----------------------------------------------\n" . + // "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . + // "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" . + // "Data:\n\n{$body_response}\n\nEnd Data\n\n" . + // "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . + // "----------------------------------------------"); + + unset($action_response); + unset($action_response_export); + + // Parse response, confirm HTTP response code etc. + $split_headers = explode("\r\n", $header_response); + if (substr($split_headers[0], 0, 9) == "HTTP/1.1 ") { + $split_http_response_line = explode(" ", $split_headers[0]); + if ($split_http_response_line[1] == "200") { + if ($http_method == "PUT") { + return true; + } else { + return $action_response_array['data']; + } + } else { + throw new PVE2_Exception("PVE2 API: This API Request Failed.\n" . + "HTTP CODE: {$split_http_response_line[1]},\n" . + "HTTP ERROR: {$split_headers[0]},\n" . + "REPLY INFO: {$body_response}"); + return false; + } + } else { + throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); + return false; + } + + if (!empty($action_response_array['data'])) { + return $action_response_array['data']; + } else { + throw new PVE2_Exception("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . + var_export($action_response_array['data'], true)); + return false; + } + } + + /* + * array reload_node_list () + * Returns the list of node names as provided by /api2/json/nodes. + * We need this for future get/post/put/delete calls. + * ie. $this->get("nodes/XXX/status"); where XXX is one of the values from this return array. + */ + public function reload_node_list () { + $node_list = $this->get("/nodes"); + if (count($node_list) > 0) { + $nodes_array = array(); + foreach ($node_list as $node) { + $nodes_array[] = $node['node']; + } + $this->cluster_node_list = $nodes_array; + return true; + } else { + error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + return false; + } + } + + /* + * array get_node_list () + * + */ + public function get_node_list () { + // We run this if we haven't queried for cluster nodes as yet, and cache it in the object. + if ($this->cluster_node_list == null) { + if ($this->reload_node_list() === false) { + return false; + } + } + + return $this->cluster_node_list; + } + + /* + * bool|int get_next_vmid () + * Get Last VMID from a Cluster or a Node + * returns a VMID, or false if not found. + */ + public function get_next_vmid () { + $vmid = $this->get("/cluster/nextid"); + if ($vmid == null) { + return false; + } else { + return $vmid; + } + } + + /* + * array get_vms () + * Get List of all vms + */ + public function get_vms () { + $node_list = $this->get_node_list(); + $result=[]; + if (count($node_list) > 0) { + foreach ($node_list as $node_name) { + $vms_list = $this->get("nodes/" . $node_name . "/qemu/"); + if (count($vms_list) > 0) { + $key_values = array_column($vms_list, 'vmid'); + array_multisort($key_values, SORT_ASC, $vms_list); + foreach($vms_list as &$row) { + $row[node] = $node_name; + } + $result = array_merge($result, $vms_list); + } + if (count($result) > 0) { + $this->$cluster_vms_list = $result; + return $this->$cluster_vms_list; + } else { + error_log("PVE2 API: Empty list of VMs returned in this Cluster."); + return false; + } + } + } else { + error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + return false; + } + } + + /* + * bool|int start_vm ($node,$vmid) + * Start specific vm + */ + public function start_vm ($node,$vmid) { + if(isset($vmid) && isset($node)){ + $parameters = array( + "vmid" => $vmid, + "node" => $node, + ); + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/start"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Started VM " . $vmid . ""); + return true; + } else { + error_log("PVE2 API: Error starting VM " . $vmid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|int shutdown_vm ($node,$vmid) + * Gracefully shutdown specific vm + */ + public function shutdown_vm ($node,$vmid) { + if(isset($vmid) && isset($node)){ + $parameters = array( + "vmid" => $vmid, + "node" => $node, + "timeout" => 60, + ); + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/shutdown"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Shutdown VM " . $vmid . ""); + return true; + } else { + error_log("PVE2 API: Error shutting down VM " . $vmid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|int stop_vm ($node,$vmid) + * Force stop specific vm + */ + public function stop_vm ($node,$vmid) { + if(isset($vmid) && isset($node)){ + $parameters = array( + "vmid" => $vmid, + "node" => $node, + "timeout" => 60, + ); + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/stop"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Stopped VM " . $vmid . ""); + return true; + } else { + error_log("PVE2 API: Error stopping VM " . $vmid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|int resume_vm ($node,$vmid) + * Resume from suspend specific vm + */ + public function resume_vm ($node,$vmid) { + if(isset($vmid) && isset($node)){ + $parameters = array( + "vmid" => $vmid, + "node" => $node, + ); + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/resume"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Resumed VM " . $vmid . ""); + return true; + } else { + error_log("PVE2 API: Error resuming VM " . $vmid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|int suspend_vm ($node,$vmid) + * Suspend specific vm + */ + public function suspend_vm ($node,$vmid) { + if(isset($vmid) && isset($node)){ + $parameters = array( + "vmid" => $vmid, + "node" => $node, + ); + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/suspend"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Suspended VM " . $vmid . ""); + return true; + } else { + error_log("PVE2 API: Error suspending VM " . $vmid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|int clone_vm ($node,$vmid) + * Create fullclone of vm + */ + public function clone_vm ($node,$vmid) { + if(isset($vmid) && isset($node)){ + $lastid = $this->get_next_vmid(); + $parameters = array( + "vmid" => $vmid, + "node" => $node, + "newid" => $lastid, + "full" => true, + ); + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/clone"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Cloned VM " . $vmid . " to " . $lastid . ""); + return true; + } else { + error_log("PVE2 API: Error cloning VM " . $vmid . " to " . $lastid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|int snapshot_vm ($node,$vmid,$snapname = NULL) + * Create snapshot of vm + */ + public function snapshot_vm ($node,$vmid,$snapname = NULL) { + if(isset($vmid) && isset($node)){ + $lastid = $this->get_next_vmid(); + if (is_null($snapname)){ + $parameters = array( + "vmid" => $vmid, + "node" => $node, + "vmstate" => true, + ); + } else { + $parameters = array( + "vmid" => $vmid, + "node" => $node, + "vmstate" => true, + "snapname" => $snapname, + ); + } + $url = "/nodes/" . $node . "/qemu/" . $vmid . "/snapshot"; + $post = $this->post($url,$parameters); + if ($post) { + error_log("PVE2 API: Snapshotted VM " . $vmid . " to " . $lastid . ""); + return true; + } else { + error_log("PVE2 API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); + return false; + } + } else { + error_log("PVE2 API: No VM or Node valid"); + return false; + } + } + + /* + * bool|string get_version () + * Return the version and minor revision of Proxmox Server + */ + public function get_version () { + $version = $this->get("/version"); + if ($version == null) { + return false; + } else { + return $version['version']; + } + } + + /* + * object/array? get (string action_path) + */ + public function get ($action_path) { + return $this->action($action_path, "GET"); + } + + /* + * bool put (string action_path, array parameters) + */ + public function put ($action_path, $parameters) { + return $this->action($action_path, "PUT", $parameters); + } + + /* + * bool post (string action_path, array parameters) + */ + public function post ($action_path, $parameters) { + return $this->action($action_path, "POST", $parameters); + } + + /* + * bool delete (string action_path) + */ + public function delete ($action_path) { + return $this->action($action_path, "DELETE"); + } + + // Logout not required, PVEAuthCookie tokens have a 2 hour lifetime. } + ?> From 68e5e8ab28e8bdbafc09205b33be8c2cbb90917e Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:12:17 +1000 Subject: [PATCH 6/8] Add $verbose_mode; except only (no return) (pull #42 review) --- pve2_api.class.php | 119 ++++++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index a06d845..70012d9 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -40,6 +40,9 @@ class PVE2_API { protected $login_ticket_timestamp = null; protected $cluster_node_list = null; + // Set verbosity on/off, default to off + public $verbose_mode = FALSE; + public function __construct ($hostname, $username, $realm, $password, $port = 8006, $verify_ssl = false) { if (empty($hostname) || empty($username) || empty($realm) || empty($password) || empty($port)) { throw new PVE2_Exception("PVE2 API: Hostname/Username/Realm/Password/Port required for PVE2_API object constructor.", 1); @@ -134,6 +137,7 @@ public function setCookie() { } # Gets the PVE Access Ticket + # (used in PVEAuthCookie) public function getTicket() { if ($this->login_ticket['ticket']) { return $this->login_ticket['ticket']; @@ -220,7 +224,6 @@ private function action ($action_path, $http_method, $put_post_parameters = null break; default: throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Method specified.", 5); - return false; } curl_setopt($prox_ch, CURLOPT_HEADER, true); @@ -240,13 +243,14 @@ private function action ($action_path, $http_method, $put_post_parameters = null $action_response_array = json_decode($body_response, true); $action_response_export = var_export($action_response_array, true); - // error_log("----------------------------------------------\n" . - // "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . - // "Headers:\n\n{$header_response}\n\nEnd Headers\n\n" . - // "Data:\n\n{$body_response}\n\nEnd Data\n\n" . - // "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . - // "----------------------------------------------"); - + if($verbose_mode === TRUE){ + error_log("----------------------------------------------\n" . + "FULL RESPONSE:\n\n{$action_response}\n\nEND FULL RESPONSE\n\n" . + "HEADERS:\n\n{$header_response}\n\nEND HEADERS\n\n" . + "DATA:\n\n{$body_response}\n\nEND DATA\n\n" . + "RESPONSE ARRAY:\n\n{$action_response_export}\n\nEND RESPONSE ARRAY\n" . + "----------------------------------------------"); + } unset($action_response); unset($action_response_export); @@ -265,11 +269,9 @@ private function action ($action_path, $http_method, $put_post_parameters = null "HTTP CODE: {$split_http_response_line[1]},\n" . "HTTP ERROR: {$split_headers[0]},\n" . "REPLY INFO: {$body_response}"); - return false; } } else { throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); - return false; } if (!empty($action_response_array['data'])) { @@ -277,7 +279,6 @@ private function action ($action_path, $http_method, $put_post_parameters = null } else { throw new PVE2_Exception("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . var_export($action_response_array['data'], true)); - return false; } } @@ -297,7 +298,9 @@ public function reload_node_list () { $this->cluster_node_list = $nodes_array; return true; } else { - error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + } return false; } } @@ -353,12 +356,16 @@ public function get_vms () { $this->$cluster_vms_list = $result; return $this->$cluster_vms_list; } else { - error_log("PVE2 API: Empty list of VMs returned in this Cluster."); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Empty list of VMs returned in this Cluster."); + } return false; } } } else { - error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + } return false; } } @@ -376,14 +383,20 @@ public function start_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/start"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Started VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Started VM " . $vmid . ""); + } return true; } else { - error_log("PVE2 API: Error starting VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error starting VM " . $vmid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } @@ -402,14 +415,20 @@ public function shutdown_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/shutdown"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Shutdown VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Shutdown VM " . $vmid . ""); + } return true; } else { - error_log("PVE2 API: Error shutting down VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error shutting down VM " . $vmid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } @@ -428,14 +447,20 @@ public function stop_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/stop"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Stopped VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Stopped VM " . $vmid . ""); + } return true; } else { - error_log("PVE2 API: Error stopping VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error stopping VM " . $vmid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } @@ -453,14 +478,20 @@ public function resume_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/resume"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Resumed VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Resumed VM " . $vmid . ""); + } return true; } else { - error_log("PVE2 API: Error resuming VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error resuming VM " . $vmid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } @@ -478,14 +509,20 @@ public function suspend_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/status/suspend"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Suspended VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Suspended VM " . $vmid . ""); + } return true; } else { - error_log("PVE2 API: Error suspending VM " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error suspending VM " . $vmid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } @@ -506,14 +543,20 @@ public function clone_vm ($node,$vmid) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/clone"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Cloned VM " . $vmid . " to " . $lastid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Cloned VM " . $vmid . " to " . $lastid . ""); + } return true; } else { - error_log("PVE2 API: Error cloning VM " . $vmid . " to " . $lastid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error cloning VM " . $vmid . " to " . $lastid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } @@ -542,14 +585,20 @@ public function snapshot_vm ($node,$vmid,$snapname = NULL) { $url = "/nodes/" . $node . "/qemu/" . $vmid . "/snapshot"; $post = $this->post($url,$parameters); if ($post) { - error_log("PVE2 API: Snapshotted VM " . $vmid . " to " . $lastid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Snapshotted VM " . $vmid . " to " . $lastid . ""); + } return true; } else { - error_log("PVE2 API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); + if($verbose_mode === TRUE){ + error_log("PVE2 API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); + } return false; } } else { - error_log("PVE2 API: No VM or Node valid"); + if($verbose_mode === TRUE){ + error_log("PVE2 API: No VM or Node valid"); + } return false; } } From 1ab736e55001de2398449f7882da1124c9d90ddb Mon Sep 17 00:00:00 2001 From: Luke S Thompson <73861168+lsthompson@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:15:30 +1000 Subject: [PATCH 7/8] Revert exception to error_log per review --- pve2_api.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pve2_api.class.php b/pve2_api.class.php index 70012d9..c3f0b05 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -265,10 +265,13 @@ private function action ($action_path, $http_method, $put_post_parameters = null return $action_response_array['data']; } } else { - throw new PVE2_Exception("PVE2 API: This API Request Failed.\n" . + if($verbose_mode === TRUE){ + error_log("PVE2 API: This API Request Failed.\n" . "HTTP CODE: {$split_http_response_line[1]},\n" . "HTTP ERROR: {$split_headers[0]},\n" . "REPLY INFO: {$body_response}"); + } + return false; } } else { throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); From 0238c583c358e4007ef8c06169e2756ba64b08bd Mon Sep 17 00:00:00 2001 From: Luke S Thompson Date: Wed, 3 Sep 2025 18:22:00 +1000 Subject: [PATCH 8/8] Tidy-up; improve README; etc. --- README.md | 27 +++++++++----- TODO | 1 - pve2_api.class.php | 90 +++++++++++++++++++++++----------------------- 3 files changed, 63 insertions(+), 55 deletions(-) delete mode 100644 TODO diff --git a/README.md b/README.md index 55f59c2..003cbea 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,23 @@ +# (PVE2 API) PHP Client + This class provides the building blocks for someone wanting to use PHP to talk to Proxmox's API. + Relatively simple piece of code, just provides a get/put/post/delete abstraction layer as methods on top of Proxmox's REST API, while also handling the Login Ticket headers required for authentication. -See http://pve.proxmox.com/wiki/Proxmox_VE_API for information about how this API works. -API spec available at https://pve.proxmox.com/pve-docs/api-viewer/index.html +## Docs + +- API info available: http://pve.proxmox.com/wiki/Proxmox_VE_API +- API spec available: https://pve.proxmox.com/pve-docs/api-viewer/index.html -## Requirements: ## +## Requirements -PHP 5/7/8 with cURL (including SSL/TLS) support. +- PHP 7.x.x/8.x.x +- cURL (inc. SSL/TLS) -## Usage: ## +## Usage Examples -Example - Return status array for each Proxmox Host in this cluster. +### Return Status for each Node in Cluster require_once("./pve2-api-php-client/pve2_api.class.php"); @@ -28,7 +34,7 @@ Example - Return status array for each Proxmox Host in this cluster. exit; } -Example - Create a new Linux Container (LXC) on the first host in the cluster. +### Create a new Linux Container (CT) require_once("./pve2-api-php-client/pve2_api.class.php"); @@ -64,7 +70,7 @@ Example - Create a new Linux Container (LXC) on the first host in the cluster. exit; } -Example - Modify DNS settings on an existing container on the first host. +### Modify DNS settings for existing CT require_once("./pve2-api-php-client/pve2_api.class.php"); @@ -90,7 +96,7 @@ Example - Modify DNS settings on an existing container on the first host. exit; } -Example - Delete an existing container. +### Delete an existing CT require_once("./pve2-api-php-client/pve2_api.class.php"); @@ -106,5 +112,8 @@ Example - Delete an existing container. exit; } +## License + Licensed under the MIT License. + See LICENSE file. diff --git a/TODO b/TODO deleted file mode 100644 index 6615912..0000000 --- a/TODO +++ /dev/null @@ -1 +0,0 @@ -- add a new class extending PVE2_API that provides abstraction methods to common tasks, ie. create VM/container, change container settings, etc. diff --git a/pve2_api.class.php b/pve2_api.class.php index c3f0b05..d429f92 100755 --- a/pve2_api.class.php +++ b/pve2_api.class.php @@ -45,19 +45,19 @@ class PVE2_API { public function __construct ($hostname, $username, $realm, $password, $port = 8006, $verify_ssl = false) { if (empty($hostname) || empty($username) || empty($realm) || empty($password) || empty($port)) { - throw new PVE2_Exception("PVE2 API: Hostname/Username/Realm/Password/Port required for PVE2_API object constructor.", 1); + throw new PVE2_Exception("PVE API: Hostname/Username/Realm/Password/Port required for PVE2_API object constructor.", 1); } // Check hostname resolves. if (gethostbyname($hostname) == $hostname && !filter_var($hostname, FILTER_VALIDATE_IP)) { - throw new PVE2_Exception("PVE2 API: Cannot resolve {$hostname}.", 2); + throw new PVE2_Exception("PVE API: Cannot resolve {$hostname}.", 2); } // Check port is between 1 and 65535. if (!filter_var($port, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 65535]])) { - throw new PVE2_Exception("PVE2 API: Port must be an integer between 1 and 65535.", 6); + throw new PVE2_Exception("PVE API: Port must be an integer between 1 and 65535.", 6); } // Check that verify_ssl is boolean. if (!is_bool($verify_ssl)) { - throw new PVE2_Exception("PVE2 API: verify_ssl must be boolean.", 7); + throw new PVE2_Exception("PVE API: verify_ssl must be boolean.", 7); } $this->hostname = $hostname; @@ -110,7 +110,7 @@ public function login () { // Just to be safe, set this to null again. $this->login_ticket_timestamp = null; if ($login_request_info['ssl_verify_result'] == 1) { - throw new PVE2_Exception("PVE2 API: Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); + throw new PVE2_Exception("PVE API: Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4); } return false; } else { @@ -130,7 +130,7 @@ public function login () { # Use with care, and DO NOT use with root, it may harm your system public function setCookie() { if (!$this->check_login_ticket()) { - throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); + throw new PVE2_Exception("PVE API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); } setrawcookie("PVEAuthCookie", $this->login_ticket['ticket'], 0, "/"); @@ -179,7 +179,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null } if (!$this->check_login_ticket()) { - throw new PVE2_Exception("PVE2 API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); + throw new PVE2_Exception("PVE API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); } // Prepare cURL resource. @@ -223,7 +223,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers); break; default: - throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Method specified.", 5); + throw new PVE2_Exception("PVE API: Error - Invalid HTTP Method specified.", 5); } curl_setopt($prox_ch, CURLOPT_HEADER, true); @@ -266,7 +266,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: This API Request Failed.\n" . + error_log("PVE API: This API Request Failed.\n" . "HTTP CODE: {$split_http_response_line[1]},\n" . "HTTP ERROR: {$split_headers[0]},\n" . "REPLY INFO: {$body_response}"); @@ -274,13 +274,13 @@ private function action ($action_path, $http_method, $put_post_parameters = null return false; } } else { - throw new PVE2_Exception("PVE2 API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); + throw new PVE2_Exception("PVE API: Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); } if (!empty($action_response_array['data'])) { return $action_response_array['data']; } else { - throw new PVE2_Exception("PVE2 API: \$action_response_array['data'] is empty. Returning false.\n" . + throw new PVE2_Exception("PVE API: \$action_response_array['data'] is empty. Returning false.\n" . var_export($action_response_array['data'], true)); } } @@ -302,7 +302,7 @@ public function reload_node_list () { return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + error_log("PVE API: Empty list of Nodes returned in this Cluster."); } return false; } @@ -339,7 +339,7 @@ public function get_next_vmid () { /* * array get_vms () - * Get List of all vms + * Get List of all VMs */ public function get_vms () { $node_list = $this->get_node_list(); @@ -360,14 +360,14 @@ public function get_vms () { return $this->$cluster_vms_list; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Empty list of VMs returned in this Cluster."); + error_log("PVE API: Empty list of VMs returned in this Cluster."); } return false; } } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Empty list of Nodes returned in this Cluster."); + error_log("PVE API: Empty list of Nodes returned in this Cluster."); } return false; } @@ -375,7 +375,7 @@ public function get_vms () { /* * bool|int start_vm ($node,$vmid) - * Start specific vm + * Start specific VM */ public function start_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -387,18 +387,18 @@ public function start_vm ($node,$vmid) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Started VM " . $vmid . ""); + error_log("PVE API: Started VM " . $vmid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error starting VM " . $vmid . ""); + error_log("PVE API: Error starting VM " . $vmid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -406,7 +406,7 @@ public function start_vm ($node,$vmid) { /* * bool|int shutdown_vm ($node,$vmid) - * Gracefully shutdown specific vm + * Gracefully shutdown specific VM */ public function shutdown_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -419,18 +419,18 @@ public function shutdown_vm ($node,$vmid) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Shutdown VM " . $vmid . ""); + error_log("PVE API: Shutdown VM " . $vmid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error shutting down VM " . $vmid . ""); + error_log("PVE API: Error shutting down VM " . $vmid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -438,7 +438,7 @@ public function shutdown_vm ($node,$vmid) { /* * bool|int stop_vm ($node,$vmid) - * Force stop specific vm + * Force stop specific VM */ public function stop_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -451,18 +451,18 @@ public function stop_vm ($node,$vmid) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Stopped VM " . $vmid . ""); + error_log("PVE API: Stopped VM " . $vmid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error stopping VM " . $vmid . ""); + error_log("PVE API: Error stopping VM " . $vmid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -470,7 +470,7 @@ public function stop_vm ($node,$vmid) { /* * bool|int resume_vm ($node,$vmid) - * Resume from suspend specific vm + * Resume from suspend specific VM */ public function resume_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -482,18 +482,18 @@ public function resume_vm ($node,$vmid) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Resumed VM " . $vmid . ""); + error_log("PVE API: Resumed VM " . $vmid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error resuming VM " . $vmid . ""); + error_log("PVE API: Error resuming VM " . $vmid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -501,7 +501,7 @@ public function resume_vm ($node,$vmid) { /* * bool|int suspend_vm ($node,$vmid) - * Suspend specific vm + * Suspend specific VM */ public function suspend_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -513,18 +513,18 @@ public function suspend_vm ($node,$vmid) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Suspended VM " . $vmid . ""); + error_log("PVE API: Suspended VM " . $vmid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error suspending VM " . $vmid . ""); + error_log("PVE API: Error suspending VM " . $vmid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -532,7 +532,7 @@ public function suspend_vm ($node,$vmid) { /* * bool|int clone_vm ($node,$vmid) - * Create fullclone of vm + * Create full clone of VM */ public function clone_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -547,18 +547,18 @@ public function clone_vm ($node,$vmid) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Cloned VM " . $vmid . " to " . $lastid . ""); + error_log("PVE API: Cloned VM " . $vmid . " to " . $lastid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error cloning VM " . $vmid . " to " . $lastid . ""); + error_log("PVE API: Error cloning VM " . $vmid . " to " . $lastid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -566,7 +566,7 @@ public function clone_vm ($node,$vmid) { /* * bool|int snapshot_vm ($node,$vmid,$snapname = NULL) - * Create snapshot of vm + * Create snapshot of VM */ public function snapshot_vm ($node,$vmid,$snapname = NULL) { if(isset($vmid) && isset($node)){ @@ -589,18 +589,18 @@ public function snapshot_vm ($node,$vmid,$snapname = NULL) { $post = $this->post($url,$parameters); if ($post) { if($verbose_mode === TRUE){ - error_log("PVE2 API: Snapshotted VM " . $vmid . " to " . $lastid . ""); + error_log("PVE API: Snapshotted VM " . $vmid . " to " . $lastid . ""); } return true; } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); + error_log("PVE API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); } return false; } } else { if($verbose_mode === TRUE){ - error_log("PVE2 API: No VM or Node valid"); + error_log("PVE API: No VM or Node valid"); } return false; } @@ -608,7 +608,7 @@ public function snapshot_vm ($node,$vmid,$snapname = NULL) { /* * bool|string get_version () - * Return the version and minor revision of Proxmox Server + * Return the version and minor revision of Proxmox VE (PVE) */ public function get_version () { $version = $this->get("/version"); @@ -650,4 +650,4 @@ public function delete ($action_path) { // Logout not required, PVEAuthCookie tokens have a 2 hour lifetime. } -?> +?> \ No newline at end of file