diff --git a/Gopkg.lock b/Gopkg.lock index ce1768c8675..ccbf164b476 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -860,12 +860,15 @@ [[projects]] branch = "master" - digest = "1:6985ae3d34c74a894dbafce8895c02d7913b26f2e502653a769215cfd1fdc9e3" + digest = "1:8318bee320a5bba99484209e22a947ebc98f9393c9d20272cf865c7ff546997d" name = "github.com/vmware/govmomi" packages = [ ".", + "find", + "list", "nfc", "object", + "ovf", "property", "session", "task", @@ -1520,7 +1523,10 @@ "github.com/stretchr/testify/assert", "github.com/vincent-petithory/dataurl", "github.com/vmware/govmomi", + "github.com/vmware/govmomi/find", + "github.com/vmware/govmomi/nfc", "github.com/vmware/govmomi/object", + "github.com/vmware/govmomi/ovf", "github.com/vmware/govmomi/property", "github.com/vmware/govmomi/vapi/rest", "github.com/vmware/govmomi/vapi/tags", diff --git a/pkg/asset/cluster/cluster.go b/pkg/asset/cluster/cluster.go index ab6acc0c699..671eaf33244 100644 --- a/pkg/asset/cluster/cluster.go +++ b/pkg/asset/cluster/cluster.go @@ -12,6 +12,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/cluster/aws" + "github.com/openshift/installer/pkg/asset/cluster/vsphere" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/password" "github.com/openshift/installer/pkg/terraform" @@ -77,6 +78,11 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) { return err } } + if installConfig.Config.Platform.VSphere != nil { + if err := vsphere.PreTerraform(context.TODO(), clusterID.InfraID, installConfig); err != nil { + return err + } + } stateFile, err := terraform.Apply(tmpDir, installConfig.Config.Platform.Name(), extraArgs...) if err != nil { diff --git a/pkg/asset/cluster/tfvars.go b/pkg/asset/cluster/tfvars.go index 65b94273b24..a72d35b1572 100644 --- a/pkg/asset/cluster/tfvars.go +++ b/pkg/asset/cluster/tfvars.go @@ -425,17 +425,22 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error { for i, c := range controlPlanes { controlPlaneConfigs[i] = c.Spec.ProviderSpec.Value.Object.(*vsphereprovider.VSphereMachineProviderSpec) } - data, err = vspheretfvars.TFVars( + data, cachedImage, err := vspheretfvars.TFVars( vspheretfvars.TFVarsSources{ ControlPlaneConfigs: controlPlaneConfigs, Username: installConfig.Config.VSphere.Username, Password: installConfig.Config.VSphere.Password, Cluster: installConfig.Config.VSphere.Cluster, + ImageURI: string(*rhcosImage), }, ) if err != nil { return errors.Wrapf(err, "failed to get %s Terraform variables", platform) } + + // TODO: is this wise to overwrite the install-config? + installConfig.Config.VSphere.ClusterOSImage = cachedImage + t.FileList = append(t.FileList, &asset.File{ Filename: fmt.Sprintf(TfPlatformVarsFileName, platform), Data: data, diff --git a/pkg/asset/cluster/vsphere/archive.go b/pkg/asset/cluster/vsphere/archive.go new file mode 100644 index 00000000000..680ad61e231 --- /dev/null +++ b/pkg/asset/cluster/vsphere/archive.go @@ -0,0 +1,203 @@ +/* +Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vsphere + +import ( + "archive/tar" + "bytes" + "context" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/vmware/govmomi/ovf" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/soap" +) + +// ArchiveFlag doesn't register any flags; +// only encapsulates some common archive related functionality. +type ArchiveFlag struct { + Archive +} + +func newArchiveFlag(ctx context.Context) (*ArchiveFlag, context.Context) { + return &ArchiveFlag{}, ctx +} + +// Register ArchiveFlag +func (f *ArchiveFlag) Register(ctx context.Context, fs *flag.FlagSet) { +} + +// Process ArchiveFlag +func (f *ArchiveFlag) Process(ctx context.Context) error { + return nil +} + +// ReadOvf - open and read OVF file +func (f *ArchiveFlag) ReadOvf(fpath string) ([]byte, error) { + r, _, err := f.Open(fpath) + if err != nil { + return nil, err + } + defer r.Close() + + return ioutil.ReadAll(r) +} + +// ReadEnvelope - open and read OVF envelope +func (f *ArchiveFlag) ReadEnvelope(data []byte) (*ovf.Envelope, error) { + e, err := ovf.Unmarshal(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("failed to parse ovf: %s", err) + } + + return e, nil +} + +// Archive interface +type Archive interface { + Open(string) (io.ReadCloser, int64, error) +} + +// TapeArchive struct +type TapeArchive struct { + Path string + Opener +} + +//TapeArchiveEntry struct +type TapeArchiveEntry struct { + io.Reader + f io.Closer + + Name string +} + +// Close the TapeArchiveEntry +func (t *TapeArchiveEntry) Close() error { + return t.f.Close() +} + +// Open the TapeArchive locally +func (t *TapeArchive) Open(name string) (io.ReadCloser, int64, error) { + f, _, err := t.OpenFile(t.Path) + if err != nil { + return nil, 0, err + } + + r := tar.NewReader(f) + + for { + h, err := r.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, 0, err + } + + matched, err := path.Match(name, path.Base(h.Name)) + if err != nil { + return nil, 0, err + } + + if matched { + return &TapeArchiveEntry{r, f, h.Name}, h.Size, nil + } + } + + _ = f.Close() + + return nil, 0, os.ErrNotExist +} + +// FileArchive contains the path and the opener, which can be either remotely vCenter +// or locally +type FileArchive struct { + Path string + Opener +} + +// Open the FileArchive +func (t *FileArchive) Open(name string) (io.ReadCloser, int64, error) { + fpath := name + if name != t.Path { + index := strings.LastIndex(t.Path, "/") + if index != -1 { + fpath = t.Path[:index] + "/" + name + } + } + + return t.OpenFile(fpath) +} + +// Opener - contains the soap client to vCenter +type Opener struct { + *vim25.Client +} + +func isRemotePath(path string) bool { + if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { + return true + } + return false +} + +// OpenLocal - open the OVF/OVA locally +func (o Opener) OpenLocal(path string) (io.ReadCloser, int64, error) { + f, err := os.Open(filepath.Clean(path)) + if err != nil { + return nil, 0, err + } + + s, err := f.Stat() + if err != nil { + return nil, 0, err + } + + return f, s.Size(), nil +} + +// OpenFile - determine which method to open the OVA/OVF +func (o Opener) OpenFile(path string) (io.ReadCloser, int64, error) { + if isRemotePath(path) { + return o.OpenRemote(path) + } + return o.OpenLocal(path) +} + +// OpenRemote - open in vSphere +func (o Opener) OpenRemote(link string) (io.ReadCloser, int64, error) { + if o.Client == nil { + return nil, 0, errors.New("remote path not supported") + } + + u, err := url.Parse(link) + if err != nil { + return nil, 0, err + } + + return o.Download(context.Background(), u, &soap.DefaultDownload) +} diff --git a/pkg/asset/cluster/vsphere/import.go b/pkg/asset/cluster/vsphere/import.go new file mode 100644 index 00000000000..57babdc180b --- /dev/null +++ b/pkg/asset/cluster/vsphere/import.go @@ -0,0 +1,278 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + //_ "github.com/sirupsen/logrus" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/nfc" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/ovf" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" + + "github.com/vmware/govmomi/vim25/soap" + + installertypes "github.com/openshift/installer/pkg/types" + vspheretypes "github.com/openshift/installer/pkg/types/vsphere" +) + +// ImportOvaParams contains the vCenter objects required to import a OVA into vSphere. +type ImportOvaParams struct { + ResourcePool *object.ResourcePool + Datacenter *object.Datacenter + Datastore *object.Datastore + Network *object.Network + Host *object.HostSystem + Folder *object.Folder +} + +func findImportOvaParams(client *vim25.Client, datacenter, cluster, datastore, network string) (*ImportOvaParams, error) { + var ccrMo mo.ClusterComputeResource + ctx := context.TODO() + importOvaParams := &ImportOvaParams{} + + finder := find.NewFinder(client) + + // Find the object Datacenter by using its name provided by install-config + dcObj, err := finder.Datacenter(ctx, datacenter) + if err != nil { + return nil, err + } + importOvaParams.Datacenter = dcObj + + // Find the top-level (and hidden to view) folders in the + // datacenter + folders, err := importOvaParams.Datacenter.Folders(ctx) + if err != nil { + return nil, err + } + // The only folder we are interested in is VmFolder + // Which can contain our template + importOvaParams.Folder = folders.VmFolder + + clusterPath := fmt.Sprintf("/%s/host/%s", datacenter, cluster) + + // Find the cluster object by the datacenter and cluster name to + // generate the path e.g. /datacenter/host/cluster + clusterComputeResource, err := finder.ClusterComputeResource(ctx, clusterPath) + if err != nil { + return nil, err + } + + // Get the network properties that is defined in ClusterComputeResource + // We need to know if the network name provided exists in the cluster that was + // also provided. + err = clusterComputeResource.Properties(context.TODO(), clusterComputeResource.Reference(), []string{"network"}, &ccrMo) + if err != nil { + return nil, err + } + + // Find the network object using the provided network name + for _, networkMoRef := range ccrMo.Network { + networkObj := object.NewNetwork(client, networkMoRef) + networkObjectName, err := networkObj.ObjectName(ctx) + if err != nil { + return nil, err + } + if network == networkObjectName { + importOvaParams.Network = networkObj + break + } + } + + // Find all the datastores that are configured under the cluster + datastores, err := clusterComputeResource.Datastores(ctx) + if err != nil { + return nil, err + } + + // Find the specific datastore by the name provided + for _, datastoreObj := range datastores { + datastoreObjName, err := datastoreObj.ObjectName(ctx) + if err != nil { + return nil, err + } + if datastore == datastoreObjName { + importOvaParams.Datastore = datastoreObj + break + } + } + + // Find all the HostSystem(s) under cluster + hosts, err := clusterComputeResource.Hosts(ctx) + if err != nil { + return nil, err + } + foundDatastore := false + foundNetwork := false + var hostSystemManagedObject mo.HostSystem + + // Confirm that the network and datastore that was provided is + // available for use on the HostSystem we will import the + // OVA to. + for _, hostObj := range hosts { + hostObj.Properties(ctx, hostObj.Reference(), []string{"network", "datastore"}, &hostSystemManagedObject) + + if err != nil { + return nil, err + } + for _, dsMoRef := range hostSystemManagedObject.Datastore { + + if importOvaParams.Datastore.Reference().Value == dsMoRef.Value { + foundDatastore = true + break + } + } + for _, nMoRef := range hostSystemManagedObject.Network { + if importOvaParams.Network.Reference().Value == nMoRef.Value { + foundNetwork = true + break + } + } + + if foundDatastore && foundNetwork { + importOvaParams.Host = hostObj + resourcePool, err := hostObj.ResourcePool(ctx) + if err != nil { + return nil, err + } + importOvaParams.ResourcePool = resourcePool + } + } + if !foundDatastore { + return nil, errors.Errorf("The hosts in the cluster do not have the datastore provided in install-config.yaml") + } + if !foundNetwork { + return nil, errors.Errorf("The hosts in the cluster do not have the network provided in install-config.yaml") + } + + return importOvaParams, nil +} + +func importOva(config *installertypes.InstallConfig, infraID string) error { + + virtualMachineName := infraID + "-rhcos" + ctx := context.TODO() + // Login to vCenter, rest is not used in this case + client, _, err := vspheretypes.CreateVSphereClients(ctx, + config.VSphere.VCenter, + config.VSphere.Username, + config.VSphere.Password) + + if err != nil { + return err + } + + importOvaParams, err := findImportOvaParams(client, + config.VSphere.Datacenter, + config.VSphere.Cluster, + config.VSphere.DefaultDatastore, + config.VSphere.Network) + + if err != nil { + return err + } + + ovaTapeArchive := &TapeArchive{Path: config.VSphere.ClusterOSImage} + ovaTapeArchive.Client = client + + archive := &ArchiveFlag{} + archive.Archive = ovaTapeArchive + + ovfDescriptor, err := archive.ReadOvf("desc.ovf") + if err != nil { + return err + } + + ovfEnvelope, err := archive.ReadEnvelope(ovfDescriptor) + if err != nil { + return errors.Errorf("failed to parse ovf: %s", err) + } + + // The RHCOS OVA only has one network defined by default + // The OVF envelope defines this. We need a 1:1 mapping + // between networks with the OVF and the host + if len(ovfEnvelope.Network.Networks) != 1 { + return errors.Errorf("Expected the OVA to only have a single network adapter") + } + // Create mapping between OVF and the network object + // found by Name + networkMappings := []types.OvfNetworkMapping{{ + Name: ovfEnvelope.Network.Networks[0].Name, + Network: importOvaParams.Network.Reference(), + }} + // This is a very minimal spec for importing + // an OVF. + cisp := types.OvfCreateImportSpecParams{ + EntityName: virtualMachineName, + NetworkMapping: networkMappings, + } + + m := ovf.NewManager(client) + spec, err := m.CreateImportSpec(ctx, + string(ovfDescriptor), + importOvaParams.ResourcePool.Reference(), + importOvaParams.Datastore.Reference(), + cisp) + + if err != nil { + return err + } + if spec.Error != nil { + return errors.New(spec.Error[0].LocalizedMessage) + } + + //Creates a new entity in this resource pool. + //See VMware vCenter API documentation: Managed Object - ResourcePool - ImportVApp + lease, err := importOvaParams.ResourcePool.ImportVApp(ctx, + spec.ImportSpec, + importOvaParams.Folder, + importOvaParams.Host) + + if err != nil { + return err + } + + info, err := lease.Wait(ctx, spec.FileItem) + if err != nil { + return err + } + + u := lease.StartUpdater(ctx, info) + defer u.Done() + + for _, i := range info.Items { + // upload the vmdk to which ever host that was first + // available with the required network and datastore. + err = upload(ctx, archive, lease, i) + if err != nil { + return err + } + } + err = lease.Complete(ctx) + if err != nil { + return err + } + + return nil +} + +func upload(ctx context.Context, archive *ArchiveFlag, lease *nfc.Lease, item nfc.FileItem) error { + file := item.Path + + f, size, err := archive.Open(file) + if err != nil { + return err + } + defer f.Close() + + opts := soap.Upload{ + ContentLength: size, + } + + return lease.Upload(ctx, item, f, opts) +} diff --git a/pkg/asset/cluster/vsphere/vsphere.go b/pkg/asset/cluster/vsphere/vsphere.go index e1d45fe2c5a..590b6048e4d 100644 --- a/pkg/asset/cluster/vsphere/vsphere.go +++ b/pkg/asset/cluster/vsphere/vsphere.go @@ -1,6 +1,9 @@ package vsphere import ( + "context" + + "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" "github.com/openshift/installer/pkg/types/vsphere" ) @@ -13,3 +16,9 @@ func Metadata(config *types.InstallConfig) *vsphere.Metadata { Password: config.VSphere.Password, } } + +// PreTerraform performs any infrastructure initialization which must +// happen before Terraform creates the remaining infrastructure. +func PreTerraform(ctx context.Context, infraID string, installConfig *installconfig.InstallConfig) error { + return importOva(installConfig.Config, infraID) +} diff --git a/pkg/asset/machines/master.go b/pkg/asset/machines/master.go index 346fa1aa62f..39ca5675d8c 100644 --- a/pkg/asset/machines/master.go +++ b/pkg/asset/machines/master.go @@ -315,7 +315,12 @@ func (m *Master) Generate(dependencies asset.Parents) error { mpool.Set(pool.Platform.VSphere) pool.Platform.VSphere = &mpool - machines, err = vsphere.Machines(clusterID.InfraID, ic, pool, string(*rhcosImage), "master", "master-user-data") + imageName, err := rhcosutils.GenerateVSphereImageName(string(*rhcosImage), clusterID.InfraID) + if err != nil { + return err + } + + machines, err = vsphere.Machines(clusterID.InfraID, ic, pool, imageName, "master", "master-user-data") if err != nil { return errors.Wrap(err, "failed to create master machine objects") } diff --git a/pkg/asset/machines/worker.go b/pkg/asset/machines/worker.go index 9a67ec006d4..c1d7752136a 100644 --- a/pkg/asset/machines/worker.go +++ b/pkg/asset/machines/worker.go @@ -312,7 +312,12 @@ func (w *Worker) Generate(dependencies asset.Parents) error { mpool.Set(pool.Platform.VSphere) pool.Platform.VSphere = &mpool - sets, err := vsphere.MachineSets(clusterID.InfraID, ic, &pool, string(*rhcosImage), "worker", "worker-user-data") + imageName, err := rhcosutils.GenerateVSphereImageName(string(*rhcosImage), clusterID.InfraID) + if err != nil { + return err + } + + sets, err := vsphere.MachineSets(clusterID.InfraID, ic, &pool, imageName, "worker", "worker-user-data") if err != nil { return errors.Wrap(err, "failed to create worker machine objects") } diff --git a/pkg/asset/rhcos/image.go b/pkg/asset/rhcos/image.go index 2e87d1cb1a8..72e0bd5d534 100644 --- a/pkg/asset/rhcos/image.go +++ b/pkg/asset/rhcos/image.go @@ -3,10 +3,11 @@ package rhcos import ( "context" - "github.com/openshift/installer/pkg/types/ovirt" "os" "time" + "github.com/openshift/installer/pkg/types/ovirt" + "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -95,8 +96,15 @@ func osImage(config *types.InstallConfig) (string, error) { // because this contains the necessary ironic config drive // ignition support, which isn't enabled in the UPI BM images osimage, err = rhcos.OpenStack(ctx, arch) - case none.Name, vsphere.Name: + case vsphere.Name: + // Check for RHCOS image URL override + if config.Platform.VSphere.ClusterOSImage != "" { + osimage = config.Platform.VSphere.ClusterOSImage + break + } + osimage, err = rhcos.VMware(ctx, arch) + case none.Name: default: return "", errors.New("invalid Platform") } diff --git a/pkg/rhcos/builds.go b/pkg/rhcos/builds.go index eaaf2a2558b..d2d423c82d8 100644 --- a/pkg/rhcos/builds.go +++ b/pkg/rhcos/builds.go @@ -41,6 +41,10 @@ type metadata struct { SHA256 string `json:"sha256"` UncompressedSHA256 string `json:"uncompressed-sha256"` } `json:"openstack"` + VMware struct { + Path string `json:"path"` + SHA256 string `json:"sha256"` + } `json:"vmware"` } `json:"images"` OSTreeVersion string `json:"ostree-version"` } diff --git a/pkg/rhcos/vmware.go b/pkg/rhcos/vmware.go new file mode 100644 index 00000000000..4f8d47ef935 --- /dev/null +++ b/pkg/rhcos/vmware.go @@ -0,0 +1,63 @@ +package rhcos + +import ( + "context" + "net/url" + + "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/types" +) + +// VMware fetches the URL of the Red Hat Enterprise Linux CoreOS release. +func VMware(ctx context.Context, arch types.Architecture) (string, error) { + meta, err := fetchRHCOSBuild(ctx, arch) + if err != nil { + return "", errors.Wrap(err, "failed to fetch RHCOS metadata") + } + + base, err := url.Parse(meta.BaseURI) + if err != nil { + return "", err + } + + image, err := url.Parse(meta.Images.VMware.Path) + if err != nil { + return "", err + } + + baseURL := base.ResolveReference(image).String() + + // TODO: Get Uncompressed SHA256 into rhcos.json + // Attach sha256 checksum to the URL. Always provide the + // uncompressed SHA256; the cache will take care of + // uncompressing before checksumming. + //baseURL += "?sha256=" + meta.Images.VMware.UncompressedSHA256 + + // Check that we have generated a valid URL + _, err = url.ParseRequestURI(baseURL) + if err != nil { + return "", err + } + + return baseURL, nil +} + +// GenerateVSphereImageName returns Glance image name for instances. +func GenerateVSphereImageName(rhcosImage, infraID string) (string, error) { + + //TODO: Determine how to use Base Path as imageName + /* + rhcosImageURL, err := url.ParseRequestURI(rhcosImage) + if err != nil { + return "", errors.Errorf("expected RHCOS image to be a URL") + } + fileName := path.Base(rhcosImageURL.Path) + fileNameNoExt := strings.TrimSuffix(fileName, path.Ext(fileName)) + imageName := fmt.Sprintf("%s-%s", infraID, fileNameNoExt) + + return imageName, nil + */ + + return infraID + "-rhcos", nil +} diff --git a/pkg/tfvars/vsphere/vsphere.go b/pkg/tfvars/vsphere/vsphere.go index 9a152418269..b146ab01bbe 100644 --- a/pkg/tfvars/vsphere/vsphere.go +++ b/pkg/tfvars/vsphere/vsphere.go @@ -3,6 +3,9 @@ package vsphere import ( "encoding/json" + "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/tfvars/internal/cache" vsphereapis "github.com/openshift/machine-api-operator/pkg/apis/vsphereprovider/v1alpha1" ) @@ -28,12 +31,18 @@ type TFVarsSources struct { Username string Password string Cluster string + ImageURI string } //TFVars generate vSphere-specific Terraform variables -func TFVars(sources TFVarsSources) ([]byte, error) { +func TFVars(sources TFVarsSources) ([]byte, string, error) { controlPlaneConfig := sources.ControlPlaneConfigs[0] + cachedImage, err := cache.DownloadImageFile(sources.ImageURI) + if err != nil { + return nil, "", errors.Wrap(err, "failed to use cached vsphere image") + } + cfg := &config{ VSphereURL: controlPlaneConfig.Workspace.Server, VSphereUsername: sources.Username, @@ -50,5 +59,10 @@ func TFVars(sources TFVarsSources) ([]byte, error) { Template: controlPlaneConfig.Template, } - return json.MarshalIndent(cfg, "", " ") + cfgFormatted, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return nil, "", err + } + + return cfgFormatted, cachedImage, nil } diff --git a/vendor/github.com/vmware/govmomi/find/doc.go b/vendor/github.com/vmware/govmomi/find/doc.go new file mode 100644 index 00000000000..0c8acee0163 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/find/doc.go @@ -0,0 +1,37 @@ +/* +Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package find implements inventory listing and searching. + +The Finder is an alternative to the object.SearchIndex FindByInventoryPath() and FindChild() methods. +SearchIndex.FindByInventoryPath requires an absolute path, whereas the Finder also supports relative paths +and patterns via path.Match. +SearchIndex.FindChild requires a parent to find the child, whereas the Finder also supports an ancestor via +recursive object traversal. + +The various Finder methods accept a "path" argument, which can absolute or relative to the Folder for the object type. +The Finder supports two modes, "list" and "find". The "list" mode behaves like the "ls" command, only searching within +the immediate path. The "find" mode behaves like the "find" command, with the search starting at the immediate path but +also recursing into sub Folders relative to the Datacenter. The default mode is "list" if the given path contains a "/", +otherwise "find" mode is used. + +The exception is to use a "..." wildcard with a path to find all objects recursively underneath any root object. +For example: VirtualMachineList("/DC1/...") + +See also: https://github.com/vmware/govmomi/blob/master/govc/README.md#usage +*/ +package find diff --git a/vendor/github.com/vmware/govmomi/find/error.go b/vendor/github.com/vmware/govmomi/find/error.go new file mode 100644 index 00000000000..684526dab76 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/find/error.go @@ -0,0 +1,64 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package find + +import "fmt" + +type NotFoundError struct { + kind string + path string +} + +func (e *NotFoundError) Error() string { + return fmt.Sprintf("%s '%s' not found", e.kind, e.path) +} + +type MultipleFoundError struct { + kind string + path string +} + +func (e *MultipleFoundError) Error() string { + return fmt.Sprintf("path '%s' resolves to multiple %ss", e.path, e.kind) +} + +type DefaultNotFoundError struct { + kind string +} + +func (e *DefaultNotFoundError) Error() string { + return fmt.Sprintf("no default %s found", e.kind) +} + +type DefaultMultipleFoundError struct { + kind string +} + +func (e DefaultMultipleFoundError) Error() string { + return fmt.Sprintf("default %s resolves to multiple instances, please specify", e.kind) +} + +func toDefaultError(err error) error { + switch e := err.(type) { + case *NotFoundError: + return &DefaultNotFoundError{e.kind} + case *MultipleFoundError: + return &DefaultMultipleFoundError{e.kind} + default: + return err + } +} diff --git a/vendor/github.com/vmware/govmomi/find/finder.go b/vendor/github.com/vmware/govmomi/find/finder.go new file mode 100644 index 00000000000..a46c70bf57f --- /dev/null +++ b/vendor/github.com/vmware/govmomi/find/finder.go @@ -0,0 +1,1058 @@ +/* +Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package find + +import ( + "context" + "errors" + "path" + "strings" + + "github.com/vmware/govmomi/list" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type Finder struct { + client *vim25.Client + r recurser + dc *object.Datacenter + si *object.SearchIndex + folders *object.DatacenterFolders +} + +func NewFinder(client *vim25.Client, all ...bool) *Finder { + props := false + if len(all) == 1 { + props = all[0] + } + + f := &Finder{ + client: client, + si: object.NewSearchIndex(client), + r: recurser{ + Collector: property.DefaultCollector(client), + All: props, + }, + } + + if len(all) == 0 { + // attempt to avoid SetDatacenter() requirement + f.dc, _ = f.DefaultDatacenter(context.Background()) + } + + return f +} + +func (f *Finder) SetDatacenter(dc *object.Datacenter) *Finder { + f.dc = dc + f.folders = nil + return f +} + +// findRoot makes it possible to use "find" mode with a different root path. +// Example: ResourcePoolList("/dc1/host/cluster1/...") +func (f *Finder) findRoot(ctx context.Context, root *list.Element, parts []string) bool { + if len(parts) == 0 { + return false + } + + ix := len(parts) - 1 + + if parts[ix] != "..." { + return false + } + + if ix == 0 { + return true // We already have the Object for root.Path + } + + // Lookup the Object for the new root.Path + rootPath := path.Join(root.Path, path.Join(parts[:ix]...)) + + ref, err := f.si.FindByInventoryPath(ctx, rootPath) + if err != nil || ref == nil { + // If we get an error or fail to match, fall through to find() with the original root and path + return false + } + + root.Path = rootPath + root.Object = ref + + return true +} + +func (f *Finder) find(ctx context.Context, arg string, s *spec) ([]list.Element, error) { + isPath := strings.Contains(arg, "/") + + root := list.Element{ + Path: "/", + Object: object.NewRootFolder(f.client), + } + + parts := list.ToParts(arg) + + if len(parts) > 0 { + switch parts[0] { + case "..": // Not supported; many edge case, little value + return nil, errors.New("cannot traverse up a tree") + case ".": // Relative to whatever + pivot, err := s.Relative(ctx) + if err != nil { + return nil, err + } + + mes, err := mo.Ancestors(ctx, f.client, f.client.ServiceContent.PropertyCollector, pivot.Reference()) + if err != nil { + return nil, err + } + + for _, me := range mes { + // Skip root entity in building inventory path. + if me.Parent == nil { + continue + } + root.Path = path.Join(root.Path, me.Name) + } + + root.Object = pivot + parts = parts[1:] + } + } + + if s.listMode(isPath) { + if f.findRoot(ctx, &root, parts) { + parts = []string{"*"} + } else { + return f.r.List(ctx, s, root, parts) + } + } + + s.Parents = append(s.Parents, s.Nested...) + + return f.r.Find(ctx, s, root, parts) +} + +func (f *Finder) datacenter() (*object.Datacenter, error) { + if f.dc == nil { + return nil, errors.New("please specify a datacenter") + } + + return f.dc, nil +} + +// datacenterPath returns the absolute path to the Datacenter containing the given ref +func (f *Finder) datacenterPath(ctx context.Context, ref types.ManagedObjectReference) (string, error) { + mes, err := mo.Ancestors(ctx, f.client, f.client.ServiceContent.PropertyCollector, ref) + if err != nil { + return "", err + } + + // Chop leaves under the Datacenter + for i := len(mes) - 1; i > 0; i-- { + if mes[i].Self.Type == "Datacenter" { + break + } + mes = mes[:i] + } + + var p string + + for _, me := range mes { + // Skip root entity in building inventory path. + if me.Parent == nil { + continue + } + + p = p + "/" + me.Name + } + + return p, nil +} + +func (f *Finder) dcFolders(ctx context.Context) (*object.DatacenterFolders, error) { + if f.folders != nil { + return f.folders, nil + } + + dc, err := f.datacenter() + if err != nil { + return nil, err + } + + folders, err := dc.Folders(ctx) + if err != nil { + return nil, err + } + + f.folders = folders + + return f.folders, nil +} + +func (f *Finder) dcReference(_ context.Context) (object.Reference, error) { + dc, err := f.datacenter() + if err != nil { + return nil, err + } + + return dc, nil +} + +func (f *Finder) vmFolder(ctx context.Context) (object.Reference, error) { + folders, err := f.dcFolders(ctx) + if err != nil { + return nil, err + } + + return folders.VmFolder, nil +} + +func (f *Finder) hostFolder(ctx context.Context) (object.Reference, error) { + folders, err := f.dcFolders(ctx) + if err != nil { + return nil, err + } + + return folders.HostFolder, nil +} + +func (f *Finder) datastoreFolder(ctx context.Context) (object.Reference, error) { + folders, err := f.dcFolders(ctx) + if err != nil { + return nil, err + } + + return folders.DatastoreFolder, nil +} + +func (f *Finder) networkFolder(ctx context.Context) (object.Reference, error) { + folders, err := f.dcFolders(ctx) + if err != nil { + return nil, err + } + + return folders.NetworkFolder, nil +} + +func (f *Finder) rootFolder(_ context.Context) (object.Reference, error) { + return object.NewRootFolder(f.client), nil +} + +func (f *Finder) managedObjectList(ctx context.Context, path string, tl bool, include []string) ([]list.Element, error) { + fn := f.rootFolder + + if f.dc != nil { + fn = f.dcReference + } + + if path == "" { + path = "." + } + + s := &spec{ + Relative: fn, + Parents: []string{"ComputeResource", "ClusterComputeResource", "HostSystem", "VirtualApp", "StoragePod"}, + Include: include, + } + + if tl { + s.Contents = true + s.ListMode = types.NewBool(true) + } + + return f.find(ctx, path, s) +} + +// Element returns an Element for the given ManagedObjectReference +// This method is only useful for looking up the InventoryPath of a ManagedObjectReference. +func (f *Finder) Element(ctx context.Context, ref types.ManagedObjectReference) (*list.Element, error) { + rl := func(_ context.Context) (object.Reference, error) { + return ref, nil + } + + s := &spec{ + Relative: rl, + } + + e, err := f.find(ctx, "./", s) + if err != nil { + return nil, err + } + + if len(e) == 0 { + return nil, &NotFoundError{ref.Type, ref.Value} + } + + if len(e) > 1 { + panic("ManagedObjectReference must be unique") + } + + return &e[0], nil +} + +// ObjectReference converts the given ManagedObjectReference to a type from the object package via object.NewReference +// with the object.Common.InventoryPath field set. +func (f *Finder) ObjectReference(ctx context.Context, ref types.ManagedObjectReference) (object.Reference, error) { + e, err := f.Element(ctx, ref) + if err != nil { + return nil, err + } + + r := object.NewReference(f.client, ref) + + type common interface { + SetInventoryPath(string) + } + + r.(common).SetInventoryPath(e.Path) + + if f.dc != nil { + if ds, ok := r.(*object.Datastore); ok { + ds.DatacenterPath = f.dc.InventoryPath + } + } + + return r, nil +} + +func (f *Finder) ManagedObjectList(ctx context.Context, path string, include ...string) ([]list.Element, error) { + return f.managedObjectList(ctx, path, false, include) +} + +func (f *Finder) ManagedObjectListChildren(ctx context.Context, path string, include ...string) ([]list.Element, error) { + return f.managedObjectList(ctx, path, true, include) +} + +func (f *Finder) DatacenterList(ctx context.Context, path string) ([]*object.Datacenter, error) { + s := &spec{ + Relative: f.rootFolder, + Include: []string{"Datacenter"}, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var dcs []*object.Datacenter + for _, e := range es { + ref := e.Object.Reference() + if ref.Type == "Datacenter" { + dc := object.NewDatacenter(f.client, ref) + dc.InventoryPath = e.Path + dcs = append(dcs, dc) + } + } + + if len(dcs) == 0 { + return nil, &NotFoundError{"datacenter", path} + } + + return dcs, nil +} + +func (f *Finder) Datacenter(ctx context.Context, path string) (*object.Datacenter, error) { + dcs, err := f.DatacenterList(ctx, path) + if err != nil { + return nil, err + } + + if len(dcs) > 1 { + return nil, &MultipleFoundError{"datacenter", path} + } + + return dcs[0], nil +} + +func (f *Finder) DefaultDatacenter(ctx context.Context) (*object.Datacenter, error) { + dc, err := f.Datacenter(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return dc, nil +} + +func (f *Finder) DatacenterOrDefault(ctx context.Context, path string) (*object.Datacenter, error) { + if path != "" { + dc, err := f.Datacenter(ctx, path) + if err != nil { + return nil, err + } + return dc, nil + } + + return f.DefaultDatacenter(ctx) +} + +func (f *Finder) DatastoreList(ctx context.Context, path string) ([]*object.Datastore, error) { + s := &spec{ + Relative: f.datastoreFolder, + Parents: []string{"StoragePod"}, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var dss []*object.Datastore + for _, e := range es { + ref := e.Object.Reference() + if ref.Type == "Datastore" { + ds := object.NewDatastore(f.client, ref) + ds.InventoryPath = e.Path + + if f.dc == nil { + // In this case SetDatacenter was not called and path is absolute + ds.DatacenterPath, err = f.datacenterPath(ctx, ref) + if err != nil { + return nil, err + } + } else { + ds.DatacenterPath = f.dc.InventoryPath + } + + dss = append(dss, ds) + } + } + + if len(dss) == 0 { + return nil, &NotFoundError{"datastore", path} + } + + return dss, nil +} + +func (f *Finder) Datastore(ctx context.Context, path string) (*object.Datastore, error) { + dss, err := f.DatastoreList(ctx, path) + if err != nil { + return nil, err + } + + if len(dss) > 1 { + return nil, &MultipleFoundError{"datastore", path} + } + + return dss[0], nil +} + +func (f *Finder) DefaultDatastore(ctx context.Context) (*object.Datastore, error) { + ds, err := f.Datastore(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return ds, nil +} + +func (f *Finder) DatastoreOrDefault(ctx context.Context, path string) (*object.Datastore, error) { + if path != "" { + ds, err := f.Datastore(ctx, path) + if err != nil { + return nil, err + } + return ds, nil + } + + return f.DefaultDatastore(ctx) +} + +func (f *Finder) DatastoreClusterList(ctx context.Context, path string) ([]*object.StoragePod, error) { + s := &spec{ + Relative: f.datastoreFolder, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var sps []*object.StoragePod + for _, e := range es { + ref := e.Object.Reference() + if ref.Type == "StoragePod" { + sp := object.NewStoragePod(f.client, ref) + sp.InventoryPath = e.Path + sps = append(sps, sp) + } + } + + if len(sps) == 0 { + return nil, &NotFoundError{"datastore cluster", path} + } + + return sps, nil +} + +func (f *Finder) DatastoreCluster(ctx context.Context, path string) (*object.StoragePod, error) { + sps, err := f.DatastoreClusterList(ctx, path) + if err != nil { + return nil, err + } + + if len(sps) > 1 { + return nil, &MultipleFoundError{"datastore cluster", path} + } + + return sps[0], nil +} + +func (f *Finder) DefaultDatastoreCluster(ctx context.Context) (*object.StoragePod, error) { + sp, err := f.DatastoreCluster(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return sp, nil +} + +func (f *Finder) DatastoreClusterOrDefault(ctx context.Context, path string) (*object.StoragePod, error) { + if path != "" { + sp, err := f.DatastoreCluster(ctx, path) + if err != nil { + return nil, err + } + return sp, nil + } + + return f.DefaultDatastoreCluster(ctx) +} + +func (f *Finder) ComputeResourceList(ctx context.Context, path string) ([]*object.ComputeResource, error) { + s := &spec{ + Relative: f.hostFolder, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var crs []*object.ComputeResource + for _, e := range es { + var cr *object.ComputeResource + + switch o := e.Object.(type) { + case mo.ComputeResource, mo.ClusterComputeResource: + cr = object.NewComputeResource(f.client, o.Reference()) + default: + continue + } + + cr.InventoryPath = e.Path + crs = append(crs, cr) + } + + if len(crs) == 0 { + return nil, &NotFoundError{"compute resource", path} + } + + return crs, nil +} + +func (f *Finder) ComputeResource(ctx context.Context, path string) (*object.ComputeResource, error) { + crs, err := f.ComputeResourceList(ctx, path) + if err != nil { + return nil, err + } + + if len(crs) > 1 { + return nil, &MultipleFoundError{"compute resource", path} + } + + return crs[0], nil +} + +func (f *Finder) DefaultComputeResource(ctx context.Context) (*object.ComputeResource, error) { + cr, err := f.ComputeResource(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return cr, nil +} + +func (f *Finder) ComputeResourceOrDefault(ctx context.Context, path string) (*object.ComputeResource, error) { + if path != "" { + cr, err := f.ComputeResource(ctx, path) + if err != nil { + return nil, err + } + return cr, nil + } + + return f.DefaultComputeResource(ctx) +} + +func (f *Finder) ClusterComputeResourceList(ctx context.Context, path string) ([]*object.ClusterComputeResource, error) { + s := &spec{ + Relative: f.hostFolder, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var ccrs []*object.ClusterComputeResource + for _, e := range es { + var ccr *object.ClusterComputeResource + + switch o := e.Object.(type) { + case mo.ClusterComputeResource: + ccr = object.NewClusterComputeResource(f.client, o.Reference()) + default: + continue + } + + ccr.InventoryPath = e.Path + ccrs = append(ccrs, ccr) + } + + if len(ccrs) == 0 { + return nil, &NotFoundError{"cluster", path} + } + + return ccrs, nil +} + +func (f *Finder) DefaultClusterComputeResource(ctx context.Context) (*object.ClusterComputeResource, error) { + cr, err := f.ClusterComputeResource(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return cr, nil +} + +func (f *Finder) ClusterComputeResource(ctx context.Context, path string) (*object.ClusterComputeResource, error) { + ccrs, err := f.ClusterComputeResourceList(ctx, path) + if err != nil { + return nil, err + } + + if len(ccrs) > 1 { + return nil, &MultipleFoundError{"cluster", path} + } + + return ccrs[0], nil +} + +func (f *Finder) ClusterComputeResourceOrDefault(ctx context.Context, path string) (*object.ClusterComputeResource, error) { + if path != "" { + cr, err := f.ClusterComputeResource(ctx, path) + if err != nil { + return nil, err + } + return cr, nil + } + + return f.DefaultClusterComputeResource(ctx) +} + +func (f *Finder) HostSystemList(ctx context.Context, path string) ([]*object.HostSystem, error) { + s := &spec{ + Relative: f.hostFolder, + Parents: []string{"ComputeResource", "ClusterComputeResource"}, + Include: []string{"HostSystem"}, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var hss []*object.HostSystem + for _, e := range es { + var hs *object.HostSystem + + switch o := e.Object.(type) { + case mo.HostSystem: + hs = object.NewHostSystem(f.client, o.Reference()) + + hs.InventoryPath = e.Path + hss = append(hss, hs) + case mo.ComputeResource, mo.ClusterComputeResource: + cr := object.NewComputeResource(f.client, o.Reference()) + + cr.InventoryPath = e.Path + + hosts, err := cr.Hosts(ctx) + if err != nil { + return nil, err + } + + hss = append(hss, hosts...) + } + } + + if len(hss) == 0 { + return nil, &NotFoundError{"host", path} + } + + return hss, nil +} + +func (f *Finder) HostSystem(ctx context.Context, path string) (*object.HostSystem, error) { + hss, err := f.HostSystemList(ctx, path) + if err != nil { + return nil, err + } + + if len(hss) > 1 { + return nil, &MultipleFoundError{"host", path} + } + + return hss[0], nil +} + +func (f *Finder) DefaultHostSystem(ctx context.Context) (*object.HostSystem, error) { + hs, err := f.HostSystem(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return hs, nil +} + +func (f *Finder) HostSystemOrDefault(ctx context.Context, path string) (*object.HostSystem, error) { + if path != "" { + hs, err := f.HostSystem(ctx, path) + if err != nil { + return nil, err + } + return hs, nil + } + + return f.DefaultHostSystem(ctx) +} + +func (f *Finder) NetworkList(ctx context.Context, path string) ([]object.NetworkReference, error) { + s := &spec{ + Relative: f.networkFolder, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var ns []object.NetworkReference + for _, e := range es { + ref := e.Object.Reference() + switch ref.Type { + case "Network": + r := object.NewNetwork(f.client, ref) + r.InventoryPath = e.Path + ns = append(ns, r) + case "OpaqueNetwork": + r := object.NewOpaqueNetwork(f.client, ref) + r.InventoryPath = e.Path + ns = append(ns, r) + case "DistributedVirtualPortgroup": + r := object.NewDistributedVirtualPortgroup(f.client, ref) + r.InventoryPath = e.Path + ns = append(ns, r) + case "DistributedVirtualSwitch", "VmwareDistributedVirtualSwitch": + r := object.NewDistributedVirtualSwitch(f.client, ref) + r.InventoryPath = e.Path + ns = append(ns, r) + } + } + + if len(ns) == 0 { + return nil, &NotFoundError{"network", path} + } + + return ns, nil +} + +func (f *Finder) Network(ctx context.Context, path string) (object.NetworkReference, error) { + networks, err := f.NetworkList(ctx, path) + if err != nil { + return nil, err + } + + if len(networks) > 1 { + return nil, &MultipleFoundError{"network", path} + } + + return networks[0], nil +} + +func (f *Finder) DefaultNetwork(ctx context.Context) (object.NetworkReference, error) { + network, err := f.Network(ctx, "*") + if err != nil { + return nil, toDefaultError(err) + } + + return network, nil +} + +func (f *Finder) NetworkOrDefault(ctx context.Context, path string) (object.NetworkReference, error) { + if path != "" { + network, err := f.Network(ctx, path) + if err != nil { + return nil, err + } + return network, nil + } + + return f.DefaultNetwork(ctx) +} + +func (f *Finder) ResourcePoolList(ctx context.Context, path string) ([]*object.ResourcePool, error) { + s := &spec{ + Relative: f.hostFolder, + Parents: []string{"ComputeResource", "ClusterComputeResource", "VirtualApp"}, + Nested: []string{"ResourcePool"}, + Contents: true, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var rps []*object.ResourcePool + for _, e := range es { + var rp *object.ResourcePool + + switch o := e.Object.(type) { + case mo.ResourcePool: + rp = object.NewResourcePool(f.client, o.Reference()) + rp.InventoryPath = e.Path + rps = append(rps, rp) + } + } + + if len(rps) == 0 { + return nil, &NotFoundError{"resource pool", path} + } + + return rps, nil +} + +func (f *Finder) ResourcePool(ctx context.Context, path string) (*object.ResourcePool, error) { + rps, err := f.ResourcePoolList(ctx, path) + if err != nil { + return nil, err + } + + if len(rps) > 1 { + return nil, &MultipleFoundError{"resource pool", path} + } + + return rps[0], nil +} + +func (f *Finder) DefaultResourcePool(ctx context.Context) (*object.ResourcePool, error) { + rp, err := f.ResourcePool(ctx, "*/Resources") + if err != nil { + return nil, toDefaultError(err) + } + + return rp, nil +} + +func (f *Finder) ResourcePoolOrDefault(ctx context.Context, path string) (*object.ResourcePool, error) { + if path != "" { + rp, err := f.ResourcePool(ctx, path) + if err != nil { + return nil, err + } + return rp, nil + } + + return f.DefaultResourcePool(ctx) +} + +// ResourcePoolListAll combines ResourcePoolList and VirtualAppList +// VirtualAppList is only called if ResourcePoolList does not find any pools with the given path. +func (f *Finder) ResourcePoolListAll(ctx context.Context, path string) ([]*object.ResourcePool, error) { + pools, err := f.ResourcePoolList(ctx, path) + if err != nil { + if _, ok := err.(*NotFoundError); !ok { + return nil, err + } + + vapps, _ := f.VirtualAppList(ctx, path) + + if len(vapps) == 0 { + return nil, err + } + + for _, vapp := range vapps { + pools = append(pools, vapp.ResourcePool) + } + } + + return pools, nil +} + +func (f *Finder) DefaultFolder(ctx context.Context) (*object.Folder, error) { + ref, err := f.vmFolder(ctx) + if err != nil { + return nil, toDefaultError(err) + } + folder := object.NewFolder(f.client, ref.Reference()) + + // Set the InventoryPath of the newly created folder object + // The default foler becomes the datacenter's "vm" folder. + // The "vm" folder always exists for a datacenter. It cannot be + // removed or replaced + folder.SetInventoryPath(path.Join(f.dc.InventoryPath, "vm")) + + return folder, nil +} + +func (f *Finder) FolderOrDefault(ctx context.Context, path string) (*object.Folder, error) { + if path != "" { + folder, err := f.Folder(ctx, path) + if err != nil { + return nil, err + } + return folder, nil + } + return f.DefaultFolder(ctx) +} + +func (f *Finder) VirtualMachineList(ctx context.Context, path string) ([]*object.VirtualMachine, error) { + s := &spec{ + Relative: f.vmFolder, + Parents: []string{"VirtualApp"}, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var vms []*object.VirtualMachine + for _, e := range es { + switch o := e.Object.(type) { + case mo.VirtualMachine: + vm := object.NewVirtualMachine(f.client, o.Reference()) + vm.InventoryPath = e.Path + vms = append(vms, vm) + } + } + + if len(vms) == 0 { + return nil, &NotFoundError{"vm", path} + } + + return vms, nil +} + +func (f *Finder) VirtualMachine(ctx context.Context, path string) (*object.VirtualMachine, error) { + vms, err := f.VirtualMachineList(ctx, path) + if err != nil { + return nil, err + } + + if len(vms) > 1 { + return nil, &MultipleFoundError{"vm", path} + } + + return vms[0], nil +} + +func (f *Finder) VirtualAppList(ctx context.Context, path string) ([]*object.VirtualApp, error) { + s := &spec{ + Relative: f.vmFolder, + } + + es, err := f.find(ctx, path, s) + if err != nil { + return nil, err + } + + var apps []*object.VirtualApp + for _, e := range es { + switch o := e.Object.(type) { + case mo.VirtualApp: + app := object.NewVirtualApp(f.client, o.Reference()) + app.InventoryPath = e.Path + apps = append(apps, app) + } + } + + if len(apps) == 0 { + return nil, &NotFoundError{"app", path} + } + + return apps, nil +} + +func (f *Finder) VirtualApp(ctx context.Context, path string) (*object.VirtualApp, error) { + apps, err := f.VirtualAppList(ctx, path) + if err != nil { + return nil, err + } + + if len(apps) > 1 { + return nil, &MultipleFoundError{"app", path} + } + + return apps[0], nil +} + +func (f *Finder) FolderList(ctx context.Context, path string) ([]*object.Folder, error) { + es, err := f.ManagedObjectList(ctx, path) + if err != nil { + return nil, err + } + + var folders []*object.Folder + + for _, e := range es { + switch o := e.Object.(type) { + case mo.Folder, mo.StoragePod: + folder := object.NewFolder(f.client, o.Reference()) + folder.InventoryPath = e.Path + folders = append(folders, folder) + case *object.Folder: + // RootFolder + folders = append(folders, o) + } + } + + if len(folders) == 0 { + return nil, &NotFoundError{"folder", path} + } + + return folders, nil +} + +func (f *Finder) Folder(ctx context.Context, path string) (*object.Folder, error) { + folders, err := f.FolderList(ctx, path) + if err != nil { + return nil, err + } + + if len(folders) > 1 { + return nil, &MultipleFoundError{"folder", path} + } + + return folders[0], nil +} diff --git a/vendor/github.com/vmware/govmomi/find/recurser.go b/vendor/github.com/vmware/govmomi/find/recurser.go new file mode 100644 index 00000000000..80d958a264f --- /dev/null +++ b/vendor/github.com/vmware/govmomi/find/recurser.go @@ -0,0 +1,253 @@ +/* +Copyright (c) 2014-2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package find + +import ( + "context" + "os" + "path" + "strings" + + "github.com/vmware/govmomi/list" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25/mo" +) + +// spec is used to specify per-search configuration, independent of the Finder instance. +type spec struct { + // Relative returns the root object to resolve Relative paths (starts with ".") + Relative func(ctx context.Context) (object.Reference, error) + + // ListMode can be used to optionally force "ls" behavior, rather than "find" behavior + ListMode *bool + + // Contents configures the Recurser to list the Contents of traversable leaf nodes. + // This is typically set to true when used from the ls command, where listing + // a folder means listing its Contents. This is typically set to false for + // commands that take managed entities that are not folders as input. + Contents bool + + // Parents specifies the types which can contain the child types being searched for. + // for example, when searching for a HostSystem, parent types can be + // "ComputeResource" or "ClusterComputeResource". + Parents []string + + // Include specifies which types to be included in the results, used only in "find" mode. + Include []string + + // Nested should be set to types that can be Nested, used only in "find" mode. + Nested []string + + // ChildType avoids traversing into folders that can't contain the Include types, used only in "find" mode. + ChildType []string +} + +func (s *spec) traversable(o mo.Reference) bool { + ref := o.Reference() + + switch ref.Type { + case "Datacenter": + if len(s.Include) == 1 && s.Include[0] == "Datacenter" { + // No point in traversing deeper as Datacenters cannot be nested + return false + } + return true + case "Folder": + if f, ok := o.(mo.Folder); ok { + // TODO: Not making use of this yet, but here we can optimize when searching the entire + // inventory across Datacenters for specific types, for example: 'govc ls -t VirtualMachine /**' + // should not traverse into a Datacenter's host, network or datatore folders. + if !s.traversableChildType(f.ChildType) { + return false + } + } + + return true + } + + for _, kind := range s.Parents { + if kind == ref.Type { + return true + } + } + + return false +} + +func (s *spec) traversableChildType(ctypes []string) bool { + if len(s.ChildType) == 0 { + return true + } + + for _, t := range ctypes { + for _, c := range s.ChildType { + if t == c { + return true + } + } + } + + return false +} + +func (s *spec) wanted(e list.Element) bool { + if len(s.Include) == 0 { + return true + } + + w := e.Object.Reference().Type + + for _, kind := range s.Include { + if w == kind { + return true + } + } + + return false +} + +// listMode is a global option to revert to the original Finder behavior, +// disabling the newer "find" mode. +var listMode = os.Getenv("GOVMOMI_FINDER_LIST_MODE") == "true" + +func (s *spec) listMode(isPath bool) bool { + if listMode { + return true + } + + if s.ListMode != nil { + return *s.ListMode + } + + return isPath +} + +type recurser struct { + Collector *property.Collector + + // All configures the recurses to fetch complete objects for leaf nodes. + All bool +} + +func (r recurser) List(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) { + if len(parts) == 0 { + // Include non-traversable leaf elements in result. For example, consider + // the pattern "./vm/my-vm-*", where the pattern should match the VMs and + // not try to traverse them. + // + // Include traversable leaf elements in result, if the contents + // field is set to false. + // + if !s.Contents || !s.traversable(root.Object.Reference()) { + return []list.Element{root}, nil + } + } + + k := list.Lister{ + Collector: r.Collector, + Reference: root.Object.Reference(), + Prefix: root.Path, + } + + if r.All && len(parts) < 2 { + k.All = true + } + + in, err := k.List(ctx) + if err != nil { + return nil, err + } + + // This folder is a leaf as far as the glob goes. + if len(parts) == 0 { + return in, nil + } + + all := parts + pattern := parts[0] + parts = parts[1:] + + var out []list.Element + for _, e := range in { + matched, err := path.Match(pattern, path.Base(e.Path)) + if err != nil { + return nil, err + } + + if !matched { + matched = strings.HasSuffix(e.Path, "/"+path.Join(all...)) + if matched { + // name contains a '/' + out = append(out, e) + } + + continue + } + + nres, err := r.List(ctx, s, e, parts) + if err != nil { + return nil, err + } + + out = append(out, nres...) + } + + return out, nil +} + +func (r recurser) Find(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) { + var out []list.Element + + if len(parts) > 0 { + pattern := parts[0] + matched, err := path.Match(pattern, path.Base(root.Path)) + if err != nil { + return nil, err + } + + if matched && s.wanted(root) { + out = append(out, root) + } + } + + if !s.traversable(root.Object) { + return out, nil + } + + k := list.Lister{ + Collector: r.Collector, + Reference: root.Object.Reference(), + Prefix: root.Path, + } + + in, err := k.List(ctx) + if err != nil { + return nil, err + } + + for _, e := range in { + nres, err := r.Find(ctx, s, e, parts) + if err != nil { + return nil, err + } + + out = append(out, nres...) + } + + return out, nil +} diff --git a/vendor/github.com/vmware/govmomi/list/lister.go b/vendor/github.com/vmware/govmomi/list/lister.go new file mode 100644 index 00000000000..2ee32e6bc17 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/list/lister.go @@ -0,0 +1,563 @@ +/* +Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "context" + "fmt" + "path" + "reflect" + + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" + "github.com/vmware/govmomi/vim25/types" +) + +type Element struct { + Path string + Object mo.Reference +} + +func (e Element) String() string { + return fmt.Sprintf("%s @ %s", e.Object.Reference(), e.Path) +} + +func ToElement(r mo.Reference, prefix string) Element { + var name string + + // Comments about types to be expected in folders copied from the + // documentation of the Folder managed object: + // http://pubs.vmware.com/vsphere-55/topic/com.vmware.wssdk.apiref.doc/vim.Folder.html + switch m := r.(type) { + case mo.Folder: + name = m.Name + case mo.StoragePod: + name = m.Name + + // { "vim.Datacenter" } - Identifies the root folder and its descendant + // folders. Data center folders can contain child data center folders and + // Datacenter managed objects. Datacenter objects contain virtual machine, + // compute resource, network entity, and datastore folders. + case mo.Datacenter: + name = m.Name + + // { "vim.Virtualmachine", "vim.VirtualApp" } - Identifies a virtual machine + // folder. A virtual machine folder may contain child virtual machine + // folders. It also can contain VirtualMachine managed objects, templates, + // and VirtualApp managed objects. + case mo.VirtualMachine: + name = m.Name + case mo.VirtualApp: + name = m.Name + + // { "vim.ComputeResource" } - Identifies a compute resource + // folder, which contains child compute resource folders and ComputeResource + // hierarchies. + case mo.ComputeResource: + name = m.Name + case mo.ClusterComputeResource: + name = m.Name + case mo.HostSystem: + name = m.Name + case mo.ResourcePool: + name = m.Name + + // { "vim.Network" } - Identifies a network entity folder. + // Network entity folders on a vCenter Server can contain Network, + // DistributedVirtualSwitch, and DistributedVirtualPortgroup managed objects. + // Network entity folders on an ESXi host can contain only Network objects. + case mo.Network: + name = m.Name + case mo.OpaqueNetwork: + name = m.Name + case mo.DistributedVirtualSwitch: + name = m.Name + case mo.DistributedVirtualPortgroup: + name = m.Name + case mo.VmwareDistributedVirtualSwitch: + name = m.Name + + // { "vim.Datastore" } - Identifies a datastore folder. Datastore folders can + // contain child datastore folders and Datastore managed objects. + case mo.Datastore: + name = m.Name + + default: + panic("not implemented for type " + reflect.TypeOf(r).String()) + } + + e := Element{ + Path: path.Join(prefix, name), + Object: r, + } + + return e +} + +type Lister struct { + Collector *property.Collector + Reference types.ManagedObjectReference + Prefix string + All bool +} + +func (l Lister) retrieveProperties(ctx context.Context, req types.RetrieveProperties, dst *[]interface{}) error { + res, err := l.Collector.RetrieveProperties(ctx, req) + if err != nil { + return err + } + + // Instead of using mo.LoadRetrievePropertiesResponse, use a custom loop to + // iterate over the results and ignore entries that have properties that + // could not be retrieved (a non-empty `missingSet` property). Since the + // returned objects are enumerated by vSphere in the first place, any object + // that has a non-empty `missingSet` property is indicative of a race + // condition in vSphere where the object was enumerated initially, but was + // removed before its properties could be collected. + for _, p := range res.Returnval { + v, err := mo.ObjectContentToType(p) + if err != nil { + // Ignore fault if it is ManagedObjectNotFound + if soap.IsVimFault(err) { + switch soap.ToVimFault(err).(type) { + case *types.ManagedObjectNotFound: + continue + } + } + + return err + } + + *dst = append(*dst, v) + } + + return nil +} + +func (l Lister) List(ctx context.Context) ([]Element, error) { + switch l.Reference.Type { + case "Folder", "StoragePod": + return l.ListFolder(ctx) + case "Datacenter": + return l.ListDatacenter(ctx) + case "ComputeResource", "ClusterComputeResource": + // Treat ComputeResource and ClusterComputeResource as one and the same. + // It doesn't matter from the perspective of the lister. + return l.ListComputeResource(ctx) + case "ResourcePool": + return l.ListResourcePool(ctx) + case "HostSystem": + return l.ListHostSystem(ctx) + case "VirtualApp": + return l.ListVirtualApp(ctx) + default: + return nil, fmt.Errorf("cannot traverse type " + l.Reference.Type) + } +} + +func (l Lister) ListFolder(ctx context.Context) ([]Element, error) { + spec := types.PropertyFilterSpec{ + ObjectSet: []types.ObjectSpec{ + { + Obj: l.Reference, + SelectSet: []types.BaseSelectionSpec{ + &types.TraversalSpec{ + Path: "childEntity", + Skip: types.NewBool(false), + Type: "Folder", + }, + }, + Skip: types.NewBool(true), + }, + }, + } + + // Retrieve all objects that we can deal with + childTypes := []string{ + "Folder", + "Datacenter", + "VirtualApp", + "VirtualMachine", + "Network", + "ComputeResource", + "ClusterComputeResource", + "Datastore", + "DistributedVirtualSwitch", + } + + for _, t := range childTypes { + pspec := types.PropertySpec{ + Type: t, + } + + if l.All { + pspec.All = types.NewBool(true) + } else { + pspec.PathSet = []string{"name"} + + // Additional basic properties. + switch t { + case "Folder": + pspec.PathSet = append(pspec.PathSet, "childType") + case "ComputeResource", "ClusterComputeResource": + // The ComputeResource and ClusterComputeResource are dereferenced in + // the ResourcePoolFlag. Make sure they always have their resourcePool + // field populated. + pspec.PathSet = append(pspec.PathSet, "resourcePool") + } + } + + spec.PropSet = append(spec.PropSet, pspec) + } + + req := types.RetrieveProperties{ + SpecSet: []types.PropertyFilterSpec{spec}, + } + + var dst []interface{} + + err := l.retrieveProperties(ctx, req, &dst) + if err != nil { + return nil, err + } + + es := []Element{} + for _, v := range dst { + es = append(es, ToElement(v.(mo.Reference), l.Prefix)) + } + + return es, nil +} + +func (l Lister) ListDatacenter(ctx context.Context) ([]Element, error) { + ospec := types.ObjectSpec{ + Obj: l.Reference, + Skip: types.NewBool(true), + } + + // Include every datastore folder in the select set + fields := []string{ + "vmFolder", + "hostFolder", + "datastoreFolder", + "networkFolder", + } + + for _, f := range fields { + tspec := types.TraversalSpec{ + Path: f, + Skip: types.NewBool(false), + Type: "Datacenter", + } + + ospec.SelectSet = append(ospec.SelectSet, &tspec) + } + + pspec := types.PropertySpec{ + Type: "Folder", + } + + if l.All { + pspec.All = types.NewBool(true) + } else { + pspec.PathSet = []string{"name", "childType"} + } + + req := types.RetrieveProperties{ + SpecSet: []types.PropertyFilterSpec{ + { + ObjectSet: []types.ObjectSpec{ospec}, + PropSet: []types.PropertySpec{pspec}, + }, + }, + } + + var dst []interface{} + + err := l.retrieveProperties(ctx, req, &dst) + if err != nil { + return nil, err + } + + es := []Element{} + for _, v := range dst { + es = append(es, ToElement(v.(mo.Reference), l.Prefix)) + } + + return es, nil +} + +func (l Lister) ListComputeResource(ctx context.Context) ([]Element, error) { + ospec := types.ObjectSpec{ + Obj: l.Reference, + Skip: types.NewBool(true), + } + + fields := []string{ + "host", + "resourcePool", + } + + for _, f := range fields { + tspec := types.TraversalSpec{ + Path: f, + Skip: types.NewBool(false), + Type: "ComputeResource", + } + + ospec.SelectSet = append(ospec.SelectSet, &tspec) + } + + childTypes := []string{ + "HostSystem", + "ResourcePool", + } + + var pspecs []types.PropertySpec + for _, t := range childTypes { + pspec := types.PropertySpec{ + Type: t, + } + + if l.All { + pspec.All = types.NewBool(true) + } else { + pspec.PathSet = []string{"name"} + } + + pspecs = append(pspecs, pspec) + } + + req := types.RetrieveProperties{ + SpecSet: []types.PropertyFilterSpec{ + { + ObjectSet: []types.ObjectSpec{ospec}, + PropSet: pspecs, + }, + }, + } + + var dst []interface{} + + err := l.retrieveProperties(ctx, req, &dst) + if err != nil { + return nil, err + } + + es := []Element{} + for _, v := range dst { + es = append(es, ToElement(v.(mo.Reference), l.Prefix)) + } + + return es, nil +} + +func (l Lister) ListResourcePool(ctx context.Context) ([]Element, error) { + ospec := types.ObjectSpec{ + Obj: l.Reference, + Skip: types.NewBool(true), + } + + fields := []string{ + "resourcePool", + } + + for _, f := range fields { + tspec := types.TraversalSpec{ + Path: f, + Skip: types.NewBool(false), + Type: "ResourcePool", + } + + ospec.SelectSet = append(ospec.SelectSet, &tspec) + } + + childTypes := []string{ + "ResourcePool", + } + + var pspecs []types.PropertySpec + for _, t := range childTypes { + pspec := types.PropertySpec{ + Type: t, + } + + if l.All { + pspec.All = types.NewBool(true) + } else { + pspec.PathSet = []string{"name"} + } + + pspecs = append(pspecs, pspec) + } + + req := types.RetrieveProperties{ + SpecSet: []types.PropertyFilterSpec{ + { + ObjectSet: []types.ObjectSpec{ospec}, + PropSet: pspecs, + }, + }, + } + + var dst []interface{} + + err := l.retrieveProperties(ctx, req, &dst) + if err != nil { + return nil, err + } + + es := []Element{} + for _, v := range dst { + es = append(es, ToElement(v.(mo.Reference), l.Prefix)) + } + + return es, nil +} + +func (l Lister) ListHostSystem(ctx context.Context) ([]Element, error) { + ospec := types.ObjectSpec{ + Obj: l.Reference, + Skip: types.NewBool(true), + } + + fields := []string{ + "datastore", + "network", + "vm", + } + + for _, f := range fields { + tspec := types.TraversalSpec{ + Path: f, + Skip: types.NewBool(false), + Type: "HostSystem", + } + + ospec.SelectSet = append(ospec.SelectSet, &tspec) + } + + childTypes := []string{ + "Datastore", + "Network", + "VirtualMachine", + } + + var pspecs []types.PropertySpec + for _, t := range childTypes { + pspec := types.PropertySpec{ + Type: t, + } + + if l.All { + pspec.All = types.NewBool(true) + } else { + pspec.PathSet = []string{"name"} + } + + pspecs = append(pspecs, pspec) + } + + req := types.RetrieveProperties{ + SpecSet: []types.PropertyFilterSpec{ + { + ObjectSet: []types.ObjectSpec{ospec}, + PropSet: pspecs, + }, + }, + } + + var dst []interface{} + + err := l.retrieveProperties(ctx, req, &dst) + if err != nil { + return nil, err + } + + es := []Element{} + for _, v := range dst { + es = append(es, ToElement(v.(mo.Reference), l.Prefix)) + } + + return es, nil +} + +func (l Lister) ListVirtualApp(ctx context.Context) ([]Element, error) { + ospec := types.ObjectSpec{ + Obj: l.Reference, + Skip: types.NewBool(true), + } + + fields := []string{ + "resourcePool", + "vm", + } + + for _, f := range fields { + tspec := types.TraversalSpec{ + Path: f, + Skip: types.NewBool(false), + Type: "VirtualApp", + } + + ospec.SelectSet = append(ospec.SelectSet, &tspec) + } + + childTypes := []string{ + "ResourcePool", + "VirtualMachine", + } + + var pspecs []types.PropertySpec + for _, t := range childTypes { + pspec := types.PropertySpec{ + Type: t, + } + + if l.All { + pspec.All = types.NewBool(true) + } else { + pspec.PathSet = []string{"name"} + } + + pspecs = append(pspecs, pspec) + } + + req := types.RetrieveProperties{ + SpecSet: []types.PropertyFilterSpec{ + { + ObjectSet: []types.ObjectSpec{ospec}, + PropSet: pspecs, + }, + }, + } + + var dst []interface{} + + err := l.retrieveProperties(ctx, req, &dst) + if err != nil { + return nil, err + } + + es := []Element{} + for _, v := range dst { + es = append(es, ToElement(v.(mo.Reference), l.Prefix)) + } + + return es, nil +} diff --git a/vendor/github.com/vmware/govmomi/list/path.go b/vendor/github.com/vmware/govmomi/list/path.go new file mode 100644 index 00000000000..f3a10652015 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/list/path.go @@ -0,0 +1,44 @@ +/* +Copyright (c) 2014 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "path" + "strings" +) + +func ToParts(p string) []string { + p = path.Clean(p) + if p == "/" { + return []string{} + } + + if len(p) > 0 { + // Prefix ./ if relative + if p[0] != '/' && p[0] != '.' { + p = "./" + p + } + } + + ps := strings.Split(p, "/") + if ps[0] == "" { + // Start at root + ps = ps[1:] + } + + return ps +} diff --git a/vendor/github.com/vmware/govmomi/ovf/cim.go b/vendor/github.com/vmware/govmomi/ovf/cim.go new file mode 100644 index 00000000000..427699954c4 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/cim.go @@ -0,0 +1,128 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ovf + +import ( + "github.com/vmware/govmomi/vim25/types" +) + +/* +Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_VirtualSystemSettingData.xsd +*/ + +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"` +} + +/* +Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_ResourceAllocationSettingData.xsd +*/ + +type CIMResourceAllocationSettingData struct { + ElementName string `xml:"ElementName"` + InstanceID string `xml:"InstanceID"` + + ResourceType *uint16 `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"` +} + +/* +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 *uint16 `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"` +} diff --git a/vendor/github.com/vmware/govmomi/ovf/doc.go b/vendor/github.com/vmware/govmomi/ovf/doc.go new file mode 100644 index 00000000000..6284b1ac581 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/doc.go @@ -0,0 +1,25 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package ovf provides functionality to unmarshal and inspect the structure +of an OVF file. It is not a complete implementation of the specification and +is intended to be used to import virtual infrastructure into vSphere. + +For a complete specification of the OVF standard, refer to: +https://www.dmtf.org/sites/default/files/standards/documents/DSP0243_2.1.0.pdf +*/ +package ovf diff --git a/vendor/github.com/vmware/govmomi/ovf/env.go b/vendor/github.com/vmware/govmomi/ovf/env.go new file mode 100644 index 00000000000..3ec1b99d000 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/env.go @@ -0,0 +1,99 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ovf + +import ( + "bytes" + "fmt" + + "github.com/vmware/govmomi/vim25/xml" +) + +const ( + ovfEnvHeader = `` + ovfEnvPlatformSection = ` + %s + %s + %s + %s + ` + ovfEnvPropertyHeader = `` + ovfEnvPropertyEntry = `` + ovfEnvPropertyFooter = `` + ovfEnvFooter = `` +) + +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"` + + Platform *PlatformSection `xml:"PlatformSection"` + Property *PropertySection `xml:"PropertySection"` +} + +type PlatformSection struct { + Kind string `xml:"Kind"` + Version string `xml:"Version"` + Vendor string `xml:"Vendor"` + Locale string `xml:"Locale"` +} + +type PropertySection struct { + Properties []EnvProperty `xml:"Property"` +} + +type EnvProperty struct { + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` +} + +// Marshal marshals Env to xml by using xml.Marshal. +func (e Env) Marshal() (string, error) { + x, err := xml.Marshal(e) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s%s", xml.Header, x), nil +} + +// MarshalManual manually marshals Env to xml suitable for a vApp guest. +// It exists to overcome the lack of expressiveness in Go's XML namespaces. +func (e Env) MarshalManual() string { + var buffer bytes.Buffer + + buffer.WriteString(xml.Header) + buffer.WriteString(fmt.Sprintf(ovfEnvHeader, e.EsxID)) + buffer.WriteString(fmt.Sprintf(ovfEnvPlatformSection, e.Platform.Kind, e.Platform.Version, e.Platform.Vendor, e.Platform.Locale)) + + buffer.WriteString(fmt.Sprint(ovfEnvPropertyHeader)) + for _, p := range e.Property.Properties { + buffer.WriteString(fmt.Sprintf(ovfEnvPropertyEntry, p.Key, p.Value)) + } + buffer.WriteString(fmt.Sprint(ovfEnvPropertyFooter)) + + buffer.WriteString(fmt.Sprint(ovfEnvFooter)) + + return buffer.String() +} diff --git a/vendor/github.com/vmware/govmomi/ovf/envelope.go b/vendor/github.com/vmware/govmomi/ovf/envelope.go new file mode 100644 index 00000000000..fa4690d88a0 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/envelope.go @@ -0,0 +1,200 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ovf + +type Envelope struct { + References []File `xml:"References>File"` + + // Package level meta-data + Annotation *AnnotationSection `xml:"AnnotationSection"` + Product *ProductSection `xml:"ProductSection"` + Network *NetworkSection `xml:"NetworkSection"` + Disk *DiskSection `xml:"DiskSection"` + OperatingSystem *OperatingSystemSection `xml:"OperatingSystemSection"` + Eula *EulaSection `xml:"EulaSection"` + VirtualHardware *VirtualHardwareSection `xml:"VirtualHardwareSection"` + ResourceAllocation *ResourceAllocationSection `xml:"ResourceAllocationSection"` + DeploymentOption *DeploymentOptionSection `xml:"DeploymentOptionSection"` + + // Content: A VirtualSystem or a VirtualSystemCollection + VirtualSystem *VirtualSystem `xml:"VirtualSystem"` +} + +type VirtualSystem struct { + Content + + Annotation []AnnotationSection `xml:"AnnotationSection"` + Product []ProductSection `xml:"ProductSection"` + OperatingSystem []OperatingSystemSection `xml:"OperatingSystemSection"` + Eula []EulaSection `xml:"EulaSection"` + VirtualHardware []VirtualHardwareSection `xml:"VirtualHardwareSection"` +} + +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"` +} + +type Content struct { + ID string `xml:"id,attr"` + Info string `xml:"Info"` + Name *string `xml:"Name"` +} + +type Section struct { + Required *bool `xml:"required,attr"` + Info string `xml:"Info"` +} + +type AnnotationSection struct { + Section + + Annotation string `xml:"Annotation"` +} + +type ProductSection struct { + Section + + Class *string `xml:"class,attr"` + Instance *string `xml:"instance,attr"` + + 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"` +} + +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"` + + Label *string `xml:"Label"` + Description *string `xml:"Description"` + + Values []PropertyConfigurationValue `xml:"Value"` +} + +type PropertyConfigurationValue struct { + Value string `xml:"value,attr"` + Configuration *string `xml:"configuration,attr"` +} + +type NetworkSection struct { + Section + + Networks []Network `xml:"Network"` +} + +type Network struct { + Name string `xml:"name,attr"` + + Description string `xml:"Description"` +} + +type DiskSection struct { + Section + + Disks []VirtualDiskDesc `xml:"Disk"` +} + +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"` +} + +type OperatingSystemSection struct { + Section + + ID int16 `xml:"id,attr"` + Version *string `xml:"version,attr"` + OSType *string `xml:"osType,attr"` + + Description *string `xml:"Description"` +} + +type EulaSection struct { + Section + + License string `xml:"License"` +} + +type VirtualHardwareSection struct { + Section + + ID *string `xml:"id,attr"` + Transport *string `xml:"transport,attr"` + + System *VirtualSystemSettingData `xml:"System"` + Item []ResourceAllocationSettingData `xml:"Item"` + StorageItem []StorageAllocationSettingData `xml:"StorageItem"` +} + +type VirtualSystemSettingData struct { + CIMVirtualSystemSettingData +} + +type ResourceAllocationSettingData struct { + CIMResourceAllocationSettingData + + Required *bool `xml:"required,attr"` + Configuration *string `xml:"configuration,attr"` + Bound *string `xml:"bound,attr"` +} + +type StorageAllocationSettingData struct { + CIMStorageAllocationSettingData + + Required *bool `xml:"required,attr"` + Configuration *string `xml:"configuration,attr"` + Bound *string `xml:"bound,attr"` +} + +type ResourceAllocationSection struct { + Section + + Item []ResourceAllocationSettingData `xml:"Item"` +} + +type DeploymentOptionSection struct { + Section + + Configuration []DeploymentOptionConfiguration `xml:"Configuration"` +} + +type DeploymentOptionConfiguration struct { + ID string `xml:"id,attr"` + Default *bool `xml:"default,attr"` + + Label string `xml:"Label"` + Description string `xml:"Description"` +} diff --git a/vendor/github.com/vmware/govmomi/ovf/manager.go b/vendor/github.com/vmware/govmomi/ovf/manager.go new file mode 100644 index 00000000000..3ee2afdd46a --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/manager.go @@ -0,0 +1,103 @@ +/* +Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ovf + +import ( + "context" + + "github.com/vmware/govmomi/vim25" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +type Manager struct { + types.ManagedObjectReference + + c *vim25.Client +} + +func NewManager(c *vim25.Client) *Manager { + return &Manager{*c.ServiceContent.OvfManager, c} +} + +// CreateDescriptor wraps methods.CreateDescriptor +func (m *Manager) CreateDescriptor(ctx context.Context, obj mo.Reference, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) { + req := types.CreateDescriptor{ + This: m.Reference(), + Obj: obj.Reference(), + Cdp: cdp, + } + + res, err := methods.CreateDescriptor(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} + +// CreateImportSpec wraps methods.CreateImportSpec +func (m *Manager) CreateImportSpec(ctx context.Context, ovfDescriptor string, resourcePool mo.Reference, datastore mo.Reference, cisp types.OvfCreateImportSpecParams) (*types.OvfCreateImportSpecResult, error) { + req := types.CreateImportSpec{ + This: m.Reference(), + OvfDescriptor: ovfDescriptor, + ResourcePool: resourcePool.Reference(), + Datastore: datastore.Reference(), + Cisp: cisp, + } + + res, err := methods.CreateImportSpec(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} + +// ParseDescriptor wraps methods.ParseDescriptor +func (m *Manager) ParseDescriptor(ctx context.Context, ovfDescriptor string, pdp types.OvfParseDescriptorParams) (*types.OvfParseDescriptorResult, error) { + req := types.ParseDescriptor{ + This: m.Reference(), + OvfDescriptor: ovfDescriptor, + Pdp: pdp, + } + + res, err := methods.ParseDescriptor(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} + +// ValidateHost wraps methods.ValidateHost +func (m *Manager) ValidateHost(ctx context.Context, ovfDescriptor string, host mo.Reference, vhp types.OvfValidateHostParams) (*types.OvfValidateHostResult, error) { + req := types.ValidateHost{ + This: m.Reference(), + OvfDescriptor: ovfDescriptor, + Host: host.Reference(), + Vhp: vhp, + } + + res, err := methods.ValidateHost(ctx, m.c, &req) + if err != nil { + return nil, err + } + + return &res.Returnval, nil +} diff --git a/vendor/github.com/vmware/govmomi/ovf/ovf.go b/vendor/github.com/vmware/govmomi/ovf/ovf.go new file mode 100644 index 00000000000..bd279e757d7 --- /dev/null +++ b/vendor/github.com/vmware/govmomi/ovf/ovf.go @@ -0,0 +1,35 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ovf + +import ( + "io" + + "github.com/vmware/govmomi/vim25/xml" +) + +func Unmarshal(r io.Reader) (*Envelope, error) { + var e Envelope + + dec := xml.NewDecoder(r) + err := dec.Decode(&e) + if err != nil { + return nil, err + } + + return &e, nil +}