From 55a535a62b119d7407b9ee3085e59cc9645f4153 Mon Sep 17 00:00:00 2001 From: WhatTheServer Date: Sun, 11 Apr 2021 04:03:55 -0400 Subject: [PATCH] Update VultrAPI2.php Tested and corrected alot of functions based on testing the API. Refactored app_list and some others to match the old format response wise with hopes this will make things work smoothly when transitioning to V2 API as the data should be more like the old expected format.and require less refactoring. --- servers/vultr/vendor/VultrAPI2.php | 2571 ++++++++++++++-------------- 1 file changed, 1319 insertions(+), 1252 deletions(-) diff --git a/servers/vultr/vendor/VultrAPI2.php b/servers/vultr/vendor/VultrAPI2.php index 24f5d05..160b413 100644 --- a/servers/vultr/vendor/VultrAPI2.php +++ b/servers/vultr/vendor/VultrAPI2.php @@ -1,1254 +1,1321 @@ api_token = $token; - $this->cache_ttl = $cache_ttl; - $this->account = self::account_info(); - } - - /** - * Get Account info - * @see https://www.vultr.com/api/#tag/account - * @return mixed - */ - public function account_info() - { - return self::get('account'); - } - - /** - * Get OS list - * @see https://www.vultr.com/api/#operation/list-os - * @return mixed - */ - public function os_list() - { - return self::get('os'); - } - - /** - * OS Upgrade/Change Options - * @see https://www.vultr.com/api/#operation/get-instance-upgrades - * @see - * @param mixed $subid - * @return mixed - */ - public function os_change_list($subid) - { - return self::get("instances/{$subid}/upgrades?type=os"); - } - - /** - * OS Change/ Reinstall - * @see https://www.vultr.com/api/#operation/update-instance - * @see - * @param mixed $subid - * @param mixed $osid // https://www.vultr.com/api/#operation/list-isos - * @return mixed - */ - public function os_change($subid, $osid) - { - return self::patch("instances/{$subid}", array('os_id' => $osid)); - } - - /** - * List available ISO iamges - * @see https://www.vultr.com/api/#iso_list - * @return mixed Available ISO images - * */ - public function iso_list() - { - return self::get('iso'); - } - - /** - * Attach ISO - * @see https://www.vultr.com/api/#operation/attach-instance-iso - * @param mixed $subid - * @param mixed $isoid - * @return mixed - */ - public function attach_iso($subid, $isoid) - { - return self::post("instances/{$subid}/iso/attach", array('iso_id' => $isoid)); - } - - /** - * Detach ISO - * @see https://www.vultr.com/api/#operation/detach-instance-iso - * @param mixed $subid - * @return mixed - */ - public function detach_iso($subid) - { - return self::post("instances/{$subid}/iso/detach"); - } - - /** - * Get ISO Status - * @see https://www.vultr.com/api/#operation/get-instance-iso-status - * @param [type] $subid - * @return mixed - */ - public function iso_status($subid) - { - return self::get("instances/{$subid}/iso"); - } - - /** - * List available snapshots - * @see https://www.vultr.com/api/#snapshot_snapshot_list - * @return mixed - */ - public function snapshot_list() - { - return self::get('snapshots'); - } - - /** - * Destroy snapshot - * @see https://www.vultr.com/api/#snapshot_destroy - * @param int $snapshot_id - * @return int HTTP response code - */ - public function snapshot_destroy($snapshot_id) - { - //$args = array('SNAPSHOTID' => $snapshot_id); - return self::delete("snapshots/{$snapshot_id}"); - } - - /** - * Create snapshot - * @see https://www.vultr.com/api/#snapshot_create - * @param int $server_id - */ - public function snapshot_create($server_id, $description) - { - $args = array('instance_id' => $server_id, 'description' => $description); - return self::post('snapshots', $args); - } - - /** - * Create Domain - * @see https://www.vultr.com/api/#operation/create-dns-domain - * @param string $domain - * @param string $serverIP // optional IP for the Domain DNS - */ - public function domain_create($domain, $serverIP) - { - $args = array('domain' => $domain, 'ip' => $serverIP); - return self::post('domains', $args); - } - - /** - * Delete Domain - * @see https://www.vultr.com/api/#operation/delete-dns-domain-record - * @param string $domain - */ - public function domain_delete($domain) - { - //$args = array('domain' => $domain); - return self::delete("domains/{$domain}"); - } - - - - public function dns_list() - { - return self::get('domains'); - } - - public function dns_records($domain) - { - return self::get("domains/{$domain}/records"); - } - - public function create_record($domain, $args) - { - return self::post("domains/{$domain}/records", $args); - } - - public function delete_record($domain, $recordids) - { - return self::delete("domains/{$domain}/records{$recordid}"); - } - - public function update_record($domain, $args) - { - return self::patch("domains/{$domain}/records{$recordid}", $args); - } - - public function soa_update($domain, $args) - { - return self::patch("domains/{$domain}/soa", $args); - } - - public function soa_info($domain, $args) - { - return self::get("domains/{$domain}/soa", $args); - } - - public function upgrade_plan_list($subid) - { - return self::get("instances/{$subid}/upgrades" . '?type=plans'); - } - - /** - * Upgrade Plan - * @see https://www.vultr.com/api/#operation/update-instance - * @param string $subid - * @param integer $vpsplanid // https://www.vultr.com/api/#operation/list-plans - * @return void - */ - public function upgrade_plan($subid, $vpsplanid) - { - return self::patch("instances/{$subid}", array('plan' => $vpsplanid)); - } - - /** - * List available applications - * @see https://www.vultr.com/api/#app_app_list - * @return mixed Available applications - * */ - public function app_list() - { - return self::get('applications'); - } - - /** - * List available applications for instance - * @see https://www.vultr.com/api/#operation/get-instance-upgrades - * @return mixed Available applications - */ - public function app_change_list($subid) - { - return self::get("instances/{$subid}/upgrades?type=applications"); - } - - /** - * Reinstall via Application ID - * @see https://www.vultr.com/api/#operation/update-instance - * @param integer $subid // server - * @param integer $appid // Available applications from https://www.vultr.com/api/#operation/list-applications - */ - public function app_change($subid, $appid) - { - return self::patch("instances/{$subid}", array('app_id' => $appid)); - } - - /** - * List available plans - * @see https://www.vultr.com/api/#plans_plan_list - * @return mixed - */ - public function plans_list() - { - return self::get('plans'); - } - - /** - * List available regions - * @see https://www.vultr.com/api/#regions_region_list - * @return mixed - */ - public function regions_list() - { - return self::get('regions'); - } - - /** - * Determine region availability - * @see https://www.vultr.com/api/#regions_region_available - * @param int $datacenter_id - * @return mixed VPS plans available at given region - */ - public function regions_availability($datacenter_id) - { - $did = (int)$datacenter_id; - return self::get("regions/{$datacenter_id}/availability"); - } - - // https://www.vultr.com/api/#operation/get-instance-userdata - public function server_userData($server_id) - { - return self::get("instances/{$server_id}/user-data"); - } - - /** - * List startup scripts - * @see https://www.vultr.com/api/#startupscript_startupscript_list - * @return mixed List of startup scripts - */ - public function startupscript_list() - { - return self::get('startup-scripts'); - } - - /** - * Update startup script - * @see https://www.vultr.com/api/#operation/update-startup-script - * @param int $script_id - * @param string $name - * @param string $script script contents - * @return int HTTP response code - * */ - public function startupscript_update($script_id, $name, $script, $script_type='default') - { - $args = array( - 'type' => $script_type, - 'name' => $name, - 'script' => $script - ); - return self::patch("startup-scripts/{$script_id}", $args); - } - - /** - * Destroy startup script - * @see https://www.vultr.com/api/#startupscript_destroy - * @param int $script_id - * @return int HTTP respnose code - */ - public function startupscript_destroy($script_id) - { - //$args = array('SCRIPTID' => $script_id); - return self::delete("startup-scripts/{$script_id}"); - } - - /** - * Create startup script - * @see https://www.vultr.com/api/#operation/create-startup-script - * @param string $script_name - * @param string $script_contents - * @return int Script ID - */ - public function startupscript_create($script_name, $script_contents, $script_type) - { - $args = array( - 'name' => $script_name, - 'script' => $script_contents, - 'type' => $script_type - ); - $script = self::post('startup-scripts', $args); - return $script['startup_script']['id']; - } - - /** - * @param $region_id - * @param $plan_id - * @throws Exception - */ - public function server_available($region_id, $plan_id) - { - $availability = self::regions_availability($region_id); - if (!in_array($plan_id, $availability)) - { - throw new Exception('Plan ID ' . $plan_id . ' is not available in region ' . $region_id); - } - } - - /** - * List servers - * @see https://www.vultr.com/api/#server_server_list - * @return mixed List of servers - */ - public function server_list() - { - return self::get('instances'); - } - - /** - * Display server bandwidth - * @see https://www.vultr.com/api/#operation/get-instance-bandwidth - * @param int $server_id - * @return mixed Bandwidth history - */ - public function bandwidth($server_id) - { - //$args = array('SUBID' => (int)$server_id); - return self::get("instances/{$server_id}/bandwidth"); - } - - /** - * List IPv4 Addresses allocated to specified server - * @see https://www.vultr.com/api/#operation/get-instance-ipv4 - * @param int $server_id - * @return mixed IPv4 address list - */ - public function list_ipv4($server_id) - { - //$args = array('SUBID' => (int)$server_id); - $ipv4 = self::get("instances/{$server_id}/ipv4"); - return $ipv4[(int)$server_id]; - } - - /** - * Create IPv4 address - * @see https://www.vultr.com/api/#operation/create-instance-ipv4 - * @param int $server_id - * @param string Reboot server after adding IP: , default: yes - * @return int HTTP response code - * */ - public function ipv4_create($server_id, $reboot = 'yes') - { - $args = array( - 'reboot' => ($reboot == 'yes' ? 'yes' : 'no') - ); - return self::post("instances/{$server_id}/ipv4", $args); - } - - /** - * Destroy IPv4 Address - * @see https://www.vultr.com/api/#operation/delete-instance-ipv4 - * @param int $server_ID - * @param string $ip IPv4 address - * @return int HTTP response code - * */ - public function destroy_ipv4($server_id, $ip4) - { - - return self::delete("instances/{$server_id}/ipv4/{$ip4}"); - } - - /** - * Set Reverse DNS for IPv4 address - * @see https://www.vultr.com/api/#operation/create-instance-reverse-ipv4 - * @param string $ip - * @param string $rdns - * @return int HTTP response code - */ - public function reverse_set_ipv4($ip, $rdns, $id) - { - $args = array( - //'SUBID' => $id, - 'ip' => $ip, - 'reverse' => $rdns - - ); - return self::post("instances/{$id}/ipv4/reverse", $args); - } - - /** - * Set Default Reverse DNS for IPv4 address - * @see https://www.vultr.com/api/#operation/post-instances-instance-id-ipv4-reverse-default - * @param string $server_id - * @param string $ip - * @return int HTTP response code - */ - public function reverse_default_ipv4($server_id, $ip) - { - $args = array( - //'reverse' => (int)$server_id, - 'ip' => $ip - ); - return self::post("instances/{$server_id}/reverse/default", $args); - } - - /** - * List IPv6 addresses for specified server - * @see https://www.vultr.com/api/#operation/get-instance-ipv6 - * @param int $server_id - * @return mixed IPv6 allocation info - */ - public function list_ipv6($server_id) - { - //$args = array('SUBID' => (int)$server_id); - $ipv6 = self::get("instances/{$server_id}/ipv6"); - return $ipv6[(int)$server_id]; - } - - /** - * List Instance IPv6 Reverse - * @see https://www.vultr.com/api/#operation/list-instance-ipv6-reverse - * @param int $server_id - * @return mixed IPv6 allocation info - */ - public function reverse_list_ipv6($server_id) - { - - $ipv6 = self::get("instances/{$server_id}/ipv6/reverse"); - return $ipv6[(int)$server_id]; - } - - /** - * Get Application Information - * @see https://www.vultr.com/api/#operation/get-instance - * @param int $server_id - * @return mixed Application Information - */ - public function get_app_info($server_id) - { - //$args = array('SUBID' => (int)$server_id); - $app_id = self::get("instances/{$server_id}")['app_id']; - $app_info = self::get("instances/{$server_id}")['applications'][$app_id]; - return $app_info['name']; - } - - - /** - * Set Reverse DNS for IPv6 address - * @see https://www.vultr.com/api/#operation/create-instance-reverse-ipv6 - * @param int $server_id - * @param string $ip - * @param string $rdns - * @return int HTTP response code - */ - public function reverse_set_ipv6($server_id, $ip, $rdns) - { - $args = array( - 'ip' => $ip, - 'reverse' => $rdns - ); - return self::post('instances/{$server_id}/ipv6/reverse', $args); - } - - /** - * Delete IPv6 Reverse DNS - * @see https://www.vultr.com/api/#operation/delete-instance-reverse-ipv6 - * @param int $server_id - * @param string $ip6 IPv6 address - * @return int HTTP response code - * */ - public function reverse_delete_ipv6($server_id, $ip6) - { - - return self::delete("instances/{$server_id}/ipv6/reverse/{$ip6}"); - } - - /** - * Reboot server - * @see https://www.vultr.com/api/#operation/reboot-instance - * @param int $server_id - * @return int HTTP response code - */ - public function reboot($server_id) - { - return self::post("instances/{$server_id}/reboot"); - } - - /** - * Halt server - * @see https://www.vultr.com/api/#operation/halt-instances - * @param int $server_id - * @return int HTTP response code - */ - public function halt($server_id) - { - $args = array('instance_ids' => array((int)$server_id)); - return self::post('instances/halt', $args); - } - - /** - * Start server - * @see https://www.vultr.com/api/#operation/start-instance - * @param int $server_id - * @return int HTTP response code - */ - public function start($server_id) - { - return self::post("instances/{$server_id}/start"); - } - - /** - * Destroy server - * @see https://www.vultr.com/api/#operation/delete-instance - * @param int $server_id - * @return int HTTP response code - */ - public function destroy($server_id) - { - //$args = array('SUBID' => (int)$server_id); - return self::delete("instances/{$server_id}"); - } - - /** - * Reinstall OS on an instance - * @see https://www.vultr.com/api/#operation/reinstall-instance - * @param int $server_id - * @return int HTTP response code - */ - public function reinstall($server_id, $hostname='') - { - $args = array('hostname' => $hostname); - return self::post("instances/{$server_id}/reinstall", $args); - } - - /** - * Set server label - * @see https://www.vultr.com/api/#operation/update-instance - * @param int $server_id - * @param string $label - * @return int HTTP response code - */ - public function label_set($server_id, $label) - { - $args = array( - //'SUBID' => (int)$server_id, - 'label' => $label - ); - return self::patch("instances/{$server_id}", $args); - } - - /** - * Restore Server Snapshot - * @see https://www.vultr.com/api/#operation/restore-instance - * @param int $server_id - * @param string $snapshot_id Hexadecimal string with Restore ID - * @return int HTTP response code - */ - public function restore_snapshot($server_id, $snapshot_id) - { - $args = array( - //'backup_id' => (int)$server_id, - 'snapshot_id' => preg_replace('/[^a-f0-9]/', '', $snapshot_id) - ); - return self::post("instances/{$server_id}/restore", $args); - } - - /** - * Restore Backup - * @see https://www.vultr.com/api/#operation/restore-instance - * @param int $server_id - * @param string $backup_id - * @return int HTTP response code - */ - public function restore_backup($server_id, $backup_id) - { - $args = array( - //'SUBID' => $server_id, - 'backup_id' => $backup_id - ); - return self::post("instances/{$server_id}/restore", $args); - } - - /** - * List Backups - * @see https://www.vultr.com/api/#operation/list-backups - * @return mixed - */ - public function backup_list() - { - return self::get('backups'); - } - - /** - * Server Create - * @see https://www.vultr.com/api/#operation/create-instance - * @param $config - * @return bool|int|mixed|string - */ - public function create($config) - { - try - { - self::server_available((int)$config['DCID'], (int)$config['VPSPLANID']); - } - catch (Exception $e) - { - return FALSE; - } - - return self::post('instances', $config); - } - - /** - * SSH Keys List method - * @see https://www.vultr.com/api/#sshkey_sshkey_list - * @return FALSE if no SSH keys are available - * @return mixed with whatever ssh keys get returned - */ - public function sshkeys_list() - { - $try = self::get('ssh-keys'); - if (sizeof($try) < 1) - { - return FALSE; - } - return $try; - } - - /** - * SSH Keys Create method - * @see https://www.vultr.com/api/#sshkey_sshkey_create - * @param string $name - * @param string $key [openssh formatted public key] - * @return FALSE if no SSH keys are available - * @return mixed with whatever ssh keys get returned - */ - public function sshkey_create($name, $key) - { - $args = array( - 'name' => $name, - 'ssh_key' => $key - ); - return self::post('ssh-keys', $args); - } - - /** - * SSH Keys Update method - * @see https://www.vultr.com/api/#sshkey_sshkey_update - * @param string $key_id - * @param string $name - * @param string $key [openssh formatted public key] - * @return int HTTP response code - */ - public function sshkey_update($key_id, $name, $key) - { - $args = array( - 'SSHKEYID' => $key_id, - 'name' => $name, - 'ssh_key' => $key - ); - return self::patch('sshkey/update', $args); - } - - /** - * SSH Keys Destroy method - * @see https://www.vultr.com/api/#sshkey_sshkey_destroy - * @param string $key_id - * @return int HTTP response code - */ - public function sshkey_destroy($key_id) - { - $args = array('SSHKEYID' => $key_id); - return self::delete('sshkey/destroy', $args); - } - - /** - * GET Method - * @param string $method - * @param mixed $args - */ - public function get($method, $args = FALSE) - { - $this->request_type = 'GET'; - $this->get_code = false; - return self::query($method, $args); - } - - - /** - * DELETE Method - * @param $method - * @param $args - * @return bool|int|mixed|string - */ - public function delete($method, $args) - { - $this->request_type = 'DELETE'; - return self::query($method, $args); - } - - - // /** - // * CODE Method - // * @param string $method - // * @param mixed $args - // * @return mixed if no exceptions thrown - // * */ - // public function code($method, $args = FALSE) - // { - // $this->request_type = 'POST'; - // $this->get_code = true; - // return self::query($method, $args); - // } - - /** - * PATCH Method - * @param $method - * @param $args - * @return bool|int|mixed|string - */ - public function patch($method, $args) - { - $this->request_type = 'PATCH'; - return self::query($method, $args); - } - - /** - * POST Method - * @param $method - * @param $args - * @return bool|int|mixed|string - */ - public function post($method, $args) - { - $this->request_type = 'POST'; - return self::query($method, $args); - } - - /** - * PUT Method - * @param $method - * @param $args - * @return bool|int|mixed|string - */ - public function put($method, $args) - { - $this->request_type = 'PUT'; - return self::query($method, $args); - } - - /** - * API Query Function - * @param string $method - * @param mixed $args - */ - private function query($method, $args) - { - $methodArray = explode('/', $method); - $apiRequiredArray = array( - 'account', - 'auth', - 'backup', - 'baremetal', - 'block', - 'dns', - 'firewall', - 'iso', - 'network', - 'plans', - 'reservedip', - 'server', - 'snapshot', - 'sshkey', - 'startupscript', - 'user', - 'type', - 'per_page', - 'instance_id', - 'tag', - 'label', - 'main_ip', - 'cursor' - ); - - $url = $this->endpoint . $method; - $apiRequired = false; - - if ($this->debug) - { - echo $this->request_type . ' ' . $url . PHP_EOL; - } - - $_defaults = array( - CURLOPT_USERAGENT => sprintf('%s v%s (%s) - WHMCS Module', $this->agent, $this->version, 'https://github.com/whattheserver/whmcs-vultr'), - CURLOPT_HEADER => 0, - CURLOPT_VERBOSE => 0, - CURLOPT_SSL_VERIFYPEER => 0, - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_HTTP_VERSION => '1.0', - CURLOPT_FOLLOWLOCATION => 0, - CURLOPT_FRESH_CONNECT => 1, - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_FORBID_REUSE => 1, - CURLOPT_TIMEOUT => 30, - CURLOPT_HTTPHEADER => array('Accept: application/json') - ); - - if (in_array($methodArray[0], $apiRequiredArray)) - { - array_push($_defaults[CURLOPT_HTTPHEADER], "Authorization: Bearer $this->api_token"); - $apiRequired = true; - } - - $cacheable = false; - switch ($this->request_type) - { - - case 'POST': - $post_data = http_build_query($args); - $_defaults[CURLOPT_URL] = $url; - $_defaults[CURLOPT_POST] = 1; - $_defaults[CURLOPT_POSTFIELDS] = $post_data; - break; - - case 'PUT': - $post_data = http_build_query($args); - $_defaults[CURLOPT_URL] = $url; - $_defaults[CURLOPT_CUSTOMREQUEST] = 'PUT'; - $_defaults[CURLOPT_POSTFIELDS] = $post_data; - break; - - case 'PATCH': - $post_data = http_build_query($args); - $_defaults[CURLOPT_URL] = $url; - $_defaults[CURLOPT_CUSTOMREQUEST] = 'PATCH'; - $_defaults[CURLOPT_POSTFIELDS] = $post_data; - break; - - case 'DELETE': - //$post_data = http_build_query($args); - $_defaults[CURLOPT_URL] = $url; - $_defaults[CURLOPT_CUSTOMREQUEST] = 'DELETE'; - //$_defaults[CURLOPT_POSTFIELDS] = $post_data; - break; - - case 'GET': - if ($args !== FALSE) - { - $get_data = http_build_query($args); - $_defaults[CURLOPT_URL] = $url . '?' . $get_data; - } - else - { - $_defaults[CURLOPT_URL] = $url; - } - - $cacheable = true; - $response = $this->serveFromCache($_defaults[CURLOPT_URL]); - if ($response !== false) - { - $this->response_code = 200; - return $response; - } - break; - - default: - break; - } - - // To avoid rate limit hits - if ($this->readLast() == time() && $apiRequired) - { - usleep(333, 334); - } - - $apisess = curl_init(); - curl_setopt_array($apisess, $_defaults); - $response = curl_exec($apisess); - $httpCode = curl_getinfo($apisess, CURLINFO_HTTP_CODE); - logModuleCall('Vultr', $url, $args, "HTTP Code: " . $httpCode . "\n" . $response); - $this->writeLast(); - - /** - * Check to see if there were any API exceptions thrown - * If so, then error out, otherwise, keep going. - */ - try - { - self::isAPIError($apisess, $response); - } - catch (Exception $e) - { - curl_close($apisess); - $message = $e->getMessage() . PHP_EOL; - $this->message = $message; - return $message; - } - - /** - * Close our session - * Return the decoded JSON response - */ - curl_close($apisess); - $obj = json_decode($response, true); - - if ($this->get_code) - { - return (int)$this->response_code; - } - - if ($cacheable) - { - $this->saveToCache($url, $response); - } - else - { - $this->purgeCache($url); - } - - return $obj; - } - - public function getCode() - { - return (int)$this->response_code; - } - - public function checkConnection() - { - return $this->getCode() == 200 ? true : false; - } - - public function getMessage() - { - return $this->message; - } - - /** - * API Error Handling - * @param cURL_Handle $response_obj - * @param string $response - * @throws Exception if invalid API location is provided - * @throws Exception if API token is missing from request - * @throws Exception if API method does not exist - * @throws Exception if Internal Server Error occurs - * @throws Exception if the request fails otherwise - */ - public function isAPIError($response_obj, $response) - { - - $code = curl_getinfo($response_obj, CURLINFO_HTTP_CODE); - $this->response_code = $code; - - if ($this->debug) - { - echo $code . PHP_EOL; - } - - switch ($code) - { - case 200: - break; - case 201: - break; - case 202: - break; - case 204: - break; - case 400: - throw new Exception('400: Bad Request'); - break; - case 401: - throw new Exception('401: Unauthorized'); - break; - case 403: - throw new Exception('403: Forbidden'); - break; - case 404: - throw new Exception('404: Not Found'); - break; - case 500: - throw new Exception('500: Internal Server Error'); - break; - case 503: - throw new Exception('503: Service Unavailable. Your request exceeded the API rate limit.'); - break; - case 412: - throw new Exception('Request failed: ' . $response); - break; - default: - break; - } - } - - protected function serveFromCache($url) - { - // garbage collect 5% of the time - if (mt_rand(0, 19) == 0) - { - $files = glob("$this->cache_dir/*"); - $old = time() - ($this->cache_ttl * 2); - foreach ($files as $file) - { - if (filemtime($file) < $old) - { - unlink($old); - } - } - } - - $hash = md5($url); - $group = $this->groupFromUrl($url); - $file = "$this->cache_dir/$group-$hash"; - if (file_exists($file) && filemtime($file) > (time() - $this->cache_ttl)) - { - $response = file_get_contents($file); - $obj = json_decode($response, true); - return $obj; - } - return false; - } - - protected function saveToCache($url, $json) - { - if (!file_exists($this->cache_dir)) - { - mkdir($this->cache_dir); - } - - $hash = md5($url); - $group = $this->groupFromUrl($url); - $file = "$this->cache_dir/$group-$hash"; - file_put_contents($file, $json); - } - - protected function groupFromUrl($url) - { - $group = 'default'; - if (preg_match('@/v1/([^/]+)/@', $url, $match)) - { - return $match[1]; - } - } - - protected function purgeCache($url) - { - $group = $this->groupFromUrl($url); - $files = glob("$this->cache_dir/$group-*"); - foreach ($files as $file) - { - unlink($file); - } - } - - protected function writeLast() - { - if (!file_exists($this->cache_dir)) - { - mkdir($this->cache_dir); - } - - file_put_contents("$this->cache_dir/last", time()); - } - - protected function readLast() - { - if (file_exists("$this->cache_dir/last")) - { - return file_get_contents("$this->cache_dir/last"); - } - } - } +if (!class_exists('VultrAPI2')) { + /** + * Vultr.com API Client + * @package vultr + * @version 2.0 + * @author https://github.com/whattheserver/whmcs-vultr + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @see https://github.com/whattheserver/whmcs-vultr/ + */ + class VultrAPI2 + { + + /** + * API Token + * @access private + * @type string $api_token Vultr.com API token + * @see https://my.vultr.com/settings/ + */ + private $api_token = ''; + + /** + * API Endpoint + * @access public + * @type string URL for Vultr.com API + */ + public $endpoint = 'https://api.vultr.com/v2/'; + + /** + * Current Version + * @access public + * @type string Current version number + */ + public $version = '2.0'; + + /** + * User Agent + * @access public + * @type string API User-Agent string + */ + public $agent = 'Vultr.com API Client'; + + /** + * Debug Variable + * @access public + * @type bool Debug API requests + */ + public $debug = true; + + /** + * Snapshots Variable + * @access public + * @type mixed Array to store snapshot IDs + */ + public $snapshots = array(); + + /** + * Plans Variable + * @access public + * @type mixed Array to store VPS Plan IDs + */ + public $plans = array(); + + /** + * Regions Variable + * @access public + * @type mixed Array to store available regions + */ + public $regions = array(); + + /** + * Scripts Variable + * @access public + * @type mixed Array to store startup scripts + */ + public $scripts = array(); + + /** + * Servers Variable + * @access public + * @type mixed Array to store server data + */ + public $servers = array(); + + /** + * Account Variable + * @access public + * @type mixed Array to store account data + */ + public $account = array(); + + /** + * OS List Variable + * @access public + * @type mixed Array to store OS list + */ + public $oses = array(); + + /** + * SSH Keys variable + * @access public + * @type mixed Array to store SSH keys + * */ + public $ssh_keys = array(); + + /** + * Response code variable + * @access public + * @type int Holds HTTP response code from API + * */ + public $response_code = 0; + + /** + * Response code variable + * @access public + * @type bool Determines whether to include the response code, default: false + * */ + public $get_code = false; + + /** + * Cache ttl for all get requests + * @access public + * $type int TTL in seconds + */ + public $cache_ttl = 1; + public $message = ''; + + /** + * Cache folder + * @access public + * $type string Cache dir + */ + public $cache_dir = '/tmp/vultr-api-client-cache'; + private $request_type; + + /** + * Constructor function + * @param string $token + * @param int $cache_ttl + * @return void + * @see https://my.vultr.com/settings/ + */ + public function __construct(string $token, $cache_ttl = 1) + { + $this->api_token = $token; + $this->cache_ttl = $cache_ttl; + $this->account = self::account_info(); + } + + /** + * Get Account info + * @see https://www.vultr.com/api/#tag/account + * @return mixed + */ + public function account_info() + { + return self::get('account'); + } + + /** + * Get OS list + * @see https://www.vultr.com/api/#operation/list-os + * @return mixed + */ + public function os_list() + { + return self::get('os'); + } + + /** + * OS Upgrade/Change Options + * @see https://www.vultr.com/api/#operation/get-instance-upgrades + * @see + * @param string $instanceid + * @return mixed + */ + public function os_change_list(string $instanceid) + { + return self::get("instances/{$instanceid}/upgrades?type=os"); + } + + /** + * OS Change/ Reinstall + * @see https://www.vultr.com/api/#operation/update-instance + * @see + * @param string $instanceid + * @param string $osid // https://www.vultr.com/api/#operation/list-isos + * @return mixed + */ + public function os_change(string $instanceid, string $osid) + { + return self::patch("instances/{$instanceid}", array('os_id' => $osid)); + } + + /** + * List available ISOs in your account. + * @see https://www.vultr.com/api/#iso_list + * @return mixed Available ISO images + * */ + public function iso_list() + { + return self::get('iso'); + } + + /** + * List all Vultr Public ISOs. + * @see https://www.vultr.com/api/#operation/list-public-isos + * @return array Available ISO images + * */ + public function public_iso_list() + { + return self::get('iso-public'); + } + + + /** + * Attach an ISO to an Instance. + * @see https://www.vultr.com/api/#operation/attach-instance-iso + * @param string $instanceid AKA Instance ID: 35sdw3f-234w-5678-e89g-2adfwebiuewib + * @param string $isoid cb676a46-66fd-4dfb-b839-443f2e6c0b60 + * @return mixed 202|400|401|404 + */ + public function attach_iso(string $instanceid, string $isoid) + { + return self::post("instances/{$instanceid}/iso/attach", array('iso_id' => $isoid)); + } + + /** + * Detach ISO + * @see https://www.vultr.com/api/#operation/detach-instance-iso + * @param string $instanceid + * @return mixed + */ + public function detach_iso(string $instanceid) + { + return self::post("instances/{$instanceid}/iso/detach", []); + } + + /** + * Get ISO Status + * @see https://www.vultr.com/api/#operation/get-instance-iso-status + * @param string $instanceid + * @return mixed + */ + public function iso_status(string $instanceid) + { + return self::get("instances/{$instanceid}/iso"); + } + + /** + * List available snapshots + * @see https://www.vultr.com/api/#snapshot_snapshot_list + * @return mixed + */ + public function snapshot_list() + { + return self::get('snapshots'); + } + + /** + * Destroy snapshot + * @see https://www.vultr.com/api/#snapshot_destroy + * @param string $snapshot_id Example: c68bcc12-7852-4b08-9294-b81b6e7a728f + * @return int HTTP response code + */ + public function snapshot_destroy(string $snapshot_id) + { + //$args = array('SNAPSHOTID' => $snapshot_id); + return self::delete("snapshots/{$snapshot_id}"); + } + + /** + * Create snapshot + * @see https://www.vultr.com/api/#operation/create-snapshot + * @param string $instanceid + * @param $description + * @return bool|int|mixed|string + */ + public function snapshot_create(string $instanceid, $description) + { + $desc = preg_replace('/\s+/', '_', $description); + $args = array('instance_id' => $instanceid, 'description' => $desc); + return self::post('snapshots', $args); + } + + /** + * List all DNS Domains in your account. + * @see https://www.vultr.com/api/#operation/list-dns-domains + * @return int|mixed|string + */ + public function dns_list() + { + return self::get('domains'); + } + + /** + * Create Domain + * @see https://www.vultr.com/api/#operation/create-dns-domain + * @param string $domain Your registered DNS Domain name. + * @param string|null $ip The default IP address for your DNS Domain. If omitted an empty domain zone will be created. + * @param bool $dns_sec Enable or disable DNSSEC. + * @return bool|int|mixed|string + */ + public function domain_create(string $domain, string $ip=null, bool $dns_sec=false) + { + $args = array('domain' => $domain); + if ($ip !== null) { + $args['ip'] = $ip; + } + if ($dns_sec !== false) { + $args['dns_sec'] = $dns_sec; + } + return self::post('domains', $args); + } + + /** + * Delete Domain + * @see https://www.vultr.com/api/#operation/delete-dns-domain + * @param string $domain + * @return bool|int|mixed|string + */ + public function domain_delete(string $domain) + { + return self::delete("domains/{$domain}"); + } + + /** + * Get the DNS records for the Domain. + * @param $domain + * @see https://www.vultr.com/api/#operation/list-dns-domain-records + * @return int|mixed|string + */ + public function dns_records($domain) + { + return self::get("domains/{$domain}/records"); + } + + /** + * Create a DNS record for a domain. + * @see https://www.vultr.com/api/#operation/create-dns-domain-record + * @param $domain + * @param $args + * @return bool|int|mixed|string + */ + public function create_record($domain, $args) + { + return self::post("domains/{$domain}/records", $args); + } + + /** + * Delete a DNS record. + * @see https://www.vultr.com/api/#operation/delete-dns-domain-record + * @param string $domain The DNS Domain. + * @param string $recordid The DNS Record id. + * @return bool|int|mixed|string + */ + public function delete_record(string $domain, string $recordid) + { + return self::delete("domains/{$domain}/records/{$recordid}"); + } + + /** + * Update the information for a DNS record. All attributes are optional. + * If not set, the attributes will retain their original values. + * @see https://www.vultr.com/api/#operation/update-dns-domain-record + * @param $args + * @return bool|int|mixed|string + */ + public function update_record($args) + { + $domain = $args['domain']; + $recordid = $args['RECORDID']; + unset($args['domain']); + unset($args['RECORDID']); + return self::patch("domains/{$domain}/records/{$recordid}", $args); + } + + /** + * Update SOA information + * @see https://www.vultr.com/api/#operation/update-dns-domain-soa + * @param array $args : ["domain" => $domain, "nsprimary" => $nameServer1, "email" => $email] + * @return bool|int|mixed|string + */ + public function soa_update(array $args) + { + $domain = $args['domain']; + unset($args['domain']); + return self::patch("domains/{$domain}/soa", $args); + } + + /** + * Get SOA information for the DNS Domain. + * @see https://www.vultr.com/api/#operation/get-dns-domain-soa + * @param string $domain + * @return int|mixed|string + */ + public function soa_info(string $domain) + { + return self::get("domains/{$domain}/soa"); + } + + /** + * Get Available Instance Upgrades + * @see https://www.vultr.com/api/#operation/get-instance-upgrades + * @param string $instanceid + * @return int|mixed|string + */ + public function upgrade_plan_list(string $instanceid) + { + return self::get("instances/{$instanceid}/upgrades" . '?type=plans'); + } + + /** + * Upgrade Plan / Update Instance + * @see https://www.vultr.com/api/#operation/update-instance + * @param string $instanceid + * @param string $vpsplanid // https://www.vultr.com/api/#operation/list-plans + * @return void + */ + public function upgrade_plan(string $instanceid, string $vpsplanid) + { + return self::patch("instances/{$instanceid}", ['plan' => $vpsplanid]); + } + + /** + * List available applications + * @see https://www.vultr.com/api/#app_app_list + * @return array Available applications + * */ + public function app_list(): array + { + $vultr_apps_nice = []; + $apps = self::get('applications'); + + foreach ($apps as $app) { + foreach ($app as $key=>$value) { + if (!empty($value['id'])) { + $app_fixed = [ + "APPID" => $value['id'], + "name" => $value['name'], + "short_name" => $value['short_name'], + "deploy_name" => $value['deploy_name'], + ]; + $vultr_apps_nice[$value['id']] = $app_fixed; + } + } + } + return $vultr_apps_nice; + } + + /** + * List available applications for instance + * @see https://www.vultr.com/api/#operation/get-instance-upgrades + * @param string $instanceid + * @return mixed Available applications + */ + public function app_change_list(string $instanceid) + { + return self::get("instances/{$instanceid}/upgrades?type=applications"); + } + + /** + * Reinstall via Application ID + * @see https://www.vultr.com/api/#operation/update-instance + * @param string $instanceid // server + * @param integer $appid // Available applications from https://www.vultr.com/api/#operation/list-applications + * @return bool|int|mixed|string + */ + public function app_change(string $instanceid, int $appid) + { + return self::patch("instances/{$instanceid}", ['app_id' => $appid]); + } + + /** + * List available plans + * @see https://www.vultr.com/api/#plans_plan_list + * @return mixed + */ + public function plans_list() + { + return self::get('plans'); + } + + /** + * List available regions + * @see https://www.vultr.com/api/#regions_region_list + * @return mixed + */ + public function regions_list() + { + return self::get('regions'); + } + + /** + * Determine region availability + * @see https://www.vultr.com/api/#operation/list-available-compute-region + * @param string $region_id + * @return mixed VPS plans available at given region + */ + public function regions_availability(string $region_id) + { + return self::get("regions/{$region_id}/availability"); + } + + + /** + * Get Instance userdata + * Get the user-supplied, base64 encoded user data for an Instance. + * @see https://www.vultr.com/api/#operation/get-instance-userdata + * @param string $instanceid + * @return int|mixed|string + */ + public function server_userData(string $instanceid) + { + return self::get("instances/{$instanceid}/user-data"); + } + + /** + * List startup scripts + * @see https://www.vultr.com/api/#operation/list-startup-scripts + * @return mixed List of startup scripts + */ + public function startupscript_list() + { + return self::get('startup-scripts'); + } + + /** + * Update startup script + * @see https://www.vultr.com/api/#operation/update-startup-script + * @param string $script_id + * @param string $name The name of the Startup Script. + * @param string $script The base-64 encoded Startup Script contents + * @param string $script_type The Startup Script type. boot (default) pxe + * @return int HTTP response code + */ + public function startupscript_update(string $script_id, string $name, string $script, $script_type='default') + { + $args = array( + 'type' => $script_type, + 'name' => $name, + 'script' => $script + ); + return self::patch("startup-scripts/{$script_id}", $args); + } + + /** + * Destroy startup script + * @see https://www.vultr.com/api/#startupscript_destroy + * @param string $script_id + * @return int HTTP response code + */ + public function startupscript_destroy(string $script_id) + { + return self::delete("startup-scripts/{$script_id}"); + } + + /** + * Create startup script + * @see https://www.vultr.com/api/#operation/create-startup-script + * @param string $script_name The name of the Startup Script. + * @param string $script_contents The base-64 encoded Startup Script. + * @param string $script_type boot (default) or pxe + * @return string Script ID: "id": "cb676a46-66fd-4dfb-b839-443f2e6c0b60" + */ + public function startupscript_create(string $script_name, string $script_contents, string $script_type) + { + $args = array( + 'name' => $script_name, + 'script' => $script_contents, + 'type' => $script_type + ); + $script = self::post('startup-scripts', $args); + return $script['startup_script']['id']; + } + + /** + * Server Available in Region + * @param string $region_id : https://www.vultr.com/api/#operation/list-regions + * @param string $plan_id : https://www.vultr.com/api/#operation/list-plans + * @throws Exception + */ + public function server_available(string $region_id, string $plan_id) + { + $availability = self::regions_availability($region_id); + if (!in_array($plan_id, $availability)) { + throw new Exception('Plan ID ' . $plan_id . ' is not available in region ' . $region_id); + } + } + + /** + * List servers + * @see https://www.vultr.com/api/#server_server_list + * @return mixed List of servers + */ + public function server_list() + { + return self::get('instances'); + } + + /** + * Display server bandwidth + * @see https://www.vultr.com/api/#operation/get-instance-bandwidth + * @param string $instanceid + * @return mixed Bandwidth history + */ + public function bandwidth(string $instanceid) + { + return self::get("instances/{$instanceid}/bandwidth"); + } + + /** + * List IPv4 Addresses allocated to specified server + * @see https://www.vultr.com/api/#operation/get-instance-ipv4 + * @param string $instanceid + * @return mixed IPv4 address list + */ + public function list_ipv4(string $instanceid) + { + $ipv4 = self::get("instances/{$instanceid}/ipv4"); + return $ipv4['ipv4s']; + } + + /** + * Create IPv4 address + * @see https://www.vultr.com/api/#operation/create-instance-ipv4 + * @param string $instanceid + * @param string Reboot server after adding IP: , default: yes + * @return int HTTP response code + * */ + public function ipv4_create(string $instanceid, $reboot='yes') + { + $args = array( + 'reboot' => ($reboot == 'yes' ? 'yes' : 'no') + ); + return self::post("instances/{$instanceid}/ipv4", $args); + } + + /** + * Destroy IPv4 Address + * @see https://www.vultr.com/api/#operation/delete-instance-ipv4 + * @param string $instanceid + * @param string $ip4 + * @return int HTTP response code + */ + public function destroy_ipv4(string $instanceid, string $ip4) + { + return self::delete("instances/{$instanceid}/ipv4/{$ip4}"); + } + + /** + * Set Reverse DNS for IPv4 address + * @see https://www.vultr.com/api/#operation/create-instance-reverse-ipv4 + * @param string $ip The IPv4 address. + * @param string $rdns The IPv4 reverse entry. + * @param string $instanceid + * @return int HTTP response code + */ + public function reverse_set_ipv4(string $ip, string $rdns, string $instanceid) + { + $args = [ + "ip" => $ip, + "reverse" => $rdns]; + + return self::post("instances/{$instanceid}/ipv4/reverse", $args); + } + + /** + * Set Default Reverse DNS for IPv4 address + * @see https://www.vultr.com/api/#operation/post-instances-instance-id-ipv4-reverse-default + * @param string $instanceid + * @param string $ip + * @return int HTTP response code + */ + public function reverse_default_ipv4(string $instanceid, string $ip) + { + $args = array( + 'ip' => $ip + ); + return self::post("instances/{$instanceid}/reverse/default", $args); + } + + /** + * List IPv6 addresses for specified server + * @see https://www.vultr.com/api/#operation/get-instance-ipv6 + * @param string $instanceid + * @return mixed IPv6 allocation info + */ + public function list_ipv6(string $instanceid) + { + $ipv6 = self::get("instances/{$instanceid}/ipv6"); + return $ipv6['ipv6s']; + } + + /** + * List Instance IPv6 Reverse + * @see https://www.vultr.com/api/#operation/list-instance-ipv6-reverse + * @param string $instanceid + * @return mixed IPv6 allocation info + */ + public function reverse_list_ipv6(string $instanceid) + { + $ipv6 = self::get("instances/{$instanceid}/ipv6/reverse"); + return $ipv6['reverse_ipv6s']; + } + + /** + * Get Application Information + * @see https://www.vultr.com/api/#operation/get-instance + * @param string $instanceid + * @return mixed Application Information + */ + public function get_app_info(string $instanceid) + { + $app_id = self::get("instances/{$instanceid}")['instance']['app_id']; + $app_info = self::app_list()[$app_id]['name']; + return $app_info['name']; + } + + + /** + * Set Reverse DNS for IPv6 address + * @see https://www.vultr.com/api/#operation/create-instance-reverse-ipv6 + * @param string $instanceid + * @param string $ipv6 + * @param string $rdns + * @return int HTTP response code + */ + public function reverse_set_ipv6(string $instanceid, string $ipv6, string $rdns) + { + $args = array( + 'ip' => $ipv6, + 'reverse' => $rdns + ); + return self::post("instances/{$instanceid}/ipv6/reverse", $args); + } + + /** + * Delete IPv6 Reverse DNS + * @see https://www.vultr.com/api/#operation/delete-instance-reverse-ipv6 + * @param string $instanceid + * @param string $ipv6 IPv6 address + * @return int HTTP response code + * */ + public function reverse_delete_ipv6(string $instanceid, string $ipv6) + { + return self::delete("instances/{$instanceid}/ipv6/reverse/{$ipv6}"); + } + + /** + * Reboot server + * @see https://www.vultr.com/api/#operation/reboot-instance + * @param string $instanceid + * @return int HTTP response code + */ + public function reboot(string $instanceid) + { + return self::post("instances/{$instanceid}/reboot", []); + } + + /** + * Halt server + * @see https://www.vultr.com/api/#operation/halt-instances + * @param string $instanceid + * @return int HTTP response code + */ + public function halt(string $instanceid) + { + $args = array('instance_ids' => array($instanceid)); + return self::post('instances/halt', $args); + } + + /** + * Start server + * @see https://www.vultr.com/api/#operation/start-instance + * @param string $instanceid + * @return int HTTP response code + */ + public function start(string $instanceid) + { + return self::post("instances/{$instanceid}/start"); + } + + /** + * Destroy server + * @see https://www.vultr.com/api/#operation/delete-instance + * @param string $instanceid + * @return int HTTP response code + */ + public function destroy(string $instanceid) + { + return self::delete("instances/{$instanceid}"); + } + + /** + * Reinstall OS on an instance + * @see https://www.vultr.com/api/#operation/reinstall-instance + * @param string $instanceid + * @param string $hostname + * @return int HTTP response code + */ + public function reinstall(string $instanceid, $hostname='') + { + $args = array('hostname' => $hostname); + return self::post("instances/{$instanceid}/reinstall", $args); + } + + /** + * Set server label + * @see https://www.vultr.com/api/#operation/update-instance + * @param string $instanceid + * @param string $label + * @return int HTTP response code + */ + public function label_set(string $instanceid, string $label) + { + $args = array( + 'label' => $label + ); + return self::patch("instances/{$instanceid}", $args); + } + + /** + * Restore Server Snapshot + * @see https://www.vultr.com/api/#operation/restore-instance + * @param string $instanceid The Instance ID. + * @param string $snapshot_id The Snapshot id used to restore this instance. + * @return int HTTP response code + */ + public function restore_snapshot(string $instanceid, string $snapshot_id) + { + $args = array( + 'snapshot_id' => $snapshot_id + ); + return self::post("instances/{$instanceid}/restore", $args); + } + + /** + * Restore Backup + * @see https://www.vultr.com/api/#operation/restore-instance + * @param string $instanceid + * @param string $backup_id + * @return int HTTP response code + */ + public function restore_backup(string $instanceid, string $backup_id) + { + $args = array( + 'backup_id' => $backup_id + ); + return self::post("instances/{$instanceid}/restore", $args); + } + + /** + * List Backups + * @see https://www.vultr.com/api/#operation/list-backups + * @return mixed + */ + public function backup_list() + { + return self::get('backups'); + } + + /** + * List Instance specific backups + * @see https://www.vultr.com/api/#operation/list-backups + * @param $instanceid + * @return mixed + */ + public function instance_backup_list($instanceid) + { + return self::get("backups" ."?instance_id={$instanceid}"); + } + + /** + * Server Create + * @see https://www.vultr.com/api/#operation/create-instance + * @param $config + * @return bool|int|mixed|string + */ + public function create($config) + { + try { + self::server_available((int)$config['DCID'], (int)$config['VPSPLANID']); + } catch (Exception $e) { + return false; + } + + return self::post('instances', $config); + } + + /** + * SSH Keys List method + * @see https://www.vultr.com/api/#sshkey_sshkey_list + * @return FALSE if no SSH keys are available + * @return mixed with whatever ssh keys get returned + */ + public function sshkeys_list() + { + $try = self::get('ssh-keys'); + if (sizeof($try) < 1) { + return false; + } + return $try; + } + + /** + * SSH Keys Create method + * @see https://www.vultr.com/api/#sshkey_sshkey_create + * @param string $name + * @param string $key [openssh formatted public key] + * @return FALSE if no SSH keys are available + * @return mixed with whatever ssh keys get returned + */ + public function sshkey_create(string $name, string $key) + { + $args = array( + 'name' => $name, + 'ssh_key' => $key + ); + return self::post('ssh-keys', $args); + } + + /** + * SSH Keys Update method + * @see https://www.vultr.com/api/#sshkey_sshkey_update + * @param string $key_id + * @param string $name + * @param string $key [openssh formatted public key] + * @return int HTTP response code + */ + public function sshkey_update(string $key_id, string $name, string $key) + { + $args = array( + 'SSHKEYID' => $key_id, + 'name' => $name, + 'ssh_key' => $key + ); + return self::patch('sshkey/update', $args); + } + + /** + * SSH Keys Destroy method + * @see https://www.vultr.com/api/#sshkey_sshkey_destroy + * @param string $key_id + * @return int HTTP response code + */ + public function sshkey_destroy(string $key_id) + { + $args = array('SSHKEYID' => $key_id); + return self::delete('sshkey/destroy', $args); + } + + /** + * GET Method + * @param string $method + * @param mixed $args + * @return int|mixed|string + */ + public function get(string $method, $args = false) + { + $this->request_type = 'GET'; + $this->get_code = false; + return self::query($method, $args); + } + + + /** + * DELETE Method + * @param $method + * @param $args + * @return bool|int|mixed|string + */ + public function delete($method, $args=[]) + { + $this->request_type = 'DELETE'; + return self::query($method, $args); + } + + + /** + * PATCH Method + * @param $method + * @param $args + * @return bool|int|mixed|string + */ + public function patch($method, $args) + { + $this->request_type = 'PATCH'; + return self::query($method, $args); + } + + /** + * POST Method + * @param $method + * @param $args + * @return bool|int|mixed|string + */ + public function post($method, $args) + { + $this->request_type = 'POST'; + return self::query($method, $args); + } + + /** + * PUT Method + * @param $method + * @param $args + * @return bool|int|mixed|string + */ + public function put($method, $args) + { + $this->request_type = 'PUT'; + return self::query($method, $args); + } + + /** + * API Query Function + * @param string $method + * @param mixed $args + * @return int|mixed|string + */ + private function query(string $method, $args) + { + $methodArray = explode('/', $method); + $apiRequiredArray = array( + 'account', + 'auth', + 'backup', + 'backups', + 'baremetal', + 'blocks', + 'dns', + 'firewalls', + 'iso', + 'network', + 'plans', + 'reservedip', + 'reserved-ips', + 'server', + 'snapshots', + 'sshkey', + 'ssh-keys', + 'startupscript', + 'startup-scripts', + 'user', + 'users', + 'type', + 'per_page', + 'instance_id', + 'tag', + 'label', + 'main_ip', + 'cursor', + 'instances', + 'os', + 'domains', + 'load-balancers', + 'bare-metals', + 'object-storage', + 'private-networks', + + ); + + $url = $this->endpoint . $method; + $apiRequired = false; + + if ($this->debug) { + echo $this->request_type . ' ' . $url . PHP_EOL; + } + + $_defaults = array( + CURLOPT_USERAGENT => sprintf('%s v%s (%s) - WHMCS Module', $this->agent, $this->version, 'https://github.com/whattheserver/vultr-provisioning-module/'), + CURLOPT_HEADER => 0, + CURLOPT_VERBOSE => 0, + CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_HTTP_VERSION => '1.0', + CURLOPT_FOLLOWLOCATION => 0, + CURLOPT_FRESH_CONNECT => 1, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_FORBID_REUSE => 1, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTPHEADER => array('Accept: application/json') + ); + + if (in_array($methodArray[0], $apiRequiredArray)) { + array_push($_defaults[CURLOPT_HTTPHEADER], "Authorization: Bearer $this->api_token"); + $apiRequired = true; + } + + $cacheable = false; + switch ($this->request_type) { + + case 'POST': + + $post_data = json_encode($args); + $_defaults[CURLOPT_URL] = $url; + $_defaults[CURLOPT_POST] = 1; + $_defaults[CURLOPT_POSTFIELDS] = $post_data; + break; + + case 'PUT': + $post_data = json_encode($args); + $_defaults[CURLOPT_URL] = $url; + $_defaults[CURLOPT_CUSTOMREQUEST] = 'PUT'; + $_defaults[CURLOPT_POSTFIELDS] = $post_data; + break; + + case 'PATCH': + $post_data = json_encode($args); + $_defaults[CURLOPT_URL] = $url; + $_defaults[CURLOPT_CUSTOMREQUEST] = 'PATCH'; + $_defaults[CURLOPT_POSTFIELDS] = $post_data; + break; + + case 'DELETE': + //$post_data = http_build_query($args); + $_defaults[CURLOPT_URL] = $url; + $_defaults[CURLOPT_CUSTOMREQUEST] = 'DELETE'; + //$_defaults[CURLOPT_POSTFIELDS] = $post_data; + break; + + case 'GET': + if ($args !== false) { + $get_data = http_build_query($args); + $_defaults[CURLOPT_URL] = $url . '?' . $get_data; + } else { + $_defaults[CURLOPT_URL] = $url; + } + + $cacheable = true; + $response = $this->serveFromCache($_defaults[CURLOPT_URL]); + if ($response !== false) { + $this->response_code = 200; + return $response; + } + break; + + default: + break; + } + + // To avoid rate limit hits + if ($this->readLast() == time() && $apiRequired) { + usleep(333); + } + + $apisess = curl_init(); + curl_setopt_array($apisess, $_defaults); + $response = curl_exec($apisess); + $httpCode = curl_getinfo($apisess, CURLINFO_HTTP_CODE); + // logModuleCall('Vultr', $url, $args, "HTTP Code: " . $httpCode . "\n" . $response); + $this->writeLast(); + + /** + * Check to see if there were any API exceptions thrown + * If so, then error out, otherwise, keep going. + */ + try { + self::isAPIError($apisess, $response); + } catch (Exception $e) { + curl_close($apisess); + $message = $e->getMessage() . PHP_EOL; + $this->message = $message; + return $message; + } + + /** + * Close our session + * Return the decoded JSON response + */ + curl_close($apisess); + $obj = json_decode($response, true); + + if ($this->get_code) { + return (int)$this->response_code; + } + + if ($cacheable) { + $this->saveToCache($url, $response); + } else { + $this->purgeCache($url); + } + + return $obj; + } + + public function getCode(): int + { + return (int)$this->response_code; + } + + public function checkConnection(): bool + { + return $this->getCode() == 200; + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * API Error Handling + * @param cURL_Handle $response_obj + * @param string $response + * @throws Exception if invalid API location is provided + * @throws Exception if API token is missing from request + * @throws Exception if API method does not exist + * @throws Exception if Internal Server Error occurs + * @throws Exception if the request fails otherwise + */ + public function isAPIError($response_obj, string $response) + { + $code = curl_getinfo($response_obj, CURLINFO_HTTP_CODE); + $this->response_code = $code; + + if ($this->debug) { + echo $code . PHP_EOL; + } + + switch ($code) { + case 400: + throw new Exception('400: Bad Request'); + break; + case 401: + throw new Exception('401: Unauthorized'); + break; + case 403: + throw new Exception('403: Forbidden'); + break; + case 404: + throw new Exception('404: Not Found'); + break; + case 500: + throw new Exception('500: Internal Server Error'); + break; + case 503: + throw new Exception('503: Service Unavailable. Your request exceeded the API rate limit.'); + break; + case 412: + throw new Exception('Request failed: ' . $response); + break; + default: + case 200: + case 201: + case 202: + case 204: + break; + } + } + + protected function serveFromCache($url) + { + // garbage collect 5% of the time + if (mt_rand(0, 19) == 0) { + $files = glob("$this->cache_dir/*"); + $old = time() - ($this->cache_ttl * 2); + foreach ($files as $file) { + if (filemtime($file) < $old) { + unlink($old); + } + } + } + + $hash = md5($url); + $group = $this->groupFromUrl($url); + $file = "$this->cache_dir/$group-$hash"; + if (file_exists($file) && filemtime($file) > (time() - $this->cache_ttl)) { + $response = file_get_contents($file); + return json_decode($response, true); + } + return false; + } + + protected function saveToCache($url, $json) + { + if (!file_exists($this->cache_dir)) { + mkdir($this->cache_dir); + } + + $hash = md5($url); + $group = $this->groupFromUrl($url); + $file = "$this->cache_dir/$group-$hash"; + file_put_contents($file, $json); + } + + protected function groupFromUrl($url) + { + $group = 'default'; + if (preg_match('@/v1/([^/]+)/@', $url, $match)) { + return $match[1]; + } + } + + protected function purgeCache($url) + { + $group = $this->groupFromUrl($url); + $files = glob("$this->cache_dir/$group-*"); + foreach ($files as $file) { + unlink($file); + } + } + + protected function writeLast() + { + if (!file_exists($this->cache_dir)) { + mkdir($this->cache_dir); + } + + file_put_contents("$this->cache_dir/last", time()); + } + + protected function readLast() + { + if (file_exists("$this->cache_dir/last")) { + return file_get_contents("$this->cache_dir/last"); + } + } + } }