From 0af06a3c8896b295a5cd54e2749e0797702ea94e Mon Sep 17 00:00:00 2001 From: akutz Date: Thu, 19 Dec 2024 09:34:06 -0600 Subject: [PATCH] api: Support encode/decode OVF to JSON This patch adds support for encoding an `ovf.Envelope` to JSON using Golang's `encoding/json` package. The resulting output can be safely decoded back to an `ovf.Envelope`. This decoded envelope is deep-equal to the one originally decoded from XML. This feature allows clients to format an OVF envelope as YAML, such as when part of a Kubernetes API. Signed-off-by: akutz --- ovf/cim.go | 174 ++++++++++++++++++++++----------------------- ovf/env.go | 24 +++---- ovf/envelope.go | 184 ++++++++++++++++++++++++------------------------ ovf/ovf_test.go | 74 +++++++++++++++++++ 4 files changed, 265 insertions(+), 191 deletions(-) diff --git a/ovf/cim.go b/ovf/cim.go index 2047696a4..7524dcecd 100644 --- a/ovf/cim.go +++ b/ovf/cim.go @@ -70,28 +70,28 @@ Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_VirtualSystem */ type CIMVirtualSystemSettingData struct { - ElementName string `xml:"ElementName"` - InstanceID string `xml:"InstanceID"` - - AutomaticRecoveryAction *uint8 `xml:"AutomaticRecoveryAction"` - AutomaticShutdownAction *uint8 `xml:"AutomaticShutdownAction"` - AutomaticStartupAction *uint8 `xml:"AutomaticStartupAction"` - AutomaticStartupActionDelay *string `xml:"AutomaticStartupActionDelay>Interval"` - AutomaticStartupActionSequenceNumber *uint16 `xml:"AutomaticStartupActionSequenceNumber"` - Caption *string `xml:"Caption"` - ConfigurationDataRoot *string `xml:"ConfigurationDataRoot"` - ConfigurationFile *string `xml:"ConfigurationFile"` - ConfigurationID *string `xml:"ConfigurationID"` - CreationTime *string `xml:"CreationTime"` - Description *string `xml:"Description"` - LogDataRoot *string `xml:"LogDataRoot"` - Notes []string `xml:"Notes"` - RecoveryFile *string `xml:"RecoveryFile"` - SnapshotDataRoot *string `xml:"SnapshotDataRoot"` - SuspendDataRoot *string `xml:"SuspendDataRoot"` - SwapFileDataRoot *string `xml:"SwapFileDataRoot"` - VirtualSystemIdentifier *string `xml:"VirtualSystemIdentifier"` - VirtualSystemType *string `xml:"VirtualSystemType"` + ElementName string `xml:"ElementName" json:"elementName"` + InstanceID string `xml:"InstanceID" json:"instanceID"` + + AutomaticRecoveryAction *uint8 `xml:"AutomaticRecoveryAction" json:"automaticRecoveryAction,omitempty"` + AutomaticShutdownAction *uint8 `xml:"AutomaticShutdownAction" json:"automaticShutdownAction,omitempty"` + AutomaticStartupAction *uint8 `xml:"AutomaticStartupAction" json:"automaticStartupAction,omitempty"` + AutomaticStartupActionDelay *string `xml:"AutomaticStartupActionDelay>Interval" json:"automaticStartupActionDelay,omitempty"` + AutomaticStartupActionSequenceNumber *uint16 `xml:"AutomaticStartupActionSequenceNumber" json:"automaticStartupActionSequenceNumber,omitempty"` + Caption *string `xml:"Caption" json:"caption,omitempty"` + ConfigurationDataRoot *string `xml:"ConfigurationDataRoot" json:"configurationDataRoot,omitempty"` + ConfigurationFile *string `xml:"ConfigurationFile" json:"configurationFile,omitempty"` + ConfigurationID *string `xml:"ConfigurationID" json:"configurationID,omitempty"` + CreationTime *string `xml:"CreationTime" json:"creationTime,omitempty"` + Description *string `xml:"Description" json:"description,omitempty"` + LogDataRoot *string `xml:"LogDataRoot" json:"logDataRoot,omitempty"` + Notes []string `xml:"Notes" json:"notes,omitempty"` + RecoveryFile *string `xml:"RecoveryFile" json:"recoveryFile,omitempty"` + SnapshotDataRoot *string `xml:"SnapshotDataRoot" json:"snapshotDataRoot,omitempty"` + SuspendDataRoot *string `xml:"SuspendDataRoot" json:"suspendDataRoot,omitempty"` + SwapFileDataRoot *string `xml:"SwapFileDataRoot" json:"swapFileDataRoot,omitempty"` + VirtualSystemIdentifier *string `xml:"VirtualSystemIdentifier" json:"virtualSystemIdentifier,omitempty"` + VirtualSystemType *string `xml:"VirtualSystemType" json:"virtualSystemType,omitempty"` } /* @@ -99,75 +99,75 @@ Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_ResourceAlloc */ type CIMResourceAllocationSettingData struct { - ElementName string `xml:"ElementName"` - InstanceID string `xml:"InstanceID"` - - ResourceType *CIMResourceType `xml:"ResourceType"` - OtherResourceType *string `xml:"OtherResourceType"` - ResourceSubType *string `xml:"ResourceSubType"` - - AddressOnParent *string `xml:"AddressOnParent"` - Address *string `xml:"Address"` - AllocationUnits *string `xml:"AllocationUnits"` - AutomaticAllocation *bool `xml:"AutomaticAllocation"` - AutomaticDeallocation *bool `xml:"AutomaticDeallocation"` - Caption *string `xml:"Caption"` - Connection []string `xml:"Connection"` - ConsumerVisibility *uint16 `xml:"ConsumerVisibility"` - Description *string `xml:"Description"` - HostResource []string `xml:"HostResource"` - Limit *uint64 `xml:"Limit"` - MappingBehavior *uint `xml:"MappingBehavior"` - Parent *string `xml:"Parent"` - PoolID *string `xml:"PoolID"` - Reservation *uint64 `xml:"Reservation"` - VirtualQuantity *uint `xml:"VirtualQuantity"` - VirtualQuantityUnits *string `xml:"VirtualQuantityUnits"` - Weight *uint `xml:"Weight"` + ElementName string `xml:"ElementName" json:"elementName,omitempty"` + InstanceID string `xml:"InstanceID" json:"instanceID,omitempty"` + + ResourceType *CIMResourceType `xml:"ResourceType" json:"resourceType,omitempty"` + OtherResourceType *string `xml:"OtherResourceType" json:"otherResourceType,omitempty"` + ResourceSubType *string `xml:"ResourceSubType" json:"resourceSubType,omitempty"` + + AddressOnParent *string `xml:"AddressOnParent" json:"addressOnParent,omitempty"` + Address *string `xml:"Address" json:"address,omitempty"` + AllocationUnits *string `xml:"AllocationUnits" json:"allocationUnits,omitempty"` + AutomaticAllocation *bool `xml:"AutomaticAllocation" json:"automaticAllocation,omitempty"` + AutomaticDeallocation *bool `xml:"AutomaticDeallocation" json:"automaticDeallocation,omitempty"` + Caption *string `xml:"Caption" json:"caption,omitempty"` + Connection []string `xml:"Connection" json:"connection,omitempty"` + ConsumerVisibility *uint16 `xml:"ConsumerVisibility" json:"consumerVisibility,omitempty"` + Description *string `xml:"Description" json:"description,omitempty"` + HostResource []string `xml:"HostResource" json:"hostResource,omitempty"` + Limit *uint64 `xml:"Limit" json:"limit,omitempty"` + MappingBehavior *uint `xml:"MappingBehavior" json:"mappingBehavior,omitempty"` + Parent *string `xml:"Parent" json:"parent,omitempty"` + PoolID *string `xml:"PoolID" json:"poolID,omitempty"` + Reservation *uint64 `xml:"Reservation" json:"reservation,omitempty"` + VirtualQuantity *uint `xml:"VirtualQuantity" json:"virtualQuantity,omitempty"` + VirtualQuantityUnits *string `xml:"VirtualQuantityUnits" json:"virtualQuantityUnits,omitempty"` + Weight *uint `xml:"Weight" json:"weight,omitempty"` } /* Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_StorageAllocationSettingData.xsd */ type CIMStorageAllocationSettingData struct { - ElementName string `xml:"ElementName"` - InstanceID string `xml:"InstanceID"` - - ResourceType *CIMResourceType `xml:"ResourceType"` - OtherResourceType *string `xml:"OtherResourceType"` - ResourceSubType *string `xml:"ResourceSubType"` - - Access *uint16 `xml:"Access"` - Address *string `xml:"Address"` - AddressOnParent *string `xml:"AddressOnParent"` - AllocationUnits *string `xml:"AllocationUnits"` - AutomaticAllocation *bool `xml:"AutomaticAllocation"` - AutomaticDeallocation *bool `xml:"AutomaticDeallocation"` - Caption *string `xml:"Caption"` - ChangeableType *uint16 `xml:"ChangeableType"` - ComponentSetting []types.AnyType `xml:"ComponentSetting"` - ConfigurationName *string `xml:"ConfigurationName"` - Connection []string `xml:"Connection"` - ConsumerVisibility *uint16 `xml:"ConsumerVisibility"` - Description *string `xml:"Description"` - Generation *uint64 `xml:"Generation"` - HostExtentName *string `xml:"HostExtentName"` - HostExtentNameFormat *uint16 `xml:"HostExtentNameFormat"` - HostExtentNameNamespace *uint16 `xml:"HostExtentNameNamespace"` - HostExtentStartingAddress *uint64 `xml:"HostExtentStartingAddress"` - HostResource []string `xml:"HostResource"` - HostResourceBlockSize *uint64 `xml:"HostResourceBlockSize"` - Limit *uint64 `xml:"Limit"` - MappingBehavior *uint `xml:"MappingBehavior"` - OtherHostExtentNameFormat *string `xml:"OtherHostExtentNameFormat"` - OtherHostExtentNameNamespace *string `xml:"OtherHostExtentNameNamespace"` - Parent *string `xml:"Parent"` - PoolID *string `xml:"PoolID"` - Reservation *uint64 `xml:"Reservation"` - SoID *string `xml:"SoID"` - SoOrgID *string `xml:"SoOrgID"` - VirtualQuantity *uint `xml:"VirtualQuantity"` - VirtualQuantityUnits *string `xml:"VirtualQuantityUnits"` - VirtualResourceBlockSize *uint64 `xml:"VirtualResourceBlockSize"` - Weight *uint `xml:"Weight"` + ElementName string `xml:"ElementName" json:"elementName"` + InstanceID string `xml:"InstanceID" json:"instanceID"` + + ResourceType *CIMResourceType `xml:"ResourceType" json:"resourceType,omitempty"` + OtherResourceType *string `xml:"OtherResourceType" json:"otherResourceType,omitempty"` + ResourceSubType *string `xml:"ResourceSubType" json:"resourceSubType,omitempty"` + + Access *uint16 `xml:"Access" json:"access,omitempty"` + Address *string `xml:"Address" json:"address,omitempty"` + AddressOnParent *string `xml:"AddressOnParent" json:"addressOnParent,omitempty"` + AllocationUnits *string `xml:"AllocationUnits" json:"allocationUnits,omitempty"` + AutomaticAllocation *bool `xml:"AutomaticAllocation" json:"automaticAllocation,omitempty"` + AutomaticDeallocation *bool `xml:"AutomaticDeallocation" json:"automaticDeallocation,omitempty"` + Caption *string `xml:"Caption" json:"caption,omitempty"` + ChangeableType *uint16 `xml:"ChangeableType" json:"changeableType,omitempty"` + ComponentSetting []types.AnyType `xml:"ComponentSetting" json:"componentSetting,omitempty"` + ConfigurationName *string `xml:"ConfigurationName" json:"configurationName,omitempty"` + Connection []string `xml:"Connection" json:"connection,omitempty"` + ConsumerVisibility *uint16 `xml:"ConsumerVisibility" json:"consumerVisibility,omitempty"` + Description *string `xml:"Description" json:"description,omitempty"` + Generation *uint64 `xml:"Generation" json:"generation,omitempty"` + HostExtentName *string `xml:"HostExtentName" json:"hostExtentName,omitempty"` + HostExtentNameFormat *uint16 `xml:"HostExtentNameFormat" json:"hostExtentNameFormat,omitempty"` + HostExtentNameNamespace *uint16 `xml:"HostExtentNameNamespace" json:"hostExtentNameNamespace,omitempty"` + HostExtentStartingAddress *uint64 `xml:"HostExtentStartingAddress" json:"hostExtentStartingAddress,omitempty"` + HostResource []string `xml:"HostResource" json:"hostResource,omitempty"` + HostResourceBlockSize *uint64 `xml:"HostResourceBlockSize" json:"hostResourceBlockSize,omitempty"` + Limit *uint64 `xml:"Limit" json:"limit,omitempty"` + MappingBehavior *uint `xml:"MappingBehavior" json:"mappingBehavior,omitempty"` + OtherHostExtentNameFormat *string `xml:"OtherHostExtentNameFormat" json:"otherHostExtentNameFormat,omitempty"` + OtherHostExtentNameNamespace *string `xml:"OtherHostExtentNameNamespace" json:"otherHostExtentNameNamespace,omitempty"` + Parent *string `xml:"Parent" json:"parent,omitempty"` + PoolID *string `xml:"PoolID" json:"poolID,omitempty"` + Reservation *uint64 `xml:"Reservation" json:"reservation,omitempty"` + SoID *string `xml:"SoID" json:"soID,omitempty"` + SoOrgID *string `xml:"SoOrgID" json:"soOrgID,omitempty"` + VirtualQuantity *uint `xml:"VirtualQuantity" json:"virtualQuantity,omitempty"` + VirtualQuantityUnits *string `xml:"VirtualQuantityUnits" json:"virtualQuantityUnits,omitempty"` + VirtualResourceBlockSize *uint64 `xml:"VirtualResourceBlockSize" json:"virtualResourceBlockSize,omitempty"` + Weight *uint `xml:"Weight" json:"weight,omitempty"` } diff --git a/ovf/env.go b/ovf/env.go index 3ec1b99d0..02f7d7a2f 100644 --- a/ovf/env.go +++ b/ovf/env.go @@ -44,28 +44,28 @@ const ( ) type Env struct { - XMLName xml.Name `xml:"http://schemas.dmtf.org/ovf/environment/1 Environment"` - ID string `xml:"id,attr"` - EsxID string `xml:"http://www.vmware.com/schema/ovfenv esxId,attr"` + XMLName xml.Name `xml:"http://schemas.dmtf.org/ovf/environment/1 Environment" json:"xmlName"` + ID string `xml:"id,attr" json:"id"` + EsxID string `xml:"http://www.vmware.com/schema/ovfenv esxId,attr" json:"esxID"` - Platform *PlatformSection `xml:"PlatformSection"` - Property *PropertySection `xml:"PropertySection"` + Platform *PlatformSection `xml:"PlatformSection" json:"platformSection,omitempty"` + Property *PropertySection `xml:"PropertySection" json:"propertySection,omitempty"` } type PlatformSection struct { - Kind string `xml:"Kind"` - Version string `xml:"Version"` - Vendor string `xml:"Vendor"` - Locale string `xml:"Locale"` + Kind string `xml:"Kind" json:"kind,omitempty"` + Version string `xml:"Version" json:"version,omitempty"` + Vendor string `xml:"Vendor" json:"vendor,omitempty"` + Locale string `xml:"Locale" json:"locale,omitempty"` } type PropertySection struct { - Properties []EnvProperty `xml:"Property"` + Properties []EnvProperty `xml:"Property" json:"property,omitempty"` } type EnvProperty struct { - Key string `xml:"key,attr"` - Value string `xml:"value,attr"` + Key string `xml:"key,attr" json:"key"` + Value string `xml:"value,attr" json:"value,omitempty"` } // Marshal marshals Env to xml by using xml.Marshal. diff --git a/ovf/envelope.go b/ovf/envelope.go index 38dd889ab..287cb22d2 100644 --- a/ovf/envelope.go +++ b/ovf/envelope.go @@ -27,81 +27,81 @@ import ( // // A VirtualSystem may have zero or more VirtualHardware sections. type Envelope struct { - References []File `xml:"References>File"` + References []File `xml:"References>File" json:"references,omitempty"` // Package level meta-data - Disk *DiskSection `xml:"DiskSection,omitempty"` - Network *NetworkSection `xml:"NetworkSection,omitempty"` - DeploymentOption *DeploymentOptionSection `xml:"DeploymentOptionSection,omitempty"` + Disk *DiskSection `xml:"DiskSection,omitempty" json:"diskSection,omitempty"` + Network *NetworkSection `xml:"NetworkSection,omitempty" json:"networkSection,omitempty"` + DeploymentOption *DeploymentOptionSection `xml:"DeploymentOptionSection,omitempty" json:"deploymentOptionSection,omitempty"` // Content: A VirtualSystem or a VirtualSystemCollection - VirtualSystem *VirtualSystem `xml:"VirtualSystem,omitempty"` - VirtualSystemCollection *VirtualSystemCollection `xml:"VirtualSystemCollection,omitempty"` + VirtualSystem *VirtualSystem `xml:"VirtualSystem,omitempty" json:"virtualSystem,omitempty"` + VirtualSystemCollection *VirtualSystemCollection `xml:"VirtualSystemCollection,omitempty" json:"virtualSystemCollection,omitempty"` } type VirtualSystem struct { Content - Annotation *AnnotationSection `xml:"AnnotationSection,omitempty"` - Product []ProductSection `xml:"ProductSection,omitempty"` - Eula []EulaSection `xml:"EulaSection,omitempty"` - OperatingSystem *OperatingSystemSection `xml:"OperatingSystemSection,omitempty"` - VirtualHardware []VirtualHardwareSection `xml:"VirtualHardwareSection,omitempty"` + Annotation *AnnotationSection `xml:"AnnotationSection,omitempty" json:"annotationSection,omitempty"` + Product []ProductSection `xml:"ProductSection,omitempty" json:"productSection,omitempty"` + Eula []EulaSection `xml:"EulaSection,omitempty" json:"eulaSection,omitempty"` + OperatingSystem *OperatingSystemSection `xml:"OperatingSystemSection,omitempty" json:"operatingSystemSection,omitempty"` + VirtualHardware []VirtualHardwareSection `xml:"VirtualHardwareSection,omitempty" json:"virtualHardwareSection,omitempty"` } type VirtualSystemCollection struct { Content // Collection level meta-data - ResourceAllocation *ResourceAllocationSection `xml:"ResourceAllocationSection,omitempty"` - Annotation *AnnotationSection `xml:"AnnotationSection,omitempty"` - Product []ProductSection `xml:"ProductSection,omitempty"` - Eula []EulaSection `xml:"EulaSection,omitempty"` + ResourceAllocation *ResourceAllocationSection `xml:"ResourceAllocationSection,omitempty" json:"resourceAllocationSection,omitempty"` + Annotation *AnnotationSection `xml:"AnnotationSection,omitempty" json:"annotationSection,omitempty"` + Product []ProductSection `xml:"ProductSection,omitempty" json:"productSection,omitempty"` + Eula []EulaSection `xml:"EulaSection,omitempty" json:"eulaSection,omitempty"` // Content: One or more VirtualSystems - VirtualSystem []VirtualSystem `xml:"VirtualSystem,omitempty"` + VirtualSystem []VirtualSystem `xml:"VirtualSystem,omitempty" json:"virtualSystem,omitempty"` } type File struct { - ID string `xml:"id,attr"` - Href string `xml:"href,attr"` - Size uint `xml:"size,attr"` - Compression *string `xml:"compression,attr"` - ChunkSize *int `xml:"chunkSize,attr"` + ID string `xml:"id,attr" json:"id,omitempty"` + Href string `xml:"href,attr" json:"href,omitempty"` + Size uint `xml:"size,attr" json:"size,omitempty"` + Compression *string `xml:"compression,attr" json:"compression,omitempty"` + ChunkSize *int `xml:"chunkSize,attr" json:"chunkSize,omitempty"` } type Content struct { - ID string `xml:"id,attr"` - Info string `xml:"Info"` - Name *string `xml:"Name"` + ID string `xml:"id,attr" json:"id,omitempty"` + Info string `xml:"Info" json:"info,omitempty"` + Name *string `xml:"Name" json:"name,omitempty"` } type Section struct { - Required *bool `xml:"required,attr"` - Info string `xml:"Info"` - Category string `xml:"Category"` + Required *bool `xml:"required,attr" json:"required,omitempty"` + Info string `xml:"Info" json:"info,omitempty"` + Category string `xml:"Category" json:"category,omitempty"` } type AnnotationSection struct { Section - Annotation string `xml:"Annotation"` + Annotation string `xml:"Annotation" json:"annotation,omitempty"` } type ProductSection struct { Section - Class *string `xml:"class,attr"` - Instance *string `xml:"instance,attr"` + Class *string `xml:"class,attr" json:"class,omitempty"` + Instance *string `xml:"instance,attr" json:"instance,omitempty"` - Product string `xml:"Product"` - Vendor string `xml:"Vendor"` - Version string `xml:"Version"` - FullVersion string `xml:"FullVersion"` - ProductURL string `xml:"ProductUrl"` - VendorURL string `xml:"VendorUrl"` - AppURL string `xml:"AppUrl"` - Property []Property `xml:"Property"` + Product string `xml:"Product" json:"product,omitempty"` + Vendor string `xml:"Vendor" json:"vendor,omitempty"` + Version string `xml:"Version" json:"version,omitempty"` + FullVersion string `xml:"FullVersion" json:"fullVersion,omitempty"` + ProductURL string `xml:"ProductUrl" json:"productUrl,omitempty"` + VendorURL string `xml:"VendorUrl" json:"vendorUrl,omitempty"` + AppURL string `xml:"AppUrl" json:"appUrl,omitempty"` + Property []Property `xml:"Property" json:"property,omitempty"` } func (p ProductSection) Key(prop Property) string { @@ -119,86 +119,86 @@ func (p ProductSection) Key(prop Property) string { } type Property struct { - Key string `xml:"key,attr"` - Type string `xml:"type,attr"` - Qualifiers *string `xml:"qualifiers,attr"` - UserConfigurable *bool `xml:"userConfigurable,attr"` - Default *string `xml:"value,attr"` - Password *bool `xml:"password,attr"` - Configuration *string `xml:"configuration,attr"` + Key string `xml:"key,attr" json:"key,omitempty"` + Type string `xml:"type,attr" json:"type,omitempty"` + Qualifiers *string `xml:"qualifiers,attr" json:"qualifiers,omitempty"` + UserConfigurable *bool `xml:"userConfigurable,attr" json:"userConfigurable,omitempty"` + Default *string `xml:"value,attr" json:"default,omitempty"` + Password *bool `xml:"password,attr" json:"password,omitempty"` + Configuration *string `xml:"configuration,attr" json:"configuration,omitempty"` - Label *string `xml:"Label"` - Description *string `xml:"Description"` + Label *string `xml:"Label" json:"label,omitempty"` + Description *string `xml:"Description" json:"description,omitempty"` - Values []PropertyConfigurationValue `xml:"Value"` + Values []PropertyConfigurationValue `xml:"Value" json:"value,omitempty"` } type PropertyConfigurationValue struct { - Value string `xml:"value,attr"` - Configuration *string `xml:"configuration,attr"` + Value string `xml:"value,attr" json:"value,omitempty"` + Configuration *string `xml:"configuration,attr" json:"configuration,omitempty"` } type NetworkSection struct { Section - Networks []Network `xml:"Network"` + Networks []Network `xml:"Network" json:"network,omitempty"` } type Network struct { - Name string `xml:"name,attr"` + Name string `xml:"name,attr" json:"name,omitempty"` - Description string `xml:"Description"` + Description string `xml:"Description" json:"description,omitempty"` } type DiskSection struct { Section - Disks []VirtualDiskDesc `xml:"Disk"` + Disks []VirtualDiskDesc `xml:"Disk" json:"disk,omitempty"` } type VirtualDiskDesc struct { - DiskID string `xml:"diskId,attr"` - FileRef *string `xml:"fileRef,attr"` - Capacity string `xml:"capacity,attr"` - CapacityAllocationUnits *string `xml:"capacityAllocationUnits,attr"` - Format *string `xml:"format,attr"` - PopulatedSize *int `xml:"populatedSize,attr"` - ParentRef *string `xml:"parentRef,attr"` + DiskID string `xml:"diskId,attr" json:"diskId,omitempty"` + FileRef *string `xml:"fileRef,attr" json:"fileRef,omitempty"` + Capacity string `xml:"capacity,attr" json:"capacity,omitempty"` + CapacityAllocationUnits *string `xml:"capacityAllocationUnits,attr" json:"capacityAllocationUnits,omitempty"` + Format *string `xml:"format,attr" json:"format,omitempty"` + PopulatedSize *int `xml:"populatedSize,attr" json:"populatedSize,omitempty"` + ParentRef *string `xml:"parentRef,attr" json:"parentRef,omitempty"` } type OperatingSystemSection struct { Section - ID int16 `xml:"id,attr"` - Version *string `xml:"version,attr"` - OSType *string `xml:"osType,attr"` + ID int16 `xml:"id,attr" json:"id"` + Version *string `xml:"version,attr" json:"version,omitempty"` + OSType *string `xml:"osType,attr" json:"osType,omitempty"` - Description *string `xml:"Description"` + Description *string `xml:"Description" json:"description,omitempty"` } type EulaSection struct { Section - License string `xml:"License"` + License string `xml:"License" json:"license,omitempty"` } type Config struct { - Required *bool `xml:"required,attr"` - Key string `xml:"key,attr"` - Value string `xml:"value,attr"` + Required *bool `xml:"required,attr" json:"required,omitempty"` + Key string `xml:"key,attr" json:"key,omitempty"` + Value string `xml:"value,attr" json:"value,omitempty"` } type VirtualHardwareSection struct { Section - ID *string `xml:"id,attr"` - Transport *string `xml:"transport,attr"` + ID *string `xml:"id,attr" json:"id"` + Transport *string `xml:"transport,attr" json:"transport,omitempty"` - System *VirtualSystemSettingData `xml:"System"` - Item []ResourceAllocationSettingData `xml:"Item"` - StorageItem []StorageAllocationSettingData `xml:"StorageItem"` - Config []Config `xml:"Config"` - ExtraConfig []Config `xml:"ExtraConfig"` + System *VirtualSystemSettingData `xml:"System" json:"system,omitempty"` + Item []ResourceAllocationSettingData `xml:"Item" json:"item,omitempty"` + StorageItem []StorageAllocationSettingData `xml:"StorageItem" json:"storageItem,omitempty"` + Config []Config `xml:"Config" json:"config,omitempty"` + ExtraConfig []Config `xml:"ExtraConfig" json:"extraConfig,omitempty"` } type VirtualSystemSettingData struct { @@ -208,42 +208,42 @@ type VirtualSystemSettingData struct { type ResourceAllocationSettingData struct { CIMResourceAllocationSettingData - Required *bool `xml:"required,attr"` - Configuration *string `xml:"configuration,attr"` - Bound *string `xml:"bound,attr"` - Config []Config `xml:"Config"` - CoresPerSocket *CoresPerSocket `xml:"CoresPerSocket"` + Required *bool `xml:"required,attr" json:"required,omitempty"` + Configuration *string `xml:"configuration,attr" json:"configuration,omitempty"` + Bound *string `xml:"bound,attr" json:"bound,omitempty"` + Config []Config `xml:"Config" json:"config,omitempty"` + CoresPerSocket *CoresPerSocket `xml:"CoresPerSocket" json:"coresPerSocket,omitempty"` } type StorageAllocationSettingData struct { CIMStorageAllocationSettingData - Required *bool `xml:"required,attr"` - Configuration *string `xml:"configuration,attr"` - Bound *string `xml:"bound,attr"` + Required *bool `xml:"required,attr" json:"required,omitempty"` + Configuration *string `xml:"configuration,attr" json:"configuration,omitempty"` + Bound *string `xml:"bound,attr" json:"bound,omitempty"` } type ResourceAllocationSection struct { Section - Item []ResourceAllocationSettingData `xml:"Item"` + Item []ResourceAllocationSettingData `xml:"Item" json:"item,omitempty"` } type DeploymentOptionSection struct { Section - Configuration []DeploymentOptionConfiguration `xml:"Configuration"` + Configuration []DeploymentOptionConfiguration `xml:"Configuration" json:"configuration,omitempty"` } type DeploymentOptionConfiguration struct { - ID string `xml:"id,attr"` - Default *bool `xml:"default,attr"` + ID string `xml:"id,attr" json:"id"` + Default *bool `xml:"default,attr" json:"default,omitempty"` - Label string `xml:"Label"` - Description string `xml:"Description"` + Label string `xml:"Label" json:"label,omitempty"` + Description string `xml:"Description" json:"description,omitempty"` } type CoresPerSocket struct { - Required *bool `xml:"required,attr"` - Value int32 `xml:",chardata"` + Required *bool `xml:"required,attr" json:"required,omitempty"` + Value int32 `xml:",chardata" json:"value,omitempty"` } diff --git a/ovf/ovf_test.go b/ovf/ovf_test.go index 61fc8e661..2b7c89c25 100644 --- a/ovf/ovf_test.go +++ b/ovf/ovf_test.go @@ -18,12 +18,18 @@ package ovf import ( "bytes" + "encoding/json" "fmt" "os" + "path" + "strconv" "testing" "text/tabwriter" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" + + "github.com/vmware/govmomi/vim25/xml" ) func testEnvelope(t *testing.T, fn string) *Envelope { @@ -131,3 +137,71 @@ func TestMultipleDeploymentConfigs(t *testing.T) { assert.Equal(t, "slotInfo.pciSlotNumber", e.VirtualSystem.VirtualHardware[0].Item[2].Config[0].Key) assert.Equal(t, "128", e.VirtualSystem.VirtualHardware[0].Item[2].Config[0].Value) } + +func TestJSONEncoder(t *testing.T) { + t.Parallel() + + testCases := []string{ + "fixtures/ttylinux.ovf", + "fixtures/configspec.ovf", + "fixtures/photon5.ovf", + "fixtures/ubuntu24.10.ovf", + "fixtures/virtualsystemcollection.ovf", + } + + for i := range testCases { + tc := testCases[i] + + t.Run(path.Base(tc), func(t *testing.T) { + + t.Parallel() + + // Unmarshal the OVF envelope from XML. + decodedFromXML := testEnvelope(t, tc) + + // Marshal the OVF envelope to JSON. + data, err := json.MarshalIndent(decodedFromXML, "", " ") + + if assert.NoError(t, err) { + + if ok, _ := strconv.ParseBool(os.Getenv("DEBUG")); ok { + t.Log(string(data)) + } + + // Unmarshal the OVF envelop from JSON. + var decodedFromJSON Envelope + if assert.NoError(t, json.Unmarshal(data, &decodedFromJSON)) { + + // Assert the OVF envelopes unmarshaled from XML and from + // JSON are equal. + if assert.True( + t, + cmp.Equal(*decodedFromXML, decodedFromJSON), + cmp.Diff(*decodedFromXML, decodedFromJSON)) { + + // Take the OVF envelope that was unmarshaled from + // JSON and marshal it *back* to XML. + data, err := xml.Marshal(decodedFromJSON) + if assert.NoError(t, err) { + + // Take the OVF envelope that was unmarshaled from + // JSON, then back to XML, and unmarshal it from XML. + var decodedFromXMLFromJSON Envelope + if assert.NoError( + t, + xml.Unmarshal(data, &decodedFromXMLFromJSON)) { + + // Assert this envelope is equal to the + // original. + assert.True( + t, + cmp.Equal(*decodedFromXML, decodedFromXMLFromJSON), + cmp.Diff(*decodedFromXML, decodedFromXMLFromJSON)) + } + } + } + } + } + }) + } +}