diff --git a/README.md b/README.md index c5aa4d8..84a2ba8 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,139 @@ [](https://supportukrainenow.org) - -# PHP API Client for VmWare - +​ +# PHP API Client for VMWare +​ [![Latest Version on Packagist](https://img.shields.io/packagist/v/xelon-ag/vmware-php-client.svg?style=flat-square)](https://packagist.org/packages/xelon-ag/vmware-php-client) -[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/xelon-ag/vmware-php-client/run-tests?label=tests)](https://github.com/xelon-ag/vmware-php-client/actions?query=workflow%3Arun-tests+branch%3Amain) [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/xelon-ag/vmware-php-client/Check%20&%20fix%20styling?label=code%20style)](https://github.com/xelon-ag/vmware-php-client/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) [![Total Downloads](https://img.shields.io/packagist/dt/xelon-ag/vmware-php-client.svg?style=flat-square)](https://packagist.org/packages/xelon-ag/vmware-php-client) - -This is where your description should go. Limit it to a paragraph or two. Consider adding a small example. - -## Support us - -[](https://spatie.be/github-ad-click/vmware-php-client) - -We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). - -We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). - +​ ## Installation - +​ You can install the package via composer: - +​ ```bash composer require xelon-ag/vmware-php-client ``` - +​ You can publish the config file with: - +​ ```bash php artisan vendor:publish --tag="vmware-php-client-config" ``` - -This is the contents of the published config file: - +​ ```php return [ + 'session_ttl' => env('VMWARE_SESSION_TTL', 10), + 'enable_logs' => env('VMWARE_ENABLE_LOGS', true), ]; ``` - - -## Usage - +​ +​ +## Getting started +​ +Create a connection to your hypervisor so that you can call the methods: ```php -$vmWareClient = new Xelon\VmWareClient(); -echo $vmWareClient->echoPhrase('Hello, Xelon!'); +$vcenterClient = new Xelon\VmWareClient\VcenterClient( + 'https://10.20.30.40', + 'mylogin', + 'mypassword' +); +$vmInfo = $vcenterClient->getVmInfo('vm-123'); ``` - - -## Changelog - -Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. - -## Contributing - -Please see [CONTRIBUTING](https://github.com/gazhur94/.github/blob/main/CONTRIBUTING.md) for details. - -## Security Vulnerabilities - -Please review [our security policy](../../security/policy) on how to report security vulnerabilities. - +This lib can run in three modes: `rest`, `soap` and `both`. By default, it runs in `rest` mode, but you can set another mode in constructor: +```php +$vcenterClient = new Xelon\VmWareClient\VcenterClient( + 'https://10.20.30.40', + 'mylogin', + 'mypassword', + 'soap' +); +``` +Yet we recommend to use constants: +```php +$vcenterClient = new Xelon\VmWareClient\VcenterClient( + 'https://10.20.30.40', + 'mylogin', + 'mypassword', + Xelon\VmWareClient\VcenterClient::MODE_SOAP +); +``` +​ +### `rest` mode +​ +With `rest` mode you can use REST methods which you can find in the [VMWare API developer center](https://developer.vmware.com/apis/vsphere-automation/latest/). +For now, the lib has only some methods available. You can find full list of files in the `vendor/xelon-ag/vmware-php-client/src/Traits/Rest` folder. +​ +> We plan to add the full list of methods later. +​ +### `soap` mode +​ +Using `soap` mode allow you to use SOAP methods which you can find in [VMWare SOAP developer center](https://developer.vmware.com/apis/1192/vsphere). +For now, the lib has only some methods available. You can find full list of files in the `vendor/xelon-ag/vmware-php-client/src/Traits/SOAP` folder. +​ +> We plan to add the full list of methods later. +​ + +Here's how to make your first SOAP call: +```php +$folder = $vcenterClient->soap->createFolder('group-v3', 'foldername'); +``` +​ +If you want to use both modes at one time you can set `both` mode (Xelon\VmWareClient\VcenterClient::MODE_BOTH). +​ + If you want to run custom `soap` method, which you do not find in lib, you can run this method directly: +```php +$vcenterClient = new Xelon\VmWareClient\VcenterClient( + 'https://10.20.30.40', + 'mylogin', + 'mypassword', + Xelon\VmWareClient\VcenterClient::MODE_SOAP +); +​ +$taskInfo = $vcenterClient->soap->request('ReconfigureComputeResource_Task', [ + '_this' => [ + '_' => 'domain-c33', + 'type' => 'ComputeResource', + ], + 'spec' => [ + '@type' => 'ClusterConfigSpecEx', + 'drsConfig' => [ + '@type' => 'ClusterDrsConfigInfo', + ], + 'rulesSpec' => [ + '@type' => 'ClusterRuleSpec', + 'operation' => 'add', + 'info' => [ + '@type' => 'ClusterAntiAffinityRuleSpec', + 'enabled' => true, + 'name' => 'VM-VM Affinity rule', + 'userCreated' => true, + 'vm' => [ + ['_' => 'vm-133', 'type' => 'VirtualMachine'], + ['_' => 'vm-134', 'type' => 'VirtualMachine'] + ] + ], + ], + 'dpmConfig' => [ + '@type' => 'ClusterDpmConfigInfo', + ], + + ], + 'modify' => false, +]) +``` +​ +> Order of parameters is very important. You can find the correct order in the [documentation]((https://developer.vmware.com/apis/1192/vsphere)), the `WSDL type definition` section for each object type. +​ ## Credits - +​ - [Andrii Hazhur](https://github.com/gazhur94) -- [All Contributors](../../contributors) - +- [All Contributors](https://github.com/Xelon-AG/vmware-php-client/graphs/contributors) + ​ +## Questions and feedback +​ +If you've got questions about setup or just want to chat with the developer, please feel free to reach out to a.hazhur@bitcat.agency. +​ ## License - +​ The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/config/vmware-php-client.php b/config/vmware-php-client.php index 9b22dfe..4a962ac 100644 --- a/config/vmware-php-client.php +++ b/config/vmware-php-client.php @@ -2,5 +2,6 @@ return [ // session ttl in minutes - 'session_ttl' => env('VMWARE_SESSION_TTL', 120), + 'session_ttl' => env('VMWARE_SESSION_TTL', 10), + 'enable_logs' => env('VMWARE_ENABLE_LOGS', true), ]; diff --git a/src/Data/SoapData.php b/src/Data/SoapData.php index 8fadb34..54dee9d 100644 --- a/src/Data/SoapData.php +++ b/src/Data/SoapData.php @@ -2,9 +2,31 @@ namespace Xelon\VmWareClient\Data; +use Xelon\VmWareClient\Types\CustomizationAdapterMapping; +use Xelon\VmWareClient\Types\CustomizationFixedIp; +use Xelon\VmWareClient\Types\CustomizationFixedName; +use Xelon\VmWareClient\Types\CustomizationGuiUnattended; +use Xelon\VmWareClient\Types\CustomizationIdentification; +use Xelon\VmWareClient\Types\CustomizationIPSettings; +use Xelon\VmWareClient\Types\CustomizationPassword; +use Xelon\VmWareClient\Types\CustomizationSysprep; +use Xelon\VmWareClient\Types\CustomizationUserData; +use Xelon\VmWareClient\Types\Description; +use Xelon\VmWareClient\Types\DistributedVirtualSwitchPortConnection; +use Xelon\VmWareClient\Types\SharesInfo; +use Xelon\VmWareClient\Types\VirtualCdrom; +use Xelon\VmWareClient\Types\VirtualCdromIsoBackingInfo; +use Xelon\VmWareClient\Types\VirtualCdromRemoteAtapiBackingInfo; +use Xelon\VmWareClient\Types\VirtualDeviceConnectInfo; +use Xelon\VmWareClient\Types\VirtualDisk; +use Xelon\VmWareClient\Types\VirtualDiskFlatVer2BackingInfo; +use Xelon\VmWareClient\Types\VirtualEthernetCardDistributedVirtualPortBackingInfo; +use Xelon\VmWareClient\Types\VirtualLsiLogicSASController; +use Xelon\VmWareClient\Types\VirtualVmxnet3; + class SoapData { - public function findVmBody(string $vmId, string $pathSet = ''): array + public function objectInfoBody(string $objectId, string $objectType, string $pathSet = ''): array { return [ '_this' => [ @@ -13,15 +35,16 @@ public function findVmBody(string $vmId, string $pathSet = ''): array ], 'specSet' => [ 'propSet' => [ - 'type' => 'VirtualMachine', + 'type' => $objectType, 'all' => ! $pathSet, 'pathSet' => $pathSet, ], 'objectSet' => [ 'obj' => [ - '_' => $vmId, - 'type' => 'VirtualMachine', + '_' => $objectId, + 'type' => $objectType, ], + 'skip' => false, ], ], ]; @@ -32,54 +55,62 @@ public function addVirtualDiskSpec( int $unitNumber, bool $isHdd = false, string $name = 'New Hard disk' - ): array { - return [ - '@type' => 'VirtualDisk', + ): VirtualDisk { + return new VirtualDisk([ 'key' => -101, - 'deviceInfo' => [ - '@type' => 'Description', + 'deviceInfo' => new Description([ 'label' => $name, 'summary' => $name, - ], - 'backing' => [ - '@type' => 'VirtualDiskFlatVer2BackingInfo', + ]), + 'backing' => new VirtualDiskFlatVer2BackingInfo([ 'fileName' => '', 'diskMode' => 'persistent', 'thinProvisioned' => false, 'eagerlyScrub' => false, - ], + ]), 'controllerKey' => 1000, 'unitNumber' => $unitNumber, 'capacityInKB' => $capacityInKB, 'capacityInBytes' => $capacityInKB * 1024, 'storageIOAllocation' => [ 'limit' => $isHdd ? 3200 : -1, - 'shares' => [ - '@type' => 'SharesInfo', + 'shares' => new SharesInfo([ 'shares' => 1000, 'level' => 'normal', - ], + ]), ], - ]; + ]); } - public function editVirtualDiskSpec(array $params): array + public function editVirtualDiskSpec(array $params): VirtualDisk { - return [ - '@type' => 'VirtualDisk', + return new VirtualDisk([ 'key' => $params['key'], - 'backing' => [ - '@type' => 'VirtualDiskFlatVer2BackingInfo', + 'backing' => new VirtualDiskFlatVer2BackingInfo([ 'fileName' => $params['backing']['fileName'], 'diskMode' => $params['backing']['diskMode'] ?? 'persistent', 'thinProvisioned' => $params['backing']['thinProvisioned'] ?? false, 'eagerlyScrub' => $params['backing']['eagerlyScrub'] ?? false, - ], + ]), 'controllerKey' => $params['controllerKey'], 'unitNumber' => $params['unitNumber'], 'capacityInKB' => $params['capacityInKB'], 'capacityInBytes' => $params['capacityInKB'] * 1024, - ]; + ]); + } + + public function addBlockStorageSpec(string $blockStoragePath, int $capacityInKB, int $controllerKey = 1000): VirtualDisk + { + return new VirtualDisk([ + 'key' => -1, + 'backing' => new VirtualDiskFlatVer2BackingInfo([ + 'fileName' => $blockStoragePath, + 'diskMode' => 'independent_persistent', + ]), + 'controllerKey' => $controllerKey, + 'unitNumber' => -1, + 'capacityInKB' => $capacityInKB, + ]); } public function addNetworkSpec( @@ -88,20 +119,18 @@ public function addNetworkSpec( int $unitNumber, int $controllerKey = 100, int $key = -1 - ): array { - return [ - '@type' => 'VirtualVmxnet3', + ): VirtualVmxnet3 { + return new VirtualVmxnet3([ 'key' => $key, - 'backing' => [ - '@type' => 'VirtualEthernetCardDistributedVirtualPortBackingInfo', - 'port' => [ + 'backing' => new VirtualEthernetCardDistributedVirtualPortBackingInfo([ + 'port' => new DistributedVirtualSwitchPortConnection([ 'switchUuid' => $switchUuid, 'portgroupKey' => $portgroupKey, - ], - ], + ]), + ]), 'controllerKey' => $controllerKey, 'unitNumber' => $unitNumber, - ]; + ]); } public function editNetworkSpec( @@ -109,118 +138,104 @@ public function editNetworkSpec( string $portgroupKey, int $key, ?string $macAddress = null - ): array { - $data = [ - '@type' => 'VirtualVmxnet3', + ): VirtualVmxnet3 { + return new VirtualVmxnet3([ 'key' => $key, - 'backing' => [ - '@type' => 'VirtualEthernetCardDistributedVirtualPortBackingInfo', - 'port' => [ + 'backing' => new VirtualEthernetCardDistributedVirtualPortBackingInfo([ + 'port' => new DistributedVirtualSwitchPortConnection([ 'switchUuid' => $switchUuid, 'portgroupKey' => $portgroupKey, - ], - ], + ]), + ]), 'addressType' => 'generated', 'macAddress' => $macAddress, - ]; - - if ($macAddress) { - $data['macAddress'] = $macAddress; - } - - return $data; + ]); } - public function addSasControllerSpec() + public function addSasControllerSpec(): VirtualLsiLogicSASController { - return [ - '@type' => 'VirtualLsiLogicSASController', + return new VirtualLsiLogicSASController([ + 'key' => 1000, 'busNumber' => 1, 'hotAddRemove' => true, 'sharedBus' => 'physicalSharing', - ]; + ]); } - public function mountVirtualCdRomSpec(string $fileName, int $key, int $controllerKey, string $datastore): array + public function mountVirtualCdRomSpec(string $fileName, int $key, int $controllerKey, string $datastore): VirtualCdrom { - return [ - '@type' => 'VirtualCdrom', + return new VirtualCdrom([ 'key' => $key, - 'backing' => [ - '@type' => 'VirtualCdromIsoBackingInfo', + 'backing' => new VirtualCdromIsoBackingInfo([ 'fileName' => $fileName, 'datastore' => [ 'type' => 'Datastore', '_' => $datastore, ], - ], - 'connectable' => [ + ]), + 'connectable' => new VirtualDeviceConnectInfo([ 'startConnected' => true, 'allowGuestControl' => true, 'connected' => true, - ], + ]), 'controllerKey' => $controllerKey, - ]; + ]); } - public function unmountVirtualCdRomSpec(int $key, int $controllerKey): array + public function unmountVirtualCdRomSpec(int $key, int $controllerKey): VirtualCdrom { - return [ - '@type' => 'VirtualCdrom', + return new VirtualCdrom([ 'key' => $key, - 'backing' => [ - '@type' => 'VirtualCdromRemoteAtapiBackingInfo', + 'backing' => new VirtualCdromRemoteAtapiBackingInfo([ 'deviceName' => 'CDRom', - ], - 'connectable' => [ + ]), + 'connectable' => new VirtualDeviceConnectInfo([ 'startConnected' => false, 'allowGuestControl' => false, 'connected' => false, - ], + ]), 'controllerKey' => $controllerKey, - ]; + ]); } - public function fixedIpAdapterSpec(string $ip, string $subnetMask, array $dnsServerList, array $gateway): array + public function fixedIpAdapterSpec(string $ip, string $subnetMask, array $dnsServerList, array $gateway): CustomizationAdapterMapping { - return [ - 'adapter' => [ - '@type' => 'CustomizationFixedIp', - 'ipAddress' => $ip, - ], - 'subnetMask' => $subnetMask, - 'dnsServerList' => $dnsServerList, - 'gateway' => $gateway, - - ]; + return new CustomizationAdapterMapping([ + 'adapter' => new CustomizationIPSettings([ + 'ip' => new CustomizationFixedIp([ + 'ipAddress' => $ip, + ]), + 'subnetMask' => $subnetMask, + 'dnsServerList' => $dnsServerList, + 'gateway' => $gateway, + ]), + ]); } - public function customizationIdendity(string $hostname, string $license, string $password, string $name): array + public function customizationIdendity(string $hostname, string $license, string $password, string $name): CustomizationSysprep { - return [ - 'type' => 'CustomizationSysprep', - 'guiUnattended' => [ - 'password' => [ + return new CustomizationSysprep([ + 'guiUnattended' => new CustomizationGuiUnattended([ + 'password' => new CustomizationPassword([ 'plainText' => true, 'value' => $password, - ], + ]), 'timeZone' => 110, 'autoLogon' => true, 'autoLogonCount' => 1, - ], - 'userData' => [ + ]), + 'userData' => new CustomizationUserData([ 'fullName' => $name, 'orgName' => $name, - 'computerName' => [ - '@type' => 'CustomizationFixedName', + 'computerName' => new CustomizationFixedName([ 'name' => $hostname, - ], + ]), 'productId' => $license, - ], - 'identification' => [ + ]), + 'identification' => new CustomizationIdentification([ 'joinWorkgroup' => 'workgroup', - ], - ]; + ]), + ]); } } diff --git a/src/Requests/ApiRequest.php b/src/Requests/ApiRequest.php index 3582dfd..d847629 100644 --- a/src/Requests/ApiRequest.php +++ b/src/Requests/ApiRequest.php @@ -13,7 +13,13 @@ trait ApiRequest private function request(string $method, string $uri, array $options = []) { try { - return json_decode($this->guzzleClient->request($method, $uri, $options)->getBody()); + $result = json_decode($this->guzzleClient->request($method, $uri, $options)->getBody()); + + if ($this->version < 7 && isset($result->value)) { + return $result->value; + } + + return $result; } catch (ConnectException $e) { Log::error('Rest api Connect exception: '.$e->getMessage()); } catch (ServerException $e) { @@ -24,7 +30,7 @@ private function request(string $method, string $uri, array $options = []) return [ 'isError' => true, 'code' => $e->getCode(), - 'info' => json_decode($e->getResponse()->getBody()->getContents()), + 'info' => $this->transformErrorInfo(json_decode($e->getResponse()->getBody()->getContents(), true)), ]; } catch (ClientException $e) { // if 401, create new session and reply attempt @@ -32,4 +38,19 @@ private function request(string $method, string $uri, array $options = []) Log::error('Rest api exception : '.$e->getMessage()); } } + + private function transformErrorInfo(array $info) + { + if ($this->version < 7) { + if (isset($info['value']['messages'])) { + $info['messages'] = $info['value']['messages']; + } elseif (isset($info['localizableMessages'])) { + $info['messages'] = $info['localizableMessages']; + } + } elseif (count($info['messages']) === 0) { + $info['messages'][0]['default_message'] = $info['error_type']; + } + + return $info; + } } diff --git a/src/Requests/SoapRequest.php b/src/Requests/SoapRequest.php index db5fa9f..5a7ba8d 100644 --- a/src/Requests/SoapRequest.php +++ b/src/Requests/SoapRequest.php @@ -2,8 +2,61 @@ namespace Xelon\VmWareClient\Requests; +use Illuminate\Support\Facades\Log; +use stdClass; +use Xelon\VmWareClient\Transform\SoapTransform; + trait SoapRequest { + use SoapTransform; + + /** + * @param string $method + * @param array $requestBody + * @param bool $convertToSoap + * @return stdClass + */ + public function request(string $method, array $requestBody, bool $convertToSoap = true) + { + try { + $response = $this->soapClient->$method($convertToSoap ? $this->arrayToSoapVar($requestBody) : $requestBody); + + if (config('vmware-php-client.enable_logs')) { + Log::info( + 'SOAP REQUEST SUCCESS:'. + "\nSOAP method: ".$method. + property_exists($this->soapClient, '__last_request') + ? "\nSOAP request start***".$this->soapClient->__last_request.'***SOAP request end' + : '' + ); + } + + return $response; + } catch (\Exception $exception) { + $message = "SOAP REQUEST FAILED:\nMessage: ".$exception->getMessage(). + "\nSOAP method: ".$method. + ( + property_exists($this->soapClient, '__last_request') + ? "\nSOAP request start***".$this->soapClient->__last_request.'***SOAP request end' + : '' + ).( + property_exists($this->soapClient, '__last_request') + ? "\nSOAP response start***: ".$this->soapClient->__last_response.'***SOAP response end' + : '' + ). + "\nTrace: ".json_encode($exception->getTrace()); + + Log::error($message); + throw new \Exception($message); + } + } + + /** + * @param string $method + * @param string $vmId + * @param array $requestBody + * @return stdClass + */ private function vmRequest(string $method, string $vmId, array $requestBody = []) { $soapMessage = [ @@ -14,6 +67,6 @@ private function vmRequest(string $method, string $vmId, array $requestBody = [] ]; $soapMessage = array_merge($soapMessage, $requestBody); - return $this->soapClient->$method($soapMessage); + return $this->request($method, $soapMessage); } } diff --git a/src/Traits/Rest/CisApis.php b/src/Traits/Rest/CisApis.php new file mode 100644 index 0000000..abd315b --- /dev/null +++ b/src/Traits/Rest/CisApis.php @@ -0,0 +1,64 @@ +request( + 'post', + $this->version >= 7 + ? '/api/cis/tagging/tag-association?action=list-attached-tags' + : '/rest/com/vmware/cis/tagging/tag-association?~action=list-attached-tags', + [ + 'json' => [ + 'object_id' => [ + 'type' => 'VirtualMachine', + 'id' => $vmId, + ], + ], + ] + ); + } + + public function atachTagAssociation(string $vmId, string $tagId) + { + return $this->request( + 'post', + $this->version >= 7 + ? "/api/cis/tagging/tag-association/$tagId?action=attach" + : "/rest/com/vmware/cis/tagging/tag-association/id:$tagId?~action=attach", + [ + 'json' => [ + 'object_id' => [ + 'type' => 'VirtualMachine', + 'id' => $vmId, + ], + ], + ] + ); + } + + public function detachTagAssociation(string $vmId, string $tagId) + { + return $this->request( + 'post', + $this->version >= 7 + ? "/api/cis/tagging/tag-association/$tagId?action=detach" + : "/rest/com/vmware/cis/tagging/tag-association/id:$tagId?~action=detach", + [ + 'json' => [ + 'object_id' => [ + 'type' => 'VirtualMachine', + 'id' => $vmId, + ], + ], + ] + ); + } +} diff --git a/src/Traits/Rest/IsoApis.php b/src/Traits/Rest/IsoApis.php index f1262de..3c036e2 100644 --- a/src/Traits/Rest/IsoApis.php +++ b/src/Traits/Rest/IsoApis.php @@ -10,17 +10,27 @@ trait IsoApis public function mountImage(string $vmId, string $libraryItem) { - return $this->request('post', '/api/vcenter/iso/image?action=mount', ['form_params' => [ - 'library_item' => $libraryItem, - 'vm' => $vmId, - ]]); + if ($this->version >= 7) { + $url = '/api/vcenter/iso/image?action=mount'; + $params = ['library_item' => $libraryItem, 'vm' => $vmId]; + } else { + $url = "/rest/com/vmware/vcenter/iso/image/id:$libraryItem?~action=mount"; + $params = ['vm' => $vmId]; + } + + return $this->request('post', $url, ['form_params' => $params]); } public function unmountImage(string $vmId, string $cdRom) { - return $this->request('post', '/api/vcenter/iso/image?action=unmount', ['form_params' => [ - 'cdrom' => $cdRom, - 'vm' => $vmId, - ]]); + if ($this->version >= 7) { + $url = '/api/vcenter/iso/image?action=unmount'; + $params = ['cdrom' => $cdRom, 'vm' => $vmId]; + } else { + $url = "/rest/com/vmware/vcenter/iso/image/id:$vmId?~action=unmount"; + $params = ['cdrom' => $cdRom]; + } + + return $this->request('post', $url, ['form_params' => $params]); } } diff --git a/src/Traits/Rest/OfvApis.php b/src/Traits/Rest/OfvApis.php new file mode 100644 index 0000000..e3ded32 --- /dev/null +++ b/src/Traits/Rest/OfvApis.php @@ -0,0 +1,66 @@ + $data['network_port_group'], + 'key' => 'VM Network', + ]; + + if ($this->version < 7) { + $networkMappings = [$networkMappings]; + } + + $body = [ + 'deployment_spec' => [ + 'name' => $data['name'], + 'accept_all_EULA' => false, + 'default_datastore_id' => $data['default_datastore_id'], + 'storage_provisioning' => 'thin', + 'additional_parameters' => [ + [ + '@class' => 'com.vmware.vcenter.ovf.property_params', + 'type' => 'PropertyParams', + 'properties' => [ + [ + 'instance_id' => '', + 'class_id' => '', + 'description' => 'In order to fit into a xml attribute, this value is base64 encoded . It will be decoded, and then processed normally as user-data.', + 'id' => 'user-data', + 'label' => 'Encoded user-data', + 'category' => '', + 'type' => 'string', + 'value' => $data['user_data'], + 'ui_optional' => false, + ], + ], + ], + ], + 'network_mappings' => $networkMappings, + ], + 'target' => [ + 'resource_pool_id' => $data['resource_pool_id'], + ], + ]; + + if (isset($data['folder_id'])) { + $body['target']['folder_id'] = $data['folder_id']; + } + + return $this->request( + 'post', + $this->version >= 7 + ? "/api/vcenter/ovf/library-item/$ovfLibraryItemId?action=deploy" + : "/rest/com/vmware/vcenter/ovf/library-item/id:$ovfLibraryItemId?~action=deploy", + ['json' => $body] + ); + } +} diff --git a/src/Traits/Rest/VcenterApis.php b/src/Traits/Rest/VcenterApis.php index 1af6b2f..3c67d91 100644 --- a/src/Traits/Rest/VcenterApis.php +++ b/src/Traits/Rest/VcenterApis.php @@ -3,34 +3,50 @@ namespace Xelon\VmWareClient\Traits\Rest; use Xelon\VmWareClient\Requests\ApiRequest; +use Xelon\VmWareClient\Transform\SoapTransform; trait VcenterApis { use ApiRequest; + use SoapTransform; public function createVm() { // TODO: } - public function getVmList() + public function getVmList(array $requestBody = []) { - return $this->request('get', '/api/vcenter/vm'); + return $this->request( + 'get', + "$this->apiUrlPrefix/vcenter/vm", + ['query' => $this->getListFilterQuery($requestBody)] + ); } public function getVmInfo(string $vmId) { - return $this->request('get', "/api/vcenter/vm/$vmId"); + $result = $this->request('get', "$this->apiUrlPrefix/vcenter/vm/$vmId"); + + if ($this->version < 7) { + foreach ($result->disks as $disk) { + foreach ($disk->value as $key => $property) { + $disk->$key = $property; + } + } + } + + return $result; } - public function deleteVm() + public function deleteVm(string $vmId) { - // TODO: + return $this->request('delete', "$this->apiUrlPrefix/vcenter/vm/$vmId"); } public function cloneVm(array $requestBody) { - return $this->request('post', '/api/vcenter/vm?action=clone', ['body' => $requestBody]); + return $this->request('post', "$this->apiUrlPrefix/vcenter/vm?action=clone", ['json' => $requestBody]); } public function registerVm() @@ -52,4 +68,13 @@ public function unregisterVm() { // TODO: } + + public function getNetworkList(array $requestBody = []) + { + return $this->request( + 'get', + "$this->apiUrlPrefix/vcenter/network", + ['query' => $this->getListFilterQuery($requestBody)] + ); + } } diff --git a/src/Traits/Rest/VmApis.php b/src/Traits/Rest/VmApis.php index e4f703d..b1b3884 100644 --- a/src/Traits/Rest/VmApis.php +++ b/src/Traits/Rest/VmApis.php @@ -35,22 +35,36 @@ public function getPower() public function resetPower(string $vmId) { - return $this->request('post', "/api/vcenter/vm/$vmId/power?action=reset"); + return $this->request( + 'post', + $this->version >= 7 ? "/api/vcenter/vm/$vmId/power?action=reset" : "/rest/vcenter/vm/$vmId/power/reset" + ); } public function startPower(string $vmId) { - return $this->request('post', "/api/vcenter/vm/$vmId/power?action=start"); + return $this->request( + 'post', + $this->version >= 7 ? "/api/vcenter/vm/$vmId/power?action=start" : "/rest/vcenter/vm/$vmId/power/start" + ); } public function stopPower(string $vmId) { - return $this->request('post', "/api/vcenter/vm/$vmId/power?action=stop"); + $a = 5; + + return $this->request( + 'post', + $this->version >= 7 ? "/api/vcenter/vm/$vmId/power?action=stop" : "/rest/vcenter/vm/$vmId/power/stop" + ); } public function suspendPower(string $vmId) { - return $this->request('post', "/api/vcenter/vm/$vmId/power?action=suspend"); + return $this->request( + 'post', + $this->version >= 7 ? "/api/vcenter/vm/$vmId/power?action=suspend" : "/rest/vcenter/vm/$vmId/power/suspend" + ); } public function getTools() @@ -275,14 +289,21 @@ public function updateHardwareCpu( bool $hotAddEnabled = false, bool $hotRemoveEnabled = false ) { - return $this->request('patch', "/api/vcenter/vm/$vmId/hardware/cpu", [ - 'json' => [ - 'cores_per_socket' => $coresPerSocket, - 'count' => $count, - 'hot_add_enabled' => $hotAddEnabled, - 'hot_remove_enabled' => $hotRemoveEnabled, - ], - ]); + $requestBody = [ + 'cores_per_socket' => $coresPerSocket, + 'count' => $count, + 'hot_add_enabled' => $hotAddEnabled, + 'hot_remove_enabled' => $hotRemoveEnabled, + ]; + + if ($this->version < 7) { + $requestBody = ['spec' => $requestBody]; + } + + return $this->request( + 'patch', + "$this->apiUrlPrefix/vcenter/vm/$vmId/hardware/cpu", + ['json' => $requestBody]); } public function listHardwareDisk() @@ -292,7 +313,7 @@ public function listHardwareDisk() public function createHardwareDisk(string $vmId, array $body) { - return $this->request('post', "/api/vcenter/vm/$vmId/hardware/disk", ['json' => $body]); + return $this->request('post', "$this->apiUrlPrefix/vcenter/vm/$vmId/hardware/disk", ['json' => $body]); } public function getHardwareDisk() @@ -307,7 +328,7 @@ public function updateHardwareDisk() public function deleteHardwareDisk(string $vmId, int $diskKey) { - return $this->request('delete', "/api/vcenter/vm/$vmId/hardware/disk/$diskKey"); + return $this->request('delete', "$this->apiUrlPrefix/vcenter/vm/$vmId/hardware/disk/$diskKey"); } public function listHardwareEthernet() @@ -332,7 +353,7 @@ public function updateHardwareEthernet() public function deleteHardwareEthernet(string $vmId, int $nicKey) { - return $this->request('delete', "/api/vcenter/vm/$vmId/hardware/ethernet/$nicKey"); + return $this->request('delete', "$this->apiUrlPrefix/vcenter/vm/$vmId/hardware/ethernet/$nicKey"); } public function connectHardwareEthernet() @@ -387,12 +408,17 @@ public function getHardwareMemory() public function updateHardwareMemory(string $vmId, int $size, bool $hotAddEnabled = false) { - return $this->request('patch', "/api/vcenter/vm/$vmId/hardware/memory", [ - 'json' => [ - 'hot_add_enabled' => $hotAddEnabled, - 'size_MiB' => $size, - ], - ]); + $requestBody = ['hot_add_enabled' => $hotAddEnabled, 'size_MiB' => $size]; + + if ($this->version < 7) { + $requestBody = ['spec' => $requestBody]; + } + + return $this->request( + 'patch', + "$this->apiUrlPrefix/vcenter/vm/$vmId/hardware/memory", + ['json' => $requestBody] + ); } public function listHardwareParallel() diff --git a/src/Traits/Soap/SoapFileApis.php b/src/Traits/Soap/SoapFileApis.php deleted file mode 100644 index c55aca1..0000000 --- a/src/Traits/Soap/SoapFileApis.php +++ /dev/null @@ -1,159 +0,0 @@ - 0) { - foreach ($params as $key => $value) { - $script = str_replace('{'.$key.'}', $value, $script); - } - } - - $body = [ - '_this' => [ - 'type' => 'GuestFileManager', - '_' => 'guestOperationsFileManager', - ], - 'vm' => [ - 'type' => 'VirtualMachine', - '_' => $vmId, - ], - 'auth' => [ - '@type' => 'NamePasswordAuthentication', - 'interactiveSession' => true, - 'username' => $username, - 'password' => $password, - ], - 'guestFilePath' => "{$guestFilePath}", - 'fileAttributes' => new \stdClass(), - 'fileSize' => strlen($script), - 'overwrite' => true, - ]; - - $response = $this->soapClient->InitiateFileTransferToGuest($this->arrayToSoapVar($body)); - - $client = new GuzzleClient(['verify' => false]); - - try { - if (is_string($response->returnval) && substr($response->returnval, 0, 4) !== 'http') { - throw new Exception('File transfer invalid response url'); - } - $client->request('PUT', $response->returnval, ['body' => $script]); - } catch (Exception $e) { - throw new Exception($e->getMessage()); - } - } - - public function createTemporaryDirectoryInGuest( - string $username, - string $password, - string $vmId, - string $directoryPath, - string $prefix = '', - string $sufix = '' - ) { - $body = [ - '_this' => [ - 'type' => 'GuestFileManager', - '_' => 'guestOperationsFileManager', - ], - 'vm' => [ - 'type' => 'VirtualMachine', - '_' => $vmId, - ], - 'auth' => [ - '@type' => 'NamePasswordAuthentication', - 'interactiveSession' => false, - 'username' => $username, - 'password' => $password, - ], - 'prefix' => $prefix, - 'suffix' => $sufix, - 'directoryPath' => $directoryPath, - ]; - - return $this->soapClient->CreateTemporaryDirectoryInGuest($this->arrayToSoapVar($body)); - } - - public function deleteDirectoryInGuest( - string $username, - string $password, - string $vmId, - string $directoryPath - ) { - $body = [ - '_this' => [ - 'type' => 'GuestFileManager', - '_' => 'guestOperationsFileManager', - ], - 'vm' => [ - 'type' => 'VirtualMachine', - '_' => $vmId, - ], - 'auth' => [ - '@type' => 'NamePasswordAuthentication', - 'interactiveSession' => false, - 'username' => $username, - 'password' => $password, - ], - 'directoryPath' => $directoryPath, - 'recursive' => true, - ]; - - return $this->soapClient->DeleteDirectoryInGuest($this->arrayToSoapVar($body)); - } - - public function startProgramInGuest( - string $username, - string $password, - string $vmId, - string $filePath, - string $program = '/bin/bash' - ) { - $body = [ - '_this' => [ - 'type' => 'GuestProcessManager', - '_' => 'guestOperationsProcessManager', - ], - 'vm' => [ - 'type' => 'VirtualMachine', - '_' => $vmId, - ], - 'auth' => [ - '@type' => 'NamePasswordAuthentication', - 'interactiveSession' => false, - 'username' => $username, - 'password' => $password, - ], - 'spec' => [ - 'programPath' => $program, - 'arguments' => "{$filePath}", - ], - ]; - - return $this->soapClient->StartProgramInGuest($this->arrayToSoapVar($body)); - } -} diff --git a/src/Traits/Soap/SoapGuestApis.php b/src/Traits/Soap/SoapGuestApis.php new file mode 100644 index 0000000..7443bf1 --- /dev/null +++ b/src/Traits/Soap/SoapGuestApis.php @@ -0,0 +1,347 @@ + 0) { + foreach ($params as $key => $value) { + $data = str_replace('{'.$key.'}', $value, $data); + } + } + + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'guestFilePath' => $guestFilePath, + 'fileAttributes' => new \stdClass(), + 'fileSize' => strlen($data), + 'overwrite' => true, + ]; + + $response = $this->request('InitiateFileTransferToGuest', $body); + + $client = new GuzzleClient(['verify' => false]); + + try { + if (! isset($response->returnval) || ! is_string($response->returnval) || substr($response->returnval, 0, 4) !== 'http') { + throw new Exception('File transfer invalid response url'); + } + $client->request('PUT', $response->returnval, ['body' => $data]); + } catch (Exception $e) { + throw new Exception("{$e->getMessage()}. Response: ".json_encode($response)); + } + } + + public function createTemporaryDirectoryInGuest( + string $username, + string $password, + string $vmId, + string $directoryPath, + string $prefix = '', + string $sufix = '' + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'prefix' => $prefix, + 'suffix' => $sufix, + 'directoryPath' => $directoryPath, + ]; + + return $this->request('CreateTemporaryDirectoryInGuest', $body); + } + + public function makeDirectoryInGuest( + string $username, + string $password, + string $vmId, + string $directoryPath, + bool $createParentDirectories = false + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'directoryPath' => $directoryPath, + 'createParentDirectories' => $createParentDirectories, + ]; + + return $this->request('MakeDirectoryInGuest', $body); + } + + public function listFilesInGuest( + string $username, + string $password, + string $vmId, + string $filePath, + int $index = 0, + int $maxResults = 50, + ?string $matchPattern = null + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'filePath' => $filePath, + 'index' => $index, + 'maxResults' => $maxResults, + 'matchPattern' => $matchPattern, + ]; + + return $this->request('ListFilesInGuest', $body); + } + + public function deleteDirectoryInGuest( + string $username, + string $password, + string $vmId, + string $directoryPath + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'directoryPath' => $directoryPath, + 'recursive' => true, + ]; + + return $this->request('DeleteDirectoryInGuest', $body); + } + + public function deleteFileInGuest( + string $username, + string $password, + string $vmId, + string $filePath + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'filePath' => $filePath, + ]; + + return $this->request('DeleteFileInGuest', $body); + } + + public function startProgramInGuest( + string $username, + string $password, + string $vmId, + string $filePath, + string $program = '/bin/bash' + ) { + $body = [ + '_this' => [ + 'type' => 'GuestProcessManager', + '_' => 'guestOperationsProcessManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'spec' => [ + 'programPath' => $program, + 'arguments' => $filePath, + ], + ]; + + return $this->request('StartProgramInGuest', $body); + } + + public function createTaskCollectorForVm(string $vmId) + { + $body = [ + '_this' => [ + '_' => 'TaskManager', + 'type' => 'TaskManager', + ], + 'filter' => [ + 'entity' => [ + 'entity' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'recursion' => 'self', + ], + ], + ]; + + return $this->request('CreateCollectorForTasks', $body); + } + + public function destroyTaskCollector(string $taskCollectorId) + { + $body = [ + '_this' => [ + 'type' => 'HistoryCollector', + '_' => $taskCollectorId, + ], + ]; + + return $this->request('DestroyCollector', $body); + } + + public function getListFilesInGuest( + string $username, + string $password, + string $vmId, + string $filePath + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + 'filePath' => $filePath, + ]; + + return $this->request('ListFilesInGuest', $body); + } + + public function getListProcessInGuest( + string $username, + string $password, + string $vmId + ) { + $body = [ + '_this' => [ + 'type' => 'GuestFileManager', + '_' => 'guestOperationsFileManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + '@type' => 'NamePasswordAuthentication', + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + ]; + + return $this->request('ListProcessesInGuest', $body); + } + + public function validateCredentialsInGuest(string $username, string $password, string $vmId) + { + $body = [ + '_this' => [ + 'type' => 'GuestAuthManager', + '_' => 'guestOperationsAuthManager', + ], + 'vm' => [ + 'type' => 'VirtualMachine', + '_' => $vmId, + ], + 'auth' => new NamePasswordAuthentication([ + 'interactiveSession' => false, + 'username' => $username, + 'password' => $password, + ]), + ]; + + return $this->request('ValidateCredentialsInGuest', $body); + } +} diff --git a/src/Traits/Soap/SoapStorageApis.php b/src/Traits/Soap/SoapStorageApis.php new file mode 100644 index 0000000..0e91a57 --- /dev/null +++ b/src/Traits/Soap/SoapStorageApis.php @@ -0,0 +1,94 @@ + [ + 'id' => $vstorageId, + ], + '_this' => [ + 'type' => 'VcenterVStorageObjectManager', + '_' => 'VStorageObjectManager', + ], + 'datastore' => [ + 'type' => 'Datastore', + '_' => $datastore, + ], + ]; + + return $this->transformToArrayValues($this->request('RetrieveVStorageObject', $body)); + } + + public function deleteVcenterVStorageInfo(string $vstorageId, string $datastore) + { + $body = [ + 'id' => [ + 'id' => $vstorageId, + ], + '_this' => [ + 'type' => 'VcenterVStorageObjectManager', + '_' => 'VStorageObjectManager', + ], + 'datastore' => [ + 'type' => 'Datastore', + '_' => $datastore, + ], + ]; + + return $this->request('DeleteVStorageObject_Task', $body); + } + + public function createVStorage(string $name, int $capacityInMB, string $datastore, bool $keepAfterDeleteVm = true) + { + $body = [ + '_this' => [ + 'type' => 'VcenterVStorageObjectManager', + '_' => 'VStorageObjectManager', + ], + 'spec' => [ + 'name' => $name, + 'keepAfterDeleteVm' => $keepAfterDeleteVm, + 'backingSpec' => new VslmCreateSpecDiskFileBackingSpec([ + 'datastore' => [ + 'type' => 'Datastore', + '_' => $datastore, + ], + ]), + 'capacityInMB' => $capacityInMB, + ], + ]; + + return $this->request('CreateDisk_Task', $body); + } + + public function extendVStorage(string $vstorageId, string $datastore, int $newCapacityInMB) + { + $body = [ + '_this' => [ + 'type' => 'VcenterVStorageObjectManager', + '_' => 'VStorageObjectManager', + ], + 'id' => [ + 'id' => $vstorageId, + ], + 'datastore' => [ + 'type' => 'Datastore', + '_' => $datastore, + ], + 'newCapacityInMB' => $newCapacityInMB, + ]; + + return $this->request('ExtendDisk_Task', $body); + } +} diff --git a/src/Traits/Soap/SoapVmApis.php b/src/Traits/Soap/SoapVmApis.php index 4d098c0..f8bdffa 100644 --- a/src/Traits/Soap/SoapVmApis.php +++ b/src/Traits/Soap/SoapVmApis.php @@ -2,70 +2,99 @@ namespace Xelon\VmWareClient\Traits\Soap; +use Illuminate\Support\Facades\Log; use Xelon\VmWareClient\Requests\SoapRequest; use Xelon\VmWareClient\Transform\SoapTransform; +use Xelon\VmWareClient\Types\ClusterAntiAffinityRuleSpec; +use Xelon\VmWareClient\Types\ClusterConfigSpecEx; +use Xelon\VmWareClient\Types\ClusterDpmConfigInfo; +use Xelon\VmWareClient\Types\ClusterDrsConfigInfo; +use Xelon\VmWareClient\Types\ClusterRuleSpec; +use Xelon\VmWareClient\Types\DVPortgroupConfigSpec; +use Xelon\VmWareClient\Types\DVPortSetting; +use Xelon\VmWareClient\Types\DVSTrafficShapingPolicy; +use Xelon\VmWareClient\Types\VirtualDeviceConfigSpec; +use Xelon\VmWareClient\Types\VirtualMachineBootOptionsBootableCdromDevice; +use Xelon\VmWareClient\Types\VirtualMachineConfigSpec; trait SoapVmApis { use SoapRequest; use SoapTransform; - public function getVmInfo(string $vmId, string $pathSet = '') + public function getObjectInfo(string $objectId, string $objectType, string $pathSet = '') { - $body = [ - '_this' => [ - '_' => 'propertyCollector', - 'type' => 'PropertyCollector', - ], - 'specSet' => [ - 'propSet' => [ - 'type' => 'VirtualMachine', - 'all' => ! $pathSet, - 'pathSet' => $pathSet, - ], - 'objectSet' => [ - 'obj' => [ - '_' => $vmId, - 'type' => 'VirtualMachine', - ], - ], - ], - ]; + $body = $this->data->objectInfoBody($objectId, $objectType, $pathSet); - $result = $this->soapClient->RetrieveProperties($body); + try { + $result = $this->soapClient->RetrieveProperties($body); + } catch (\Exception $exception) { + Log::error( + "SOAP REQUEST FAILED:\nMessage: ".$exception->getMessage(). + "\nSOAP request: ".$this->soapClient->__last_request. + "\nSOAP response: ".$this->soapClient->__last_response + ); + + if (array_keys(json_decode(json_encode($exception->detail), true))[0] === 'ManagedObjectNotFoundFault') { + Log::error("404 error, type: $objectType, object id: $objectId"); + + return new \stdClass(); + } + } return $pathSet ? ($result->returnval->propSet->val ?? null) : $this->transformPropSet($result->returnval->propSet); } + public function getVmInfo(string $vmId, string $pathSet = '') + { + if (substr($vmId, 0, 2) !== 'vm') { + Log::error("Wrong vm id format: $vmId"); + + return new \stdClass(); + } + + return $this->getObjectInfo($vmId, 'VirtualMachine', $pathSet); + } + public function getTaskInfo(string $taskId, string $pathSet = '') { - $body = [ - '_this' => [ - '_' => 'propertyCollector', - 'type' => 'PropertyCollector', - ], - 'specSet' => [ - 'propSet' => [ - 'type' => 'Task', - 'all' => ! $pathSet, - 'pathSet' => $pathSet, - ], - 'objectSet' => [ - 'obj' => [ - '_' => $taskId, - 'type' => 'Task', - ], - ], - ], - ]; + return $this->getObjectInfo($taskId, 'Task', $pathSet); + } + + public function getClusterComputeResourceInfo(string $clusterComputeResourceId, string $pathSet = '') + { + return $this->getObjectInfo($clusterComputeResourceId, 'ClusterComputeResource', $pathSet); + } - $result = $this->soapClient->RetrieveProperties($body); + public function getDatastoreInfo(string $datastore, string $pathSet = '') + { + return $this->getObjectInfo($datastore, 'Datastore', $pathSet); + } - return $pathSet - ? ($result->returnval->propSet->val ?? null) - : $this->transformPropSet($result->returnval->propSet); + public function getSnapshotInfo(string $taskId, string $pathSet = '') + { + return $this->getObjectInfo($taskId, 'VirtualMachineSnapshot', $pathSet); + } + + public function getTaskHistoryCollectorInfo(string $taskHistoryCollectorId) + { + return $this->getObjectInfo($taskHistoryCollectorId, 'TaskHistoryCollector'); + } + + public function getResourcePoolInfo(?string $resourcePoolId, string $pathSet = '') + { + if (! $resourcePoolId) { + return []; + } + + return $this->getObjectInfo($resourcePoolId, 'ResourcePool', $pathSet); + } + + public function getDistributedVirtualPortgroupInfo(string $distributedVirtualPortgroupId, string $pathSet = '') + { + return $this->getObjectInfo($distributedVirtualPortgroupId, 'DistributedVirtualPortgroup', $pathSet); } public function reconfigVmTask(string $vmId, array $requestBody) @@ -93,25 +122,27 @@ public function cloneVmTask(string $vmId, array $params) ], ], 'template' => $params['spec']['template'] ?? false, - 'config' => [ - 'numCPUs' => $params['spec']['config']['numCPUs'], - 'numCoresPerSocket' => $params['spec']['config']['numCoresPerSocket'], - 'memoryMB' => $params['spec']['config']['memoryMB'], - 'deviceChange' => $params['spec']['config']['deviceChange'] ?? null, - ], + 'config' => isset($params['spec']['config']) + ? [ + 'numCPUs' => $params['spec']['config']['numCPUs'], + 'numCoresPerSocket' => $params['spec']['config']['numCoresPerSocket'], + 'memoryMB' => $params['spec']['config']['memoryMB'], + 'deviceChange' => $params['spec']['config']['deviceChange'] ?? null, + ] + : null, 'customization' => $params['spec']['customization'] ?? null, 'powerOn' => $params['spec']['powerOn'] ?? true, /*'bootOptions' => [ 'bootDelay' => $params['spec']['bootOptions']['bootDelay'] ?? 0, 'bootRetryEnabled' => $params['spec']['bootOptions']['bootRetryEnabled'] ?? true, 'bootOrder' => [ - '@type' => 'VirtualMachineBootOptionsBootableCdromDevice' + '@type' => new VirtualMachineBootOptionsBootableCdromDevice() ] ],*/ ], ]; - return $this->vmRequest('CloneVM_Task', $vmId, $this->arrayToSoapVar($body)); + return $this->vmRequest('CloneVM_Task', $vmId, $body); } public function addDisk( @@ -122,15 +153,13 @@ public function addDisk( string $name = 'New Hard disk' ) { $body = [ - 'spec' => [ - '@type' => 'VirtualMachineConfigSpec', - 'deviceChange' => [ - '@type' => 'VirtualDeviceConfigSpec', + 'spec' => new VirtualMachineConfigSpec([ + 'deviceChange' => new VirtualDeviceConfigSpec([ 'operation' => 'add', 'fileOperation' => 'create', 'device' => $this->data->addVirtualDiskSpec($capacityInKB, $unitNumber, $isHdd, $name), - ], - ], + ]), + ]), ]; return $this->reconfigVmTask($vmId, $body); @@ -139,30 +168,40 @@ public function addDisk( public function editDisk(string $vmId, array $params) { $body = [ - 'spec' => [ - '@type' => 'VirtualMachineConfigSpec', - 'deviceChange' => [ - '@type' => 'VirtualDeviceConfigSpec', + 'spec' => new VirtualMachineConfigSpec([ + 'deviceChange' => new VirtualDeviceConfigSpec([ 'operation' => 'edit', 'device' => $this->data->editVirtualDiskSpec($params), - ], - ], + ]), + ]), ]; return $this->reconfigVmTask($vmId, $body); } + public function addPersistantDisk(string $vmId, string $blockStoragePath, int $capacityInKB, int $controllerKey = 1000) + { + $body = [ + 'spec' => new VirtualMachineConfigSpec([ + 'deviceChange' => new VirtualDeviceConfigSpec([ + 'operation' => 'add', + 'device' => $this->data->addBlockStorageSpec($blockStoragePath, $capacityInKB, $controllerKey), + ]), + ]), + ]; + + return $this->reconfigVmTask($vmId, $this->arrayToSoapVar($body)); + } + public function addNetwork(string $vmId, int $unitNumber, string $portgroupKey, string $switchUuid) { $body = [ - 'spec' => [ - '@type' => 'VirtualMachineConfigSpec', - 'deviceChange' => [ - '@type' => 'VirtualDeviceConfigSpec', + 'spec' => new VirtualMachineConfigSpec([ + 'deviceChange' => new VirtualDeviceConfigSpec([ 'operation' => 'add', 'device' => $this->data->addNetworkSpec($switchUuid, $portgroupKey, $unitNumber), - ], - ], + ]), + ]), ]; return $this->reconfigVmTask($vmId, $body); @@ -176,14 +215,12 @@ public function editNetwork( int $key ) { $body = [ - 'spec' => [ - '@type' => 'VirtualMachineConfigSpec', - 'deviceChange' => [ - '@type' => 'VirtualDeviceConfigSpec', + 'spec' => new VirtualMachineConfigSpec([ + 'deviceChange' => new VirtualDeviceConfigSpec([ 'operation' => 'edit', 'device' => $this->data->editNetworkSpec($switchUuid, $portgroupKey, $key, $macAddress), - ], - ], + ]), + ]), ]; return $this->reconfigVmTask($vmId, $body); @@ -192,19 +229,113 @@ public function editNetwork( public function addSasController(string $vmId) { $body = [ - 'spec' => [ - '@type' => 'VirtualMachineConfigSpec', - 'deviceChange' => [ - '@type' => 'VirtualLsiLogicSASController', + 'spec' => new VirtualMachineConfigSpec([ + 'deviceChange' => new VirtualDeviceConfigSpec([ 'operation' => 'add', 'device' => $this->data->addSasControllerSpec(), - ], - ], + ]), + ]), ]; return $this->reconfigVmTask($vmId, $body); } + public function changeDVPortgroupSpeed( + string $distributedVirtualPortgroupId, + string $configVersion, + int $speed + ) { + $body = [ + '_this' => [ + '_' => $distributedVirtualPortgroupId, + 'type' => 'DistributedVirtualPortgroup', + ], + 'spec' => new DVPortgroupConfigSpec([ + 'configVersion' => $configVersion, + 'defaultPortConfig' => new DVPortSetting([ + 'inShapingPolicy' => new DVSTrafficShapingPolicy([ + 'inherited' => false, + 'enabled' => [ + 'inherited' => false, + 'value' => true, + ], + 'averageBandwidth' => [ + 'inherited' => false, + 'value' => $speed, + ], + 'peakBandwidth' => [ + 'inherited' => false, + 'value' => $speed, + ], + 'burstSize' => [ + 'inherited' => false, + 'value' => $speed, + ], + ]), + 'outShapingPolicy' => new DVSTrafficShapingPolicy([ + 'inherited' => false, + 'enabled' => [ + 'inherited' => false, + 'value' => true, + ], + 'averageBandwidth' => [ + 'inherited' => false, + 'value' => $speed, + ], + 'peakBandwidth' => [ + 'inherited' => false, + 'value' => $speed, + ], + 'burstSize' => [ + 'inherited' => false, + 'value' => $speed, + ], + ]), + ]), + ]), + ]; + + return $this->request('ReconfigureDVPortgroup_Task', $body); + } + + public function reconfigureComputeResource( + string $clusterComputerResourceId, + string $name, + array $vmIds + ) { + $vm = []; + + foreach ($vmIds as $vmId) { + $vm[] = [ + '_' => $vmId, + 'type' => 'VirtualMachine', + ]; + } + + $body = [ + '_this' => [ + '_' => $clusterComputerResourceId, + 'type' => 'ComputeResource', + ], + 'spec' => new ClusterConfigSpecEx([ + 'drsConfig' => new ClusterDrsConfigInfo(), + 'rulesSpec' => new ClusterRuleSpec([ + 'operation' => 'add', + 'info' => new ClusterAntiAffinityRuleSpec([ + 'enabled' => true, + 'name' => $name, + 'userCreated' => true, + 'vm' => $vm, + ]), + ]), + 'dpmConfig' => new ClusterDpmConfigInfo(), + ]), + 'modify' => false, + ]; + + return $this->request('ReconfigureComputeResource_Task', $body); + } + public function mountIso(string $vmId, string $fileName, int $key, int $controllerKey, string $datastore) { $body = [ @@ -215,9 +346,7 @@ public function mountIso(string $vmId, string $fileName, int $key, int $controll ], 'bootOptions' => [ 'bootDelay' => 5000, - 'bootOrder' => [ - '@type' => 'VirtualMachineBootOptionsBootableCdromDevice', - ], + 'bootOrder' => new VirtualMachineBootOptionsBootableCdromDevice(), ], ], ]; @@ -255,7 +384,7 @@ public function findRulesForVm(string $vmId, string $clusterComputerResource) ], ]; - return $this->soapClient->FindRulesForVm($body); + return $this->request('FindRulesForVm', $body); } public function createFolder(string $parentFolder, string $name) @@ -268,13 +397,25 @@ public function createFolder(string $parentFolder, string $name) 'name' => $name, ]; - return $this->soapClient->CreateFolder($body); + return $this->request('CreateFolder', $body); + } + + public function deleteFolder(string $folderId) + { + $body = [ + '_this' => [ + '_' => $folderId, + 'type' => 'Folder', + ], + ]; + + return $this->request('Destroy_Task', $body); } public function createSnapshot( string $vmId, string $name, - string $description, + ?string $description, bool $memory = false, bool $quiesce = true ) { @@ -287,4 +428,76 @@ public function createSnapshot( return $this->vmRequest('CreateSnapshot_Task', $vmId, $body); } + + public function revertSnapshot(string $snapshopId) + { + $body = [ + '_this' => [ + '_' => $snapshopId, + 'type' => 'VirtualMachineSnapshot', + ], + ]; + + return $this->request('RevertToSnapshot_Task', $body); + } + + public function removeSnapshot(string $snapshopId, bool $removeChildren = true, bool $consolidate = true) + { + $body = [ + '_this' => [ + '_' => $snapshopId, + 'type' => 'VirtualMachineSnapshot', + ], + 'removeChildren' => $removeChildren, + 'consolidate' => $consolidate, + ]; + + return $this->request('RemoveSnapshot_Task', $body); + } + + public function queryPerf( + string $objectId, + ?string $startTime = null, + ?string $endTime = null, + int $intervalId = 20, + ?int $maxSample = null, + array $metricIds = [], + string $entity = 'VirtualMachine' + ) { + $body = [ + '_this' => [ + '_' => 'PerfMgr', + 'type' => 'PerformanceManager', + ], + 'querySpec' => [ + 'entity' => [ + '_' => $objectId, + 'type' => $entity, + ], + 'startTime' => $startTime, + 'endTime' => $endTime, + 'maxSample' => $maxSample, + 'metricId' => array_map(fn (int $id): array => ['counterId' => $id, 'instance' => ''], $metricIds), + 'intervalId' => $intervalId, + 'format' => 'normal', + ], + ]; + + return $this->transformToArrayValues($this->request('QueryPerf', $body, false)); + } + + public function acquireTicket(string $vmId, string $ticketType = 'webmks') + { + return $this->vmRequest('AcquireTicket', $vmId, ['ticketType' => $ticketType]); + } + + public function mountToolsInstaller(string $vmId) + { + return $this->vmRequest('MountToolsInstaller', $vmId); + } + + public function unmountToolsInstaller(string $vmId) + { + return $this->vmRequest('UnmountToolsInstaller', $vmId); + } } diff --git a/src/Transform/SoapTransform.php b/src/Transform/SoapTransform.php index 4d7d9d1..28eb1d6 100644 --- a/src/Transform/SoapTransform.php +++ b/src/Transform/SoapTransform.php @@ -4,55 +4,80 @@ use SoapVar; use stdClass; +use Xelon\VmWareClient\Types\Core\DynamicData; trait SoapTransform { + private $soapTypes = [ + 'string' => XSD_STRING, + 'integer' => XSD_INT, + 'boolean' => XSD_BOOLEAN, + 'double' => XSD_FLOAT, + 'array' => SOAP_ENC_OBJECT, + 'object' => SOAP_ENC_OBJECT, + ]; + public function arrayToSoapVar(array $array): array { $typeName = null; $data = []; foreach ($array as $key => $value) { - if (is_array($value)) { + if (is_array($value) || $value instanceof DynamicData) { + if ($value instanceof DynamicData) { + $typeName = (new \ReflectionClass($value))->getShortName(); + $value = $value->toArray(); + } + if (array_key_exists('@type', $value)) { $typeName = $value['@type']; unset($value['@type']); } if (array_key_exists('type', $value)) { - $data[$key] = new SoapVar($value['_'], null, $value['type'], '', '', ''); + $data[$key] = new SoapVar($value['_'], null, $value['type'], '', $key, ''); continue; } - // TODO: Get rid of BOGUS and ENV:Struct tags - /*if (array_key_exists(0, $value)) { - $arrayData = []; + if (is_array($value) && array_key_exists(0, $value)) { + foreach ($value as $childItem) { + if ($childItem instanceof DynamicData) { + $typeName = (new \ReflectionClass($childItem))->getShortName(); + $childItem = $childItem->toArray(); + } - foreach ($value as $item) { - $arrayData[] = new SoapVar($this->arrayToSoapVar($item), SOAP_ENC_OBJECT, $typeName, 'VirtualDeviceConfigSpec', 'deviceChange', ''); - } + if (is_array($childItem)) { + if (array_key_exists('@type', $childItem)) { + $typeName = $childItem['@type']; + unset($childItem['@type']); + } - $data[$key] = new SoapVar($arrayData, SOAP_ENC_OBJECT, '', '', 'deviceChanges', ''); + if (array_key_exists('type', $childItem)) { + $data[$key] = new SoapVar($childItem['_'], null, $childItem['type'], '', $key, ''); - continue; - }*/ + continue; + } + } else { + $data[$key] = new SoapVar($childItem, null, null, null, $key); - if (array_key_exists(0, $value)) { - $arrayData = []; + continue; + } - foreach ($value as $item) { - $arrayData[] = new SoapVar($this->arrayToSoapVar($item), SOAP_ENC_OBJECT, $typeName, null, 'deviceChange', null); + $data[] = new SoapVar($this->arrayToSoapVar($childItem), SOAP_ENC_OBJECT, $typeName, null, $key); } - $data[$key] = new SoapVar($arrayData, SOAP_ENC_OBJECT, null, 'deviceChange', null, null); + // unset($array[$key]); - continue; + $deepArraySet = true; + } + + if (! isset($deepArraySet)) { + $data[$key] = new SoapVar($this->arrayToSoapVar($value), SOAP_ENC_OBJECT, $typeName, null, $key); } - $data[$key] = new SoapVar($this->arrayToSoapVar($value), SOAP_ENC_OBJECT, $typeName, null, 'empty'); $typeName = null; } elseif (! is_null($value)) { - $data[$key] = $value; + $data[$key] = new SoapVar($value, null, null, null, $key); } } @@ -67,6 +92,98 @@ public function transformPropSet(array $data): stdClass $newData->{$item->name} = $item->val; } - return $newData; + return $this->transformToArrayValues($newData); + } + + /** + * @param stdClass $object + * @return stdClass + * This function transform to array objects that should be array type + */ + public function transformToArrayValues(stdClass $object, int $startIndex = 0, ?int $onlyIndexPath = null) + { + $pathes = [ + ['latestPage', 'TaskInfo'], + ['returnval', 'sampleInfo'], + ['returnval', 'value', 'value'], + ['returnval', 'config', 'consumerId'], + ['layoutEx', 'file'], + ['layoutEx', 'snapshot'], + ['layoutEx', 'snapshot', 'disk'], + ['layoutEx', 'snapshot', 'disk', 'chain'], + ['snapshot', 'rootSnapshotList'], + ['snapshot', 'rootSnapshotList', 'childSnapshotList'], + ]; + + $recursiveOblectNames = ['childSnapshotList']; + + foreach ($pathes as $indexPath => $path) { + if ($onlyIndexPath && $onlyIndexPath !== $indexPath) { + continue; + } + + $lastIndex = count($path) - 1; + $newObj = $object; + + foreach ($path as $index => $property) { + if ($index < $startIndex || ! is_object($newObj) || empty((array) $newObj) || ! property_exists($newObj, $property)) { + continue; + } + + $newObj = $newObj->{$property}; + + $varName = "el_$indexPath"; + + isset($$varName) ? $$varName = &$$varName->{$property} : $$varName = &$object->{$property}; + + if ($index === $lastIndex && ! is_array($newObj)) { + $$varName = [$$varName]; + } + + if ($index !== $lastIndex && is_array($$varName)) { + foreach ($$varName as &$oblectItem) { + $oblectItem = $this->transformToArrayValues($oblectItem, $index + 1, $indexPath); + } + } + + foreach ($recursiveOblectNames as $recursiveOblectName) { + if ($recursiveOblectName === $property) { + $this->transformToArrayValuesRecursive($$varName, $property); + } + } + } + } + + return $object; + } + + public function getListFilterQuery(array $filter): string + { + if ($this->version < 7) { + foreach ($filter as $key => $value) { + $filter["filter.$key"] = $value; + unset($filter[$key]); + } + } + + return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', http_build_query($filter, null, '&')); + } + + private function transformToArrayValuesRecursive(&$object, string $propertyName) + { + if (is_array($object)) { + foreach ($object as &$nestedObject) { + $nestedObject = $this->transformToArrayValuesRecursive($nestedObject, $propertyName); + } + } else { + if (isset($object->{$propertyName})) { + $object->{$propertyName} = [$object->{$propertyName}]; + foreach ($object->{$propertyName} as &$nestedObject1) { + $nestedObject1 = $this->transformToArrayValuesRecursive($nestedObject1, $propertyName); + } + } + } + + return $object; } } diff --git a/src/Types/ArrayUpdateSpec.php b/src/Types/ArrayUpdateSpec.php new file mode 100644 index 0000000..933a4e4 --- /dev/null +++ b/src/Types/ArrayUpdateSpec.php @@ -0,0 +1,12 @@ + $value) { + if (property_exists(static::class, $property)) { + $this->{$property} = $value; + } + } + } + + /** + * @return array + * Generate array of class properties in order from parent to child + */ + public function toArray(): array + { + $data = []; + $classes = []; + $properties = []; + + $currentClass = static::class; + while ($currentClass && $currentClass !== self::class) { + $classes[] = $currentClass; + $currentClass = get_parent_class($currentClass); + } + + $classes = array_reverse($classes); + + foreach ($classes as $class) { + $classInfo = new \ReflectionClass($class); + + foreach ($classInfo->getProperties() as $prop) { + if ($prop->class === $class) { + $properties[] = $prop->getName(); + } + } + } + + foreach ($properties as $property) { + if ($this->$property !== null) { + $data[$property] = $this->$property; + } + } + + return $data; + } + + public function count(): int + { + $count = 0; + + foreach ($this as $property) { + if ($property !== null) { + $count++; + } + } + + return $count; + } + + public function jsonSerialize(): array + { + return $this->toArray(); + } +} diff --git a/src/Types/CustomizationAdapterMapping.php b/src/Types/CustomizationAdapterMapping.php new file mode 100644 index 0000000..4ff1452 --- /dev/null +++ b/src/Types/CustomizationAdapterMapping.php @@ -0,0 +1,12 @@ +soap = new VcenterSoapClient($this->soapClient); } + + $this->apiUrlPrefix = $this->version >= 7 ? '/api' : '/rest'; } } diff --git a/src/VcenterSoapClient.php b/src/VcenterSoapClient.php index 066d9ec..a62e261 100755 --- a/src/VcenterSoapClient.php +++ b/src/VcenterSoapClient.php @@ -4,13 +4,17 @@ use SoapClient; use Xelon\VmWareClient\Data\SoapData; -use Xelon\VmWareClient\Traits\Soap\SoapFileApis; +use Xelon\VmWareClient\Requests\SoapRequest; +use Xelon\VmWareClient\Traits\Soap\SoapGuestApis; +use Xelon\VmWareClient\Traits\Soap\SoapStorageApis; use Xelon\VmWareClient\Traits\Soap\SoapVmApis; class VcenterSoapClient { + use SoapRequest; use SoapVmApis; - use SoapFileApis; + use SoapGuestApis; + use SoapStorageApis; public SoapClient $soapClient; diff --git a/src/VmWareClientInit.php b/src/VmWareClientInit.php index 9ebf13f..8cd6cbd 100755 --- a/src/VmWareClientInit.php +++ b/src/VmWareClientInit.php @@ -24,15 +24,23 @@ class VmWareClientInit private string $password; + protected float $version; + protected ?GuzzleClient $guzzleClient; protected ?\SoapClient $soapClient; - public function __construct(string $ip, string $login, string $password, string $mode = self::MODE_REST) - { + public function __construct( + string $ip, + string $login, + string $password, + string $mode = self::MODE_REST, + float $version = 7 + ) { $this->ip = $ip; $this->login = $login; $this->password = $password; + $this->version = $version; switch ($mode) { case self::MODE_REST: @@ -68,9 +76,16 @@ private function initRestSession(): void private function createRestSession(): void { try { - $authReponse = $this->guzzleClient->post('/api/session', ['auth' => [$this->login, $this->password]]); + $authReponse = $this->guzzleClient->post( + $this->version >= 7 ? '/api/session' : '/rest/com/vmware/cis/session', + ['auth' => [$this->login, $this->password]] + ); $apiSessionId = json_decode($authReponse->getBody()); + if ($this->version < 7) { + $apiSessionId = $apiSessionId->value; + } + Cache::add("vcenter-rest-session-$this->ip", [ 'api_session_id' => $apiSessionId, 'expired_at' => Carbon::now()->addSeconds(config('vmware-php-client.session_ttl') * 60 - 30), @@ -91,11 +106,10 @@ private function createRestSession(): void private function deleteRestSession(string $apiSessionId): void { try { - $this->guzzleClient->delete('api/session', [ - 'headers' => [ - 'vmware-api-session-id' => $apiSessionId, - ], - ]); + $this->guzzleClient->delete( + $this->version >= 7 ? 'api/session' : '/rest/com/vmware/cis/session', + ['headers' => ['vmware-api-session-id' => $apiSessionId], + ]); } catch (\Exception $exception) { } @@ -158,12 +172,25 @@ private function createSoapSession(): void ]; $this->soapClient->Login($loginMessage); + if (isset($this->soapClient->_cookies)) { + $soapSessionToken = $this->soapClient->_cookies['vmware_soap_session'][0]; + } else { + $responseHeaders = $this->soapClient->__last_response_headers; + + $string = strstr($responseHeaders, 'vmware_soap_session'); + $string = strstr($string, '"'); + $string = ltrim($string, '"'); + $soapSessionToken = substr($string, 0, strpos($string, '"')); + $this->soapClient->__setCookie('vmware_soap_session', $soapSessionToken); + } + Cache::add("vcenter-soap-session-$this->ip", [ - 'vmware_soap_session' => $this->soapClient->_cookies['vmware_soap_session'][0], + 'vmware_soap_session' => $soapSessionToken, 'expired_at' => Carbon::now()->addSeconds(config('vmware-php-client.session_ttl') * 60 - 30), ]); } catch (\Exception $e) { Log::error('Soap api exception : '.$e->getMessage()); + throw new \Exception($e->getMessage()); } } @@ -186,12 +213,16 @@ private function createSoapClientWithExistingSession(string $soapSessionToken) private function deleteSoapSession() { - $sessionManager = new \stdClass(); - $sessionManager->_ = $sessionManager->type = 'SessionManager'; + try { + Cache::forget("vcenter-soap-session-$this->ip"); - $soaplogout['_this'] = $sessionManager; - $this->soapClient->Logout($soaplogout); + $sessionManager = new \stdClass(); + $sessionManager->_ = $sessionManager->type = 'SessionManager'; - Cache::forget("vcenter-soap-session-$this->ip"); + $soaplogout['_this'] = $sessionManager; + $this->soapClient->Logout($soaplogout); + } catch (\Exception $exception) { + Log::error('Can\'t delete soap session'); + } } }