diff --git a/src/Requests/SoapRequest.php b/src/Requests/SoapRequest.php index 6884241..adf6924 100644 --- a/src/Requests/SoapRequest.php +++ b/src/Requests/SoapRequest.php @@ -19,14 +19,14 @@ trait SoapRequest /** * @return stdClass */ - public function request(string $method, array $requestBody, bool $convertToSoap = true) + public function request(string $method, array $requestBody, bool $withXMLAttributes = false, bool $convertToSoap = true) { try { $response = $this->soapClient->$method($convertToSoap ? $this->arrayToSoapVar($requestBody) : $requestBody); RequestEvent::dispatch( - property_exists($this->soapClient, '__last_request') ? $this->soapClient->__last_request : '', - property_exists($this->soapClient, '__last_response') ? $this->soapClient->__last_response : '', + $this->soapClient->__getLastRequest() ?? '', + $this->soapClient->__getLastResponse() ?? '', true ); @@ -34,17 +34,19 @@ public function request(string $method, array $requestBody, bool $convertToSoap Log::info( 'SOAP REQUEST SUCCESS:'. "\nSOAP method: ".$method. - property_exists($this->soapClient, '__last_request') - ? "\nSOAP request start***".preg_replace('#.+#', '*****', $this->soapClient->__last_request).'***SOAP request end' + $this->soapClient->__getLastRequest() + ? "\nSOAP request start***".preg_replace('#.+#', '*****', $this->soapClient->__getLastRequest()).'***SOAP request end' : '' ); } - return $response; + return ($withXMLAttributes) + ? $this->parseXMLResponse($this->soapClient->__getLastResponse()) + : $response; } catch (\Exception $exception) { RequestEvent::dispatch( - property_exists($this->soapClient, '__last_request') ? $this->soapClient->__last_request : '', - property_exists($this->soapClient, '__last_response') ? $this->soapClient->__last_response : '', + $this->soapClient->__getLastRequest() ?? '', + $this->soapClient->__getLastResponse() ?? '', false ); @@ -66,21 +68,21 @@ public function request(string $method, array $requestBody, bool $convertToSoap ]); $this->soapClient->__setCookie('vmware_soap_session', $sessionInfo['vmware_soap_session']); - return $this->request($method, $requestBody, $convertToSoap); + return $this->request($method, $requestBody, $withXMLAttributes, $convertToSoap); } } $message = "SOAP REQUEST FAILED:\nMessage: ".$exception->getMessage(). "\nSOAP method: ".$method. ( - property_exists($this->soapClient, '__last_request') - ? "\nSOAP request start***".preg_replace('#.+#', '*****', $this->soapClient->__last_request).'***SOAP request end' + $this->soapClient->__getLastRequest() + ? "\nSOAP request start***".preg_replace('#.+#', '*****', $this->soapClient->__getLastRequest()).'***SOAP request end' : '' - ).( - property_exists($this->soapClient, '__last_response') - ? "\nSOAP response start***: ".$this->soapClient->__last_response.'***SOAP response end' + ).( + $this->soapClient->__getLastResponse() + ? "\nSOAP response start***: ".$this->soapClient->__getLastResponse().'***SOAP response end' : '' - ); + ); // "\nTrace: ".json_encode($exception->getTrace()); Log::error($message); @@ -103,4 +105,61 @@ private function vmRequest(string $method, string $vmId, array $requestBody = [] return $this->request($method, $soapMessage); } + + /* + * Totally not stolen from https://stackoverflow.com/a/46349713 + */ + private function parseXMLResponse(string $xmlResponse) + { + $previous_value = libxml_use_internal_errors(true); + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->preserveWhiteSpace = false; + $dom->loadXml($xmlResponse); + libxml_use_internal_errors($previous_value); + + if (libxml_get_errors()) { + return []; + } + + return $this->domToArray($dom); + } + + private function domToArray($root) { + $result = []; + + if ($root->hasAttributes()) { + $attrs = $root->attributes; + foreach ($attrs as $attr) { + $result['@attributes'][$attr->name] = $attr->value; + } + } + + if ($root->hasChildNodes()) { + $children = $root->childNodes; + if ($children->length == 1) { + $child = $children->item(0); + if (in_array($child->nodeType,[XML_TEXT_NODE,XML_CDATA_SECTION_NODE])) { + $result['_value'] = $child->nodeValue; + return count($result) == 1 + ? $result['_value'] + : $result; + } + + } + $groups = []; + foreach ($children as $child) { + if (!isset($result[$child->nodeName])) { + $result[$child->nodeName] = $this->domToArray($child); + } else { + if (!isset($groups[$child->nodeName])) { + $result[$child->nodeName] = [$result[$child->nodeName]]; + $groups[$child->nodeName] = 1; + } + $result[$child->nodeName][] = $this->domToArray($child); + } + } + } + + return $result; + } } diff --git a/src/Traits/Soap/SoapImportApis.php b/src/Traits/Soap/SoapImportApis.php new file mode 100644 index 0000000..95f2d45 --- /dev/null +++ b/src/Traits/Soap/SoapImportApis.php @@ -0,0 +1,285 @@ + [ + '_' => 'OvfManager', + 'type' => 'OvfManager', + ], + 'ovfDescriptor' => file_get_contents($ovfPath), + 'pdp' => new OvfParseDescriptorParams([ + 'locale' => '', + 'deploymentOption' => '', + ]), + ]; + return $this->request('ParseDescriptor', $body); + } + + public function createImportSpec(string $ovfPath, string $resourcepool, string $datastore, array $networkMapping = null) + { + $body = [ + '_this' => [ + '_' => 'OvfManager', + 'type' => 'OvfManager', + ], + 'ovfDescriptor' => file_get_contents($ovfPath), + 'resourcePool' => $resourcepool, + 'datastore' => $datastore, + 'cisp' => new OvfCreateImportSpecParams([ + 'locale' => '', + 'entityName' => '', + 'deploymentOption' => '', + 'networkMapping' => $networkMapping, + ]), + ]; + + return $this->request('CreateImportSpec', $body, true); + } + + public function importVApp(string $resourcePoolId, $entityConfig, $instantiationOst, $configSpec, $folder = null, $host = null) + { + $extraConfig = []; + if (isset($configSpec->extraConfig)) { + // sometimes there is only one level of nesting + if (property_exists($configSpec->extraConfig, 'key')) { + $extraConfig['key'] = $configSpec->extraConfig->key; + $extraConfig['value:string'] = $configSpec->extraConfig->value->_value ?? ''; + // but sometimes there are several + } else { + foreach ($configSpec->extraConfig as $i => $config) { + $extraConfig[$i]['key'] = $config->key; + $extraConfig[$i]['value:string'] = $config->value->_value ?? ''; + } + } + } + + // devices may vary from one VM to another, therefore we can not explicitly set those params manually + $deviceChange = []; + if (isset($configSpec->deviceChange) && \count($configSpec->deviceChange) > 0) { + foreach ($configSpec->deviceChange as $i => $deviceObj) { + $deviceConfig = json_decode(json_encode($deviceObj), true); + $deviceChange[$i] = $this->build($deviceConfig, []); + } + } + + $body = [ + '_this' => [ + '_' => $resourcePoolId, + 'type' => 'ResourcePool', + ], + 'spec' => new VirtualMachineImportSpec([ + 'entityConfig' => new VAppEntityConfigInfo([ + 'key' => $entityConfig->key ?? null, + 'tag' => $entityConfig->tag ?? null, + 'startOrder' => $entityConfig->startOrder ?? null, + 'startDelay' => $entityConfig->startDelay ?? null, + 'waitingForGuest' => $entityConfig->waitingForGuest ?? null, + 'startAction' => $entityConfig->startAction ?? null, + 'stopDelay' => $entityConfig->stopDelay ?? null, + 'stopAction' => $entityConfig->stopAction ?? null, + 'destroyWithParent' => $entityConfig->destroyWithParent ?? null, + ]), + 'instantiationOst' => new OvfConsumerOstNode([ + 'id' => $instantiationOst->id ?? null, + 'type' => $instantiationOst->type ?? null, + 'section' => $instantiationOst->section ?? null, + 'child' => $instantiationOst->child ? [ + 'id' => $instantiationOst->child->id ?? null, + 'type' => $instantiationOst->child->type ?? null, + ] : null, + 'entity' => $instantiationOst->entity ?? null, + ]), + 'configSpec' => new VirtualMachineConfigSpec([ + 'name' => $configSpec->name ?? null, + 'version' => $configSpec->version ?? null, + 'uuid' => $configSpec->uuid ?? null, + 'guestId' => $configSpec->guestId ?? null, + 'files' => $configSpec->files ? new VirtualMachineFileInfo([ + 'vmPathName' => $configSpec->files->vmPathName ?? null, + 'snapshotDirectory' => $configSpec->files->snapshotDirectory ?? null, + 'suspendDirectory' => $configSpec->files->suspendDirectory ?? null, + 'logDirectory' => $configSpec->files->logDirectory ?? null, + 'ftMetadataDirectory' => $configSpec->files->ftMetadataDirectory ?? null, + ]) : null, + 'tools' => $configSpec->tools ? new ToolsConfigInfo([ + 'toolsVersion' => $configSpec->tools->toolsVersion ?? null, + 'toolsInstallType' => $configSpec->tools->toolsInstallType ?? null, + 'afterPowerOn' => $configSpec->tools->afterPowerOn ?? null, + 'afterResume' => $configSpec->tools->afterResume ?? null, + 'beforeGuestStandby' => $configSpec->tools->beforeGuestStandby ?? null, + 'beforeGuestShutdown' => $configSpec->tools->beforeGuestShutdown ?? null, + 'beforeGuestReboot' => $configSpec->tools->beforeGuestReboot ?? null, + 'toolsUpgradePolicy' => $configSpec->tools->toolsUpgradePolicy ?? null, + 'pendingCustomization' => $configSpec->tools->pendingCustomization ?? null, + 'customizationKeyId' => $configSpec->tools->customizationKeyId ?? null, + 'syncTimeWithHostAllowed' => $configSpec->tools->syncTimeWithHostAllowed ?? null, + 'syncTimeWithHost' => $configSpec->tools->syncTimeWithHost ?? null, + 'lastInstallInfo' => $configSpec->tools->lastInstallInfo ?? null, + + ]) : null, + 'flags' => $configSpec->flags ? new VirtualMachineFlagInfo([ + 'disableAcceleration' => $configSpec->flags->disableAcceleration ?? null, + 'enableLogging' => $configSpec->flags->enableLogging ?? null, + 'useToe' => $configSpec->flags->useToe ?? null, + 'runWithDebugInfo' => $configSpec->flags->runWithDebugInfo ?? null, + 'monitorType' => $configSpec->flags->monitorType ?? null, + 'htSharing' => $configSpec->flags->htSharing ?? null, + 'snapshotDisabled' => $configSpec->flags->snapshotDisabled ?? null, + 'snapshotLocked' => $configSpec->flags->snapshotLocked ?? null, + 'diskUuidEnabled' => $configSpec->flags->diskUuidEnabled ?? null, + 'virtualMmuUsage' => $configSpec->flags->virtualMmuUsage ?? null, + 'virtualExecUsage' => $configSpec->flags->virtualExecUsage ?? null, + 'snapshotPowerOffBehavior' => $configSpec->flags->snapshotPowerOffBehavior ?? null, + 'recordReplayEnabled' => $configSpec->flags->recordReplayEnabled ?? null, + 'faultToleranceType' => $configSpec->flags->faultToleranceType ?? null, + 'cbrcCacheEnabled' => $configSpec->flags->cbrcCacheEnabled ?? null, + 'vvtdEnabled' => $configSpec->flags->vvtdEnabled ?? null, + 'vbsEnabled' => $configSpec->flags->vbsEnabled ?? null, + ]) : null, + 'numCPUs' => $configSpec->numCPUs ?? null, + 'numCoresPerSocket' => $configSpec->numCoresPerSocket ?? null, + 'memoryMB' => $configSpec->memoryMB ?? null, + 'memoryHotAddEnabled' => $configSpec->memoryHotAddEnabled ?? null, + 'cpuHotAddEnabled' => $configSpec->cpuHotAddEnabled ?? null, + 'cpuHotRemoveEnabled' => $configSpec->cpuHotRemoveEnabled ?? null, + 'virtualICH7MPresent' => $configSpec->virtualICH7MPresent ?? null, + 'virtualSMCPresent' => $configSpec->virtualSMCPresent ?? null, + 'deviceChange' => $deviceChange, + 'memoryAllocation' => isset($configSpec->memoryAllocation) ? [ + 'reservation' => $configSpec->memoryAllocation->reservation ?? null, + 'expandableReservation' => $configSpec->memoryAllocation->expandableReservation ?? null, + 'limit' => $configSpec->memoryAllocation->limit ?? null, + 'shares' => $configSpec->memoryAllocation->shares ? [ + 'shares' => $configSpec->memoryAllocation->shares->shares ?? null, + 'level' => $configSpec->memoryAllocation->shares->level ?? null, + ] : null, + ] : null, + 'extraConfig' => \count($extraConfig) > 0 ? $extraConfig : null, + 'bootOptions' => $configSpec->bootOptions ? [ + 'bootDelay' => $configSpec->bootOptions->bootDelay ?? null, + 'enterBIOSSetup' => $configSpec->bootOptions->enterBIOSSetup ?? null, + 'efiSecureBootEnabled' => $configSpec->bootOptions->efiSecureBootEnabled, + 'bootRetryEnabled' => $configSpec->bootOptions->bootRetryEnabled ?? null, + 'bootRetryDelay' => $configSpec->bootOptions->bootRetryDelay ?? null, + 'bootOrder' => $configSpec->bootOptions->bootOrder ?? null, + 'networkBootProtocol' => $configSpec->bootOptions->networkBootProtocol ?? null, + ] : null, + 'vAppConfig' => [ + 'product' => $configSpec->vAppConfig->product ? [ + 'operation' => $configSpec->vAppConfig->product->operation ?? null, + 'info' => $configSpec->vAppConfig->product->info ? [ + 'key' => $configSpec->vAppConfig->product->info->key ?? null, + 'classId' => $configSpec->vAppConfig->product->info->classId ?? null, + 'instanceId' => $configSpec->vAppConfig->product->info->instanceId ?? null, + 'name' => $configSpec->vAppConfig->product->info->name ?? null, + 'vendor' => $configSpec->vAppConfig->product->info->vendor ?? null, + 'version' => $configSpec->vAppConfig->product->info->version ?? null, + 'fullVersion' => $configSpec->vAppConfig->product->info->fullVersion ?? null, + 'vendorUrl' => $configSpec->vAppConfig->product->info->vendorUrl ?? null, + 'productUrl' => $configSpec->vAppConfig->product->info->productUrl ?? null, + 'appUrl' => $configSpec->vAppConfig->product->info->appUrl ?? null, + ] : null, + ] : null, + 'installBootRequired' => $configSpec->vAppConfig->installBootRequired ?? null, + 'installBootStopDelay' => $configSpec->vAppConfig->installBootStopDelay ?? null, + ], + 'firmware' => $configSpec->firmware ?? null, + 'nestedHVEnabled' => $configSpec->nestedHVEnabled ?? null, + 'sgxInfo' => isset($configSpec->sgxInfo) ? [ + 'epcSize' => $configSpec->sgxInfo->epcSize ?? null, + 'flcMode' => $configSpec->sgxInfo->flcMode ?? null, + 'lePubKeyHash' => $configSpec->sgxInfo->lePubKeyHash ?? null, + ] : null, + 'sevEnabled' => $configSpec->sevEnabled ?? null, + ]), + ]), + 'folder' => $folder, + 'host' => $host, + ]; + + return $this->request('ImportVApp', $body); + } + + private function build($data, $arrayToBuild, $object = null) + { + if ($object) { + foreach ($data as $key => $value) { + if (!is_array($value)) { + $object->$key = $value; + } else if (is_array($value) && array_key_exists('@attributes', $value)) { + $className = "Xelon\\VmWareClient\\Types\\".$value['@attributes']['type']; + $newObj = (new $className); + unset($value['@attributes']); + + $object->$key = $this->build($value, [], $newObj); + } else if (is_array($value)) { + $object->$key = $this->build($value, []); + } + } + + return $object; + } + + foreach ($data as $key => $value) { + if (!is_array($value)) { + $arrayToBuild[$key] = $value; + } else if (is_array($value) && array_key_exists('@attributes', $value)) { + $className = "Xelon\\VmWareClient\\Types\\".$value['@attributes']['type']; + $newObj = (new $className); + unset($value['@attributes']); + + $arrayToBuild[$key] = $this->build($value, [], $newObj); + } else if (is_array($value)) { + $arrayToBuild[$key] = $this->build($value, []); + } + } + + return $arrayToBuild; + } + + public function httpNfcLeaseProgress($httpNfcLease, int $percent) + { + $body = [ + '_this' => [ + '_' => $httpNfcLease->returnval->_, + 'type' => 'HttpNfcLease', + ], + 'percent' => $percent, + ]; + + return $this->request('HttpNfcLeaseProgress', $body); + } + + public function httpNfcLeaseComplete($httpNfcLease) + { + $body = [ + '_this' => [ + '_' => $httpNfcLease->returnval->_, + 'type' => 'HttpNfcLease', + ], + ]; + + return $this->request('HttpNfcLeaseComplete', $body); + } +} \ No newline at end of file diff --git a/src/Traits/Soap/SoapVmApis.php b/src/Traits/Soap/SoapVmApis.php index 670fc5e..28d1faf 100644 --- a/src/Traits/Soap/SoapVmApis.php +++ b/src/Traits/Soap/SoapVmApis.php @@ -30,8 +30,8 @@ public function getObjectInfo(string $objectId, string $objectType, string $path } catch (\Exception $exception) { Log::error( "SOAP REQUEST FAILED:\nMessage: ".$exception->getMessage(). - "\nSOAP request: ".$this->soapClient->__last_request. - "\nSOAP response: ".$this->soapClient->__last_response ?? '' + "\nSOAP request: ".($this->soapClient->__getLastRequest() ?? ''). + "\nSOAP response: ".($this->soapClient->__getLastResponse() ?? '') ); if (array_keys(json_decode(json_encode($exception->detail), true))[0] === 'ManagedObjectNotFoundFault') { diff --git a/src/Transform/SoapTransform.php b/src/Transform/SoapTransform.php index 84ca982..b409acb 100644 --- a/src/Transform/SoapTransform.php +++ b/src/Transform/SoapTransform.php @@ -22,6 +22,11 @@ public function arrayToSoapVar(array $array): array $typeName = null; $data = []; foreach ($array as $key => $value) { + if (str_contains($key, ':')) { + $keyString = explode(':', $key); + $key = $keyString[0]; + $type = $keyString[1]; + } if (is_array($value) || $value instanceof DynamicData) { if ($value instanceof DynamicData) { $typeName = (new \ReflectionClass($value))->getShortName(); @@ -75,7 +80,9 @@ public function arrayToSoapVar(array $array): array $typeName = null; } elseif (! is_null($value)) { - $data[$key] = new SoapVar($value, null, null, null, $key); + $encoding = isset($type) ? XSD_STRING : null; + $typeName = isset($type) ? 'xsd:string' : null; + $data[$key] = new SoapVar($value, $encoding, $typeName, null, $key); } } diff --git a/src/Types/Core/ExtensibleManagedObject.php b/src/Types/Core/ExtensibleManagedObject.php new file mode 100644 index 0000000..dd5ee98 --- /dev/null +++ b/src/Types/Core/ExtensibleManagedObject.php @@ -0,0 +1,8 @@ +soapClient->Login($loginMessage); - if (isset($this->soapClient->_cookies)) { - $soapSessionToken = $this->soapClient->_cookies['vmware_soap_session'][0]; + if (array_key_exists('vmware_soap_session', $this->soapClient->__getCookies())) { + $soapSessionToken = $this->soapClient->__getCookies()['vmware_soap_session'][0]; } else { - $responseHeaders = $this->soapClient->__last_response_headers; + $responseHeaders = $this->soapClient->__getLastResponseHeaders(); $string = strstr($responseHeaders, 'vmware_soap_session'); $string = strstr($string, '"');