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 2e206b1..d429f92 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 @@ -39,21 +40,24 @@ 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("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("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("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("verify_ssl must be boolean.", 7); + throw new PVE2_Exception("PVE API: verify_ssl must be boolean.", 7); } $this->hostname = $hostname; @@ -106,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("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 { @@ -126,12 +130,22 @@ 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("PVE 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 + # (used in PVEAuthCookie) + 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. @@ -165,7 +179,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("PVE API: Not logged into Proxmox. No login Access Ticket found or Ticket expired.", 3); } // Prepare cURL resource. @@ -209,8 +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("Error - Invalid HTTP Method specified.", 5); - return false; + throw new PVE2_Exception("PVE API: Error - Invalid HTTP Method specified.", 5); } curl_setopt($prox_ch, CURLOPT_HEADER, true); @@ -230,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); @@ -251,22 +265,23 @@ private function action ($action_path, $http_method, $put_post_parameters = null return $action_response_array['data']; } } else { - error_log("This API Request Failed.\n" . - "HTTP Response - {$split_http_response_line[1]}\n" . - "HTTP Error - {$split_headers[0]}"); + if($verbose_mode === TRUE){ + 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}"); + } return false; } } else { - error_log("Error - Invalid HTTP Response.\n" . var_export($split_headers, true)); - return false; + 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 { - error_log("\$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)); - return false; } } @@ -286,7 +301,9 @@ public function reload_node_list () { $this->cluster_node_list = $nodes_array; return true; } else { - error_log(" Empty list of nodes returned in this cluster."); + if($verbose_mode === TRUE){ + error_log("PVE API: Empty list of Nodes returned in this Cluster."); + } return false; } } @@ -322,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(); @@ -342,19 +359,23 @@ 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."); + if($verbose_mode === TRUE){ + error_log("PVE API: Empty list of VMs returned in this Cluster."); + } return false; } } } else { - error_log(" Empty list of nodes returned in this cluster."); + if($verbose_mode === TRUE){ + error_log("PVE API: Empty list of Nodes returned in this Cluster."); + } return false; } } /* * bool|int start_vm ($node,$vmid) - * Start specific vm + * Start specific VM */ public function start_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -365,21 +386,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Started VM " . $vmid . ""); + } return true; } else { - error_log("Error starting vm " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error starting VM " . $vmid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * 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)){ @@ -391,21 +418,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Shutdown VM " . $vmid . ""); + } return true; } else { - error_log("Error shutting down vm " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error shutting down VM " . $vmid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * 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)){ @@ -417,21 +450,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Stopped VM " . $vmid . ""); + } return true; } else { - error_log("Error stopping vm " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error stopping VM " . $vmid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * 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)){ @@ -442,21 +481,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Resumed VM " . $vmid . ""); + } return true; } else { - error_log("Error resuming vm " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error resuming VM " . $vmid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * bool|int suspend_vm ($node,$vmid) - * Suspend specific vm + * Suspend specific VM */ public function suspend_vm ($node,$vmid) { if(isset($vmid) && isset($node)){ @@ -467,21 +512,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Suspended VM " . $vmid . ""); + } return true; } else { - error_log("Error suspending vm " . $vmid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error suspending VM " . $vmid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * 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)){ @@ -495,21 +546,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Cloned VM " . $vmid . " to " . $lastid . ""); + } return true; } else { - error_log("Error cloning vm " . $vmid . " to " . $lastid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error cloning VM " . $vmid . " to " . $lastid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * 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)){ @@ -531,21 +588,27 @@ 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 . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Snapshotted VM " . $vmid . " to " . $lastid . ""); + } return true; } else { - error_log("Error cloning vm " . $vmid . " to " . $lastid . ""); + if($verbose_mode === TRUE){ + error_log("PVE API: Error snapshotting VM " . $vmid . " to " . $lastid . ""); + } return false; } } else { - error_log("no vm or node valid"); + if($verbose_mode === TRUE){ + error_log("PVE API: No VM or Node valid"); + } return false; } } /* * 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"); @@ -587,4 +650,4 @@ public function delete ($action_path) { // Logout not required, PVEAuthCookie tokens have a 2 hour lifetime. } -?> +?> \ No newline at end of file