tests: upload & test in vCenter. Closes #338

This commit is contained in:
Alexander Todorov 2020-07-30 09:26:13 -04:00 committed by Tom Gundersen
parent 02346faff8
commit 9cce43d384
255 changed files with 127290 additions and 3 deletions

View file

@ -0,0 +1,86 @@
/*
Copyright (c) 2018 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 internal
import (
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
// VAPI REST Paths
const (
SessionPath = "/com/vmware/cis/session"
CategoryPath = "/com/vmware/cis/tagging/category"
TagPath = "/com/vmware/cis/tagging/tag"
AssociationPath = "/com/vmware/cis/tagging/tag-association"
LibraryPath = "/com/vmware/content/library"
LibraryItemFileData = "/com/vmware/cis/data"
LibraryItemPath = "/com/vmware/content/library/item"
LibraryItemFilePath = "/com/vmware/content/library/item/file"
LibraryItemUpdateSession = "/com/vmware/content/library/item/update-session"
LibraryItemUpdateSessionFile = "/com/vmware/content/library/item/updatesession/file"
LibraryItemDownloadSession = "/com/vmware/content/library/item/download-session"
LibraryItemDownloadSessionFile = "/com/vmware/content/library/item/downloadsession/file"
LocalLibraryPath = "/com/vmware/content/local-library"
SubscribedLibraryPath = "/com/vmware/content/subscribed-library"
SubscribedLibraryItem = "/com/vmware/content/library/subscribed-item"
Subscriptions = "/com/vmware/content/library/subscriptions"
VCenterOVFLibraryItem = "/com/vmware/vcenter/ovf/library-item"
VCenterVMTXLibraryItem = "/vcenter/vm-template/library-items"
VCenterVM = "/vcenter/vm"
SessionCookieName = "vmware-api-session-id"
UseHeaderAuthn = "vmware-use-header-authn"
)
// AssociatedObject is the same structure as types.ManagedObjectReference,
// just with a different field name (ID instead of Value).
// In the API we use mo.Reference, this type is only used for wire transfer.
type AssociatedObject struct {
Type string `json:"type"`
Value string `json:"id"`
}
// Reference implements mo.Reference
func (o AssociatedObject) Reference() types.ManagedObjectReference {
return types.ManagedObjectReference(o)
}
// Association for tag-association requests.
type Association struct {
ObjectID *AssociatedObject `json:"object_id,omitempty"`
}
// NewAssociation returns an Association, converting ref to an AssociatedObject.
func NewAssociation(ref mo.Reference) Association {
obj := AssociatedObject(ref.Reference())
return Association{
ObjectID: &obj,
}
}
type SubscriptionDestination struct {
ID string `json:"subscription"`
}
type SubscriptionDestinationSpec struct {
Subscriptions []SubscriptionDestination `json:"subscriptions,omitempty"`
}
type SubscriptionItemDestinationSpec struct {
Force bool `json:"force_sync_content"`
Subscriptions []SubscriptionDestination `json:"subscriptions,omitempty"`
}

View file

@ -0,0 +1,185 @@
# The content library finder
The govmomi package now includes a finder for the content library, [`github.com/vmware/govmomi/vapi/library.Finder`](https://github.com/akutz/govmomi/blob/feature/content-library/vapi/library/finder/finder.go). This finder supports searching for objects in the content library using an inventory path design similar to the [standard govmomi finder](https://github.com/vmware/govmomi/blob/master/find/finder.go) and includes support for wildcard matching courtesy of golang's [`path.Match`](https://golang.org/pkg/path/#Match). For example:
| Pattern | Result |
|---------|--------|
| `*` | Gets all of the content libraries |
| `*/` | Gets all of the content library items for all of the content libraries. |
| `/Public/` | Gets all of the content library items for the content library named `Public` |
| `/*/*/` | Gets all of the content library files for all of the content library items for all of the content libraries. |
| `Public/*/photon2*` | Gets all of the files that begin with `photon2` in all of the content library items for the content library named `Public` |
The use of a wildcard character in the search string results in a full listing of content library objects for that part of the path's server-side analogue. If `Public/Photon2*` is searched, then all of the content library items for the `Public` library are listed in order to perform the wildcard matching. However, if `Public/PhotonOS-2-GA` then the server-side API call [`library:item:find`](https://vdc-repo.vmware.com/vmwb-repository/dcr-public/1cd28284-3b72-4885-9e31-d1c6d9e26686/71ef7304-a6c9-43b3-a3cd-868b2c236c81/doc/operations/com/vmware/content/library/item.find-operation.html) is used to find an item with the name `PhotonOS-2-GA`.
## Performance
Search strings that do not use wildcard characters **should** be **more** efficient as these searches rely on server-side find calls and do not require dumping all of the objects. However, this is only true for systems with a large number of objects in the content libraries. This is due to the number of round-trips required to lookup an object with a direct path. For example:
| Search path | Roundtrips |
|------|------------|
| `Public/Photon2` | 4 |
| `Public*/Photon2*` | 2 |
The *absolute* search path takes twice as many roundtrips compared to the search path with wildcards:
### Absolute path search logic
1. Find library ID for library with name using server-side find API
2. Get library with library ID
3. Find item ID for item with name using server-side find API
4. Get item with item ID
### Wildcard search logic
1. Get all of the libraries and filter the pattern on the client-side
2. Get all of the items for the library and filter the pattern on the client-=side
### Searches at scale
While a system that has few content library objects benefits from wildcard search logic, the fact is that the above approach regarding absolute paths proves out to be **much** more efficient for systems with large numbers of content library objects.
## `govc library.ls`
### Listing all the objects in the content library
```shell
$ govc library.ls '*/*/'
/ISOs/CentOS-7-x86_64-Minimal-1804/CentOS-7-x86_64-Minimal-1804.iso
/ISOs/CoreOS Production/coreos_production_iso_image.iso
/ISOs/VMware-VCSA-all-6.7.0-8217866.iso/VMware-VCSA-all-6.7.0-8217866.iso
/ISOs/VMware-VIM-all-6.7.0-8217866.iso/VMware-VIM-all-6.7.0-8217866.iso
/ISOs/ubuntu-16.04.5-server-amd64/ubuntu-16.04.5-server-amd64.iso
/ISOs/photon-2.0-304b817/photon-2.0-304b817.iso
/OVAs/VMware-vCenter-Server-Appliance-6.7.0.10000-8217866_OVF10.ova/VMware-vCenter-Server-Appliance-6.7.0.10000-8217866_OVF10.ova
/OVAs/coreos_production_vmware_ova/coreos_production_vmware_ova.ovf
/OVAs/coreos_production_vmware_ova/coreos_production_vmware_ova-1.vmdk
/OVAs/centos_cloud_template/centos_cloud_template-2.iso
/OVAs/centos_cloud_template/centos_cloud_template.ovf
/OVAs/centos_cloud_template/centos_cloud_template-1.vmdk
/OVAs/centos_cloud_template/centos_cloud_template-3.nvram
/OVAs/photon-custom-hw13-2.0-304b817/photon-ova-disk1.vmdk
/OVAs/photon-custom-hw13-2.0-304b817/photon-ova.ovf
/OVAs/yakity-centos/yakity-centos-2.nvram
/OVAs/yakity-centos/yakity-centos-1.vmdk
/OVAs/yakity-centos/yakity-centos.ovf
/OVAs/yakity-photon/yakity-photon-1.vmdk
/OVAs/yakity-photon/yakity-photon.ovf
/OVAs/yakity-photon/yakity-photon-2.nvram
/OVAs/ubuntu-16.04-server-cloudimg-amd64/ubuntu-16.04-server-cloudimg-amd64.ovf
/OVAs/ubuntu-16.04-server-cloudimg-amd64/ubuntu-16.04-server-cloudimg-amd64-1.vmdk
/sk8-TestUploadOVA/photon2-cloud-init/photon2-cloud-init.ovf
/sk8-TestUploadOVA/photon2-cloud-init/photon2-cloud-init-1.vmdk
/Public/photon2-cloud-init/photon2-cloud-init.ovf
/Public/photon2-cloud-init/photon2-cloud-init-1.vmdk
```
## `govc library.info`
### Getting the info for all the objects in the content library
```shell
$ govc library.info '*/*/'
Name: CentOS-7-x86_64-Minimal-1804.iso
Path: /ISOs/CentOS-7-x86_64-Minimal-1804/CentOS-7-x86_64-Minimal-1804.iso
Size: 824637106200
Version: 1
Name: coreos_production_iso_image.iso
Path: /ISOs/CoreOS Production/coreos_production_iso_image.iso
Size: 824637441624
Version: 1
Name: VMware-VCSA-all-6.7.0-8217866.iso
Path: /ISOs/VMware-VCSA-all-6.7.0-8217866.iso/VMware-VCSA-all-6.7.0-8217866.iso
Size: 824637106368
Version: 1
Name: VMware-VIM-all-6.7.0-8217866.iso
Path: /ISOs/VMware-VIM-all-6.7.0-8217866.iso/VMware-VIM-all-6.7.0-8217866.iso
Size: 824637106504
Version: 1
Name: ubuntu-16.04.5-server-amd64.iso
Path: /ISOs/ubuntu-16.04.5-server-amd64/ubuntu-16.04.5-server-amd64.iso
Size: 824637441944
Version: 1
Name: photon-2.0-304b817.iso
Path: /ISOs/photon-2.0-304b817/photon-2.0-304b817.iso
Size: 824637106672
Version: 1
Name: VMware-vCenter-Server-Appliance-6.7.0.10000-8217866_OVF10.ova
Path: /OVAs/VMware-vCenter-Server-Appliance-6.7.0.10000-8217866_OVF10.ova/VMware-vCenter-Server-Appliance-6.7.0.10000-8217866_OVF10.ova
Size: 824637106880
Version: 1
Name: coreos_production_vmware_ova.ovf
Path: /OVAs/coreos_production_vmware_ova/coreos_production_vmware_ova.ovf
Size: 824637107032
Version: 1
Name: coreos_production_vmware_ova-1.vmdk
Path: /OVAs/coreos_production_vmware_ova/coreos_production_vmware_ova-1.vmdk
Size: 824637107072
Version: 1
Name: centos_cloud_template-2.iso
Path: /OVAs/centos_cloud_template/centos_cloud_template-2.iso
Size: 824636997760
Version: 1
Name: centos_cloud_template.ovf
Path: /OVAs/centos_cloud_template/centos_cloud_template.ovf
Size: 824636997792
Version: 1
Name: centos_cloud_template-1.vmdk
Path: /OVAs/centos_cloud_template/centos_cloud_template-1.vmdk
Size: 824636997832
Version: 1
Name: centos_cloud_template-3.nvram
Path: /OVAs/centos_cloud_template/centos_cloud_template-3.nvram
Size: 824636997856
Version: 1
Name: photon-ova-disk1.vmdk
Path: /OVAs/photon-custom-hw13-2.0-304b817/photon-ova-disk1.vmdk
Size: 824637107280
Version: 1
Name: photon-ova.ovf
Path: /OVAs/photon-custom-hw13-2.0-304b817/photon-ova.ovf
Size: 824637107328
Version: 1
Name: yakity-centos-2.nvram
Path: /OVAs/yakity-centos/yakity-centos-2.nvram
Size: 824637253000
Version: 2
Name: yakity-centos-1.vmdk
Path: /OVAs/yakity-centos/yakity-centos-1.vmdk
Size: 824637253024
Version: 2
Name: yakity-centos.ovf
Path: /OVAs/yakity-centos/yakity-centos.ovf
Size: 824637253056
Version: 2
Name: yakity-photon-1.vmdk
Path: /OVAs/yakity-photon/yakity-photon-1.vmdk
Size: 824637107504
Version: 5
Name: yakity-photon.ovf
Path: /OVAs/yakity-photon/yakity-photon.ovf
Size: 824637107536
Version: 5
Name: yakity-photon-2.nvram
Path: /OVAs/yakity-photon/yakity-photon-2.nvram
Size: 824637107560
Version: 5
Name: ubuntu-16.04-server-cloudimg-amd64.ovf
Path: /OVAs/ubuntu-16.04-server-cloudimg-amd64/ubuntu-16.04-server-cloudimg-amd64.ovf
Size: 824637442112
Version: 1
Name: ubuntu-16.04-server-cloudimg-amd64-1.vmdk
Path: /OVAs/ubuntu-16.04-server-cloudimg-amd64/ubuntu-16.04-server-cloudimg-amd64-1.vmdk
Size: 824637442136
Version: 1
Name: photon2-cloud-init.ovf
Path: /sk8-TestUploadOVA/photon2-cloud-init/photon2-cloud-init.ovf
Size: 824636903184
Version: 1
Name: photon2-cloud-init-1.vmdk
Path: /sk8-TestUploadOVA/photon2-cloud-init/photon2-cloud-init-1.vmdk
Size: 824636903208
Version: 1
Name: photon2-cloud-init.ovf
Path: /Public/photon2-cloud-init/photon2-cloud-init.ovf
Size: 824637442304
Version: 3
Name: photon2-cloud-init-1.vmdk
Path: /Public/photon2-cloud-init/photon2-cloud-init-1.vmdk
Size: 824637442328
Version: 3
```

View file

@ -0,0 +1,328 @@
/*
Copyright (c) 2018 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 finder
import (
"context"
"encoding/json"
"fmt"
"path"
"strings"
"github.com/vmware/govmomi/vapi/library"
)
// Finder is a helper object for finding content library objects by their
// inventory paths: /LIBRARY/ITEM/FILE.
//
// Wildcard characters `*` and `?` are both supported. However, the use
// of a wildcard character in the search string results in a full listing of
// that part of the path's server-side items.
//
// Path parts that do not use wildcard characters rely on server-side Find
// functions to find the path token by its name. Ironically finding one
// item with a direct path takes longer than if a wildcard is used because
// of the multiple round-trips. Direct paths will be more performant on
// systems that have numerous items.
type Finder struct {
M *library.Manager
}
// NewFinder returns a new Finder.
func NewFinder(m *library.Manager) *Finder {
return &Finder{m}
}
// Find finds one or more items that match the provided inventory path(s).
func (f *Finder) Find(
ctx context.Context, ipath ...string) ([]FindResult, error) {
if len(ipath) == 0 {
ipath = []string{""}
}
var result []FindResult
for _, p := range ipath {
results, err := f.find(ctx, p)
if err != nil {
return nil, err
}
result = append(result, results...)
}
return result, nil
}
func (f *Finder) find(ctx context.Context, ipath string) ([]FindResult, error) {
if ipath == "" {
ipath = "*"
}
// Get the argument and remove any leading separator characters.
ipath = strings.TrimPrefix(ipath, "/")
// Tokenize the path into its distinct parts.
parts := strings.Split(ipath, "/")
// If there are more than three parts then the file name contains
// the "/" character. In that case collapse any additional parts
// back into the filename.
if len(parts) > 3 {
parts = []string{
parts[0],
parts[1],
strings.Join(parts[2:], "/"),
}
}
libs, err := f.findLibraries(ctx, parts[0])
if err != nil {
return nil, err
}
// If the path is a single token then the libraries are requested.
if len(parts) < 2 {
return libs, nil
}
items, err := f.findLibraryItems(ctx, libs, parts[1])
if err != nil {
return nil, err
}
// If the path is two tokens then the library items are requested.
if len(parts) < 3 {
return items, nil
}
// Get the library item files.
return f.findLibraryItemFiles(ctx, items, parts[2])
}
// FindResult is the type of object returned from a Find operation.
type FindResult interface {
// GetParent returns the parent of the find result. If the find result
// is a Library then this function will return nil.
GetParent() FindResult
// GetPath returns the inventory path of the find result.
GetPath() string
// GetID returns the ID of the find result.
GetID() string
// GetName returns the name of the find result.
GetName() string
// GetResult gets the underlying library object.
GetResult() interface{}
}
type findResult struct {
result interface{}
parent FindResult
}
func (f findResult) GetResult() interface{} {
return f.result
}
func (f findResult) GetParent() FindResult {
return f.parent
}
func (f findResult) GetPath() string {
switch f.result.(type) {
case library.Library:
return fmt.Sprintf("/%s", f.GetName())
case library.Item, library.File:
return fmt.Sprintf("%s/%s", f.parent.GetPath(), f.GetName())
default:
return ""
}
}
func (f findResult) GetID() string {
switch t := f.result.(type) {
case library.Library:
return t.ID
case library.Item:
return t.ID
default:
return ""
}
}
func (f findResult) GetName() string {
switch t := f.result.(type) {
case library.Library:
return t.Name
case library.Item:
return t.Name
case library.File:
return t.Name
default:
return ""
}
}
func (f findResult) MarshalJSON() ([]byte, error) {
return json.Marshal(f.GetResult())
}
func (f *Finder) findLibraries(
ctx context.Context,
token string) ([]FindResult, error) {
if token == "" {
token = "*"
}
var result []FindResult
// If the token does not contain any wildcard characters then perform
// a lookup by name using a server side call.
if !strings.ContainsAny(token, "*?") {
libIDs, err := f.M.FindLibrary(ctx, library.Find{Name: token})
if err != nil {
return nil, err
}
for _, id := range libIDs {
lib, err := f.M.GetLibraryByID(ctx, id)
if err != nil {
return nil, err
}
result = append(result, findResult{result: *lib})
}
if len(result) == 0 {
lib, err := f.M.GetLibraryByID(ctx, token)
if err == nil {
result = append(result, findResult{result: *lib})
}
}
return result, nil
}
libs, err := f.M.GetLibraries(ctx)
if err != nil {
return nil, err
}
for _, lib := range libs {
match, err := path.Match(token, lib.Name)
if err != nil {
return nil, err
}
if match {
result = append(result, findResult{result: lib})
}
}
return result, nil
}
func (f *Finder) findLibraryItems(
ctx context.Context,
parents []FindResult, token string) ([]FindResult, error) {
if token == "" {
token = "*"
}
var result []FindResult
for _, parent := range parents {
// If the token does not contain any wildcard characters then perform
// a lookup by name using a server side call.
if !strings.ContainsAny(token, "*?") {
childIDs, err := f.M.FindLibraryItems(
ctx, library.FindItem{
Name: token,
LibraryID: parent.GetID(),
})
if err != nil {
return nil, err
}
for _, id := range childIDs {
child, err := f.M.GetLibraryItem(ctx, id)
if err != nil {
return nil, err
}
result = append(result, findResult{
result: *child,
parent: parent,
})
}
continue
}
children, err := f.M.GetLibraryItems(ctx, parent.GetID())
if err != nil {
return nil, err
}
for _, child := range children {
match, err := path.Match(token, child.Name)
if err != nil {
return nil, err
}
if match {
result = append(
result, findResult{parent: parent, result: child})
}
}
}
return result, nil
}
func (f *Finder) findLibraryItemFiles(
ctx context.Context,
parents []FindResult, token string) ([]FindResult, error) {
if token == "" {
token = "*"
}
var result []FindResult
for _, parent := range parents {
// If the token does not contain any wildcard characters then perform
// a lookup by name using a server side call.
if !strings.ContainsAny(token, "*?") {
child, err := f.M.GetLibraryItemFile(ctx, parent.GetID(), token)
if err != nil {
return nil, err
}
result = append(result, findResult{
result: *child,
parent: parent,
})
continue
}
children, err := f.M.ListLibraryItemFiles(ctx, parent.GetID())
if err != nil {
return nil, err
}
for _, child := range children {
match, err := path.Match(token, child.Name)
if err != nil {
return nil, err
}
if match {
result = append(
result, findResult{parent: parent, result: child})
}
}
}
return result, nil
}

View file

@ -0,0 +1,308 @@
/*
Copyright (c) 2018 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 library
import (
"context"
"fmt"
"net/http"
"net/url"
"time"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vapi/internal"
"github.com/vmware/govmomi/vapi/rest"
)
// StorageBackings for Content Libraries
type StorageBackings struct {
DatastoreID string `json:"datastore_id,omitempty"`
Type string `json:"type,omitempty"`
}
// Library provides methods to create, read, update, delete, and enumerate libraries.
type Library struct {
CreationTime *time.Time `json:"creation_time,omitempty"`
Description string `json:"description,omitempty"`
ID string `json:"id,omitempty"`
LastModifiedTime *time.Time `json:"last_modified_time,omitempty"`
LastSyncTime *time.Time `json:"last_sync_time,omitempty"`
Name string `json:"name,omitempty"`
Storage []StorageBackings `json:"storage_backings,omitempty"`
Type string `json:"type,omitempty"`
Version string `json:"version,omitempty"`
Subscription *Subscription `json:"subscription_info,omitempty"`
Publication *Publication `json:"publish_info,omitempty"`
}
// Subscription info
type Subscription struct {
AuthenticationMethod string `json:"authentication_method"`
AutomaticSyncEnabled *bool `json:"automatic_sync_enabled,omitempty"`
OnDemand *bool `json:"on_demand,omitempty"`
Password string `json:"password,omitempty"`
SslThumbprint string `json:"ssl_thumbprint,omitempty"`
SubscriptionURL string `json:"subscription_url,omitempty"`
UserName string `json:"user_name,omitempty"`
}
// Publication info
type Publication struct {
AuthenticationMethod string `json:"authentication_method"`
UserName string `json:"user_name,omitempty"`
Password string `json:"password,omitempty"`
CurrentPassword string `json:"current_password,omitempty"`
PersistJSON *bool `json:"persist_json_enabled,omitempty"`
Published *bool `json:"published,omitempty"`
PublishURL string `json:"publish_url,omitempty"`
}
// SubscriberSummary as returned by ListSubscribers
type SubscriberSummary struct {
LibraryID string `json:"subscribed_library"`
LibraryName string `json:"subscribed_library_name"`
SubscriptionID string `json:"subscription"`
LibraryVcenterHostname string `json:"subscribed_library_vcenter_hostname,omitempty"`
}
// Placement information used to place a virtual machine template
type Placement struct {
ResourcePool string `json:"resource_pool,omitempty"`
Host string `json:"host,omitempty"`
Folder string `json:"folder,omitempty"`
Cluster string `json:"cluster,omitempty"`
Network string `json:"network,omitempty"`
}
// Vcenter contains information about the vCenter Server instance where a subscribed library associated with a subscription exists.
type Vcenter struct {
Hostname string `json:"hostname"`
Port int `json:"https_port,omitempty"`
ServerGUID string `json:"server_guid"`
}
// Subscriber contains the detailed info for a library subscriber.
type Subscriber struct {
LibraryID string `json:"subscribed_library"`
LibraryName string `json:"subscribed_library_name"`
LibraryLocation string `json:"subscribed_library_location"`
Placement *Placement `json:"subscribed_library_placement,omitempty"`
Vcenter *Vcenter `json:"subscribed_library_vcenter,omitempty"`
}
// SubscriberLibrary is the specification for a subscribed library to be associated with a subscription.
type SubscriberLibrary struct {
Target string `json:"target"`
LibraryID string `json:"subscribed_library,omitempty"`
Location string `json:"location"`
Vcenter *Vcenter `json:"vcenter,omitempty"`
Placement *Placement `json:"placement,omitempty"`
}
// Patch merges updates from the given src.
func (l *Library) Patch(src *Library) {
if src.Name != "" {
l.Name = src.Name
}
if src.Description != "" {
l.Description = src.Description
}
if src.Version != "" {
l.Version = src.Version
}
}
// Manager extends rest.Client, adding content library related methods.
type Manager struct {
*rest.Client
}
// NewManager creates a new Manager instance with the given client.
func NewManager(client *rest.Client) *Manager {
return &Manager{
Client: client,
}
}
// Find is the search criteria for finding libraries.
type Find struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
}
// FindLibrary returns one or more libraries that match the provided search
// criteria.
//
// The provided name is case-insensitive.
//
// Either the name or type of library may be set to empty values in order
// to search for all libraries, all libraries with a specific name, regardless
// of type, or all libraries of a specified type.
func (c *Manager) FindLibrary(ctx context.Context, search Find) ([]string, error) {
url := c.Resource(internal.LibraryPath).WithAction("find")
spec := struct {
Spec Find `json:"spec"`
}{search}
var res []string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}
// CreateLibrary creates a new library with the given Type, Name,
// Description, and CategoryID.
func (c *Manager) CreateLibrary(ctx context.Context, library Library) (string, error) {
spec := struct {
Library Library `json:"create_spec"`
}{library}
path := internal.LocalLibraryPath
if library.Type == "SUBSCRIBED" {
path = internal.SubscribedLibraryPath
sub := library.Subscription
u, err := url.Parse(sub.SubscriptionURL)
if err != nil {
return "", err
}
if u.Scheme == "https" && sub.SslThumbprint == "" {
thumbprint := c.Thumbprint(u.Host)
if thumbprint == "" {
t := c.DefaultTransport()
if t.TLSClientConfig.InsecureSkipVerify {
var info object.HostCertificateInfo
_ = info.FromURL(u, t.TLSClientConfig)
thumbprint = info.ThumbprintSHA1
}
sub.SslThumbprint = thumbprint
}
}
}
url := c.Resource(path)
var res string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}
// SyncLibrary syncs a subscribed library.
func (c *Manager) SyncLibrary(ctx context.Context, library *Library) error {
path := internal.SubscribedLibraryPath
url := c.Resource(path).WithID(library.ID).WithAction("sync")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// PublishLibrary publishes the library to specified subscriptions.
// If no subscriptions are specified, then publishes the library to all subscriptions.
func (c *Manager) PublishLibrary(ctx context.Context, library *Library, subscriptions []string) error {
path := internal.LocalLibraryPath
var spec internal.SubscriptionDestinationSpec
for i := range subscriptions {
spec.Subscriptions = append(spec.Subscriptions, internal.SubscriptionDestination{ID: subscriptions[i]})
}
url := c.Resource(path).WithID(library.ID).WithAction("publish")
return c.Do(ctx, url.Request(http.MethodPost, spec), nil)
}
// DeleteLibrary deletes an existing library.
func (c *Manager) DeleteLibrary(ctx context.Context, library *Library) error {
path := internal.LocalLibraryPath
if library.Type == "SUBSCRIBED" {
path = internal.SubscribedLibraryPath
}
url := c.Resource(path).WithID(library.ID)
return c.Do(ctx, url.Request(http.MethodDelete), nil)
}
// ListLibraries returns a list of all content library IDs in the system.
func (c *Manager) ListLibraries(ctx context.Context) ([]string, error) {
url := c.Resource(internal.LibraryPath)
var res []string
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// GetLibraryByID returns information on a library for the given ID.
func (c *Manager) GetLibraryByID(ctx context.Context, id string) (*Library, error) {
url := c.Resource(internal.LibraryPath).WithID(id)
var res Library
return &res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// GetLibraryByName returns information on a library for the given name.
func (c *Manager) GetLibraryByName(ctx context.Context, name string) (*Library, error) {
// Lookup by name
libraries, err := c.GetLibraries(ctx)
if err != nil {
return nil, err
}
for i := range libraries {
if libraries[i].Name == name {
return &libraries[i], nil
}
}
return nil, fmt.Errorf("library name (%s) not found", name)
}
// GetLibraries returns a list of all content library details in the system.
func (c *Manager) GetLibraries(ctx context.Context) ([]Library, error) {
ids, err := c.ListLibraries(ctx)
if err != nil {
return nil, fmt.Errorf("get libraries failed for: %s", err)
}
var libraries []Library
for _, id := range ids {
library, err := c.GetLibraryByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("get library %s failed for %s", id, err)
}
libraries = append(libraries, *library)
}
return libraries, nil
}
// ListSubscribers lists the subscriptions of the published library.
func (c *Manager) ListSubscribers(ctx context.Context, library *Library) ([]SubscriberSummary, error) {
url := c.Resource(internal.Subscriptions).WithParam("library", library.ID)
var res []SubscriberSummary
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// CreateSubscriber creates a subscription of the published library.
func (c *Manager) CreateSubscriber(ctx context.Context, library *Library, s SubscriberLibrary) (string, error) {
var spec struct {
Sub struct {
SubscriberLibrary SubscriberLibrary `json:"subscribed_library"`
} `json:"spec"`
}
spec.Sub.SubscriberLibrary = s
url := c.Resource(internal.Subscriptions).WithID(library.ID)
var res string
return res, c.Do(ctx, url.Request(http.MethodPost, &spec), &res)
}
// GetSubscriber returns information about the specified subscriber of the published library.
func (c *Manager) GetSubscriber(ctx context.Context, library *Library, subscriber string) (*Subscriber, error) {
id := internal.SubscriptionDestination{ID: subscriber}
url := c.Resource(internal.Subscriptions).WithID(library.ID).WithAction("get")
var res Subscriber
return &res, c.Do(ctx, url.Request(http.MethodPost, &id), &res)
}
// DeleteSubscriber deletes the specified subscription of the published library.
// The subscribed library associated with the subscription will not be deleted.
func (c *Manager) DeleteSubscriber(ctx context.Context, library *Library, subscriber string) error {
id := internal.SubscriptionDestination{ID: subscriber}
url := c.Resource(internal.Subscriptions).WithID(library.ID).WithAction("delete")
return c.Do(ctx, url.Request(http.MethodPost, &id), nil)
}

View file

@ -0,0 +1,56 @@
/*
Copyright (c) 2018 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 library
import (
"context"
"net/http"
"github.com/vmware/govmomi/vapi/internal"
)
// Checksum provides checksum information on library item files.
type Checksum struct {
Algorithm string `json:"algorithm,omitempty"`
Checksum string `json:"checksum"`
}
// File provides methods to get information on library item files.
type File struct {
Cached *bool `json:"cached,omitempty"`
Checksum *Checksum `json:"checksum_info,omitempty"`
Name string `json:"name,omitempty"`
Size *int64 `json:"size,omitempty"`
Version string `json:"version,omitempty"`
}
// ListLibraryItemFiles returns a list of all the files for a library item.
func (c *Manager) ListLibraryItemFiles(ctx context.Context, id string) ([]File, error) {
url := c.Resource(internal.LibraryItemFilePath).WithParam("library_item_id", id)
var res []File
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// GetLibraryItemFile returns a file with the provided name for a library item.
func (c *Manager) GetLibraryItemFile(ctx context.Context, id, fileName string) (*File, error) {
url := c.Resource(internal.LibraryItemFilePath).WithID(id).WithAction("get")
spec := struct {
Name string `json:"name"`
}{fileName}
var res File
return &res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}

View file

@ -0,0 +1,180 @@
/*
Copyright (c) 2018 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 library
import (
"context"
"fmt"
"net/http"
"time"
"github.com/vmware/govmomi/vapi/internal"
)
const (
ItemTypeISO = "iso"
ItemTypeOVF = "ovf"
ItemTypeVMTX = "vm-template"
)
// Item provides methods to create, read, update, delete, and enumerate library items.
type Item struct {
Cached bool `json:"cached,omitempty"`
ContentVersion string `json:"content_version,omitempty"`
CreationTime *time.Time `json:"creation_time,omitempty"`
Description string `json:"description,omitempty"`
ID string `json:"id,omitempty"`
LastModifiedTime *time.Time `json:"last_modified_time,omitempty"`
LastSyncTime *time.Time `json:"last_sync_time,omitempty"`
LibraryID string `json:"library_id,omitempty"`
MetadataVersion string `json:"metadata_version,omitempty"`
Name string `json:"name,omitempty"`
Size int64 `json:"size,omitempty"`
SourceID string `json:"source_id,omitempty"`
Type string `json:"type,omitempty"`
Version string `json:"version,omitempty"`
}
// Patch merges updates from the given src.
func (i *Item) Patch(src *Item) {
if src.Name != "" {
i.Name = src.Name
}
if src.Description != "" {
i.Description = src.Description
}
if src.Type != "" {
i.Type = src.Type
}
if src.Version != "" {
i.Version = src.Version
}
}
// CreateLibraryItem creates a new library item
func (c *Manager) CreateLibraryItem(ctx context.Context, item Item) (string, error) {
type createItemSpec struct {
Name string `json:"name"`
Description string `json:"description"`
LibraryID string `json:"library_id,omitempty"`
Type string `json:"type"`
}
spec := struct {
Item createItemSpec `json:"create_spec"`
}{
Item: createItemSpec{
Name: item.Name,
Description: item.Description,
LibraryID: item.LibraryID,
Type: item.Type,
},
}
url := c.Resource(internal.LibraryItemPath)
var res string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}
// CopyLibraryItem copies a library item
func (c *Manager) CopyLibraryItem(ctx context.Context, src *Item, dst Item) (string, error) {
body := struct {
Item `json:"destination_create_spec"`
}{dst}
url := c.Resource(internal.LibraryItemPath).WithID(src.ID).WithAction("copy")
var res string
return res, c.Do(ctx, url.Request(http.MethodPost, body), &res)
}
// SyncLibraryItem syncs a subscribed library item
func (c *Manager) SyncLibraryItem(ctx context.Context, item *Item, force bool) error {
body := struct {
Force bool `json:"force_sync_content"`
}{force}
url := c.Resource(internal.SubscribedLibraryItem).WithID(item.ID).WithAction("sync")
return c.Do(ctx, url.Request(http.MethodPost, body), nil)
}
// PublishLibraryItem publishes a library item to specified subscriptions.
// If no subscriptions are specified, then publishes the library item to all subscriptions.
func (c *Manager) PublishLibraryItem(ctx context.Context, item *Item, force bool, subscriptions []string) error {
body := internal.SubscriptionItemDestinationSpec{
Force: force,
}
for i := range subscriptions {
body.Subscriptions = append(body.Subscriptions, internal.SubscriptionDestination{ID: subscriptions[i]})
}
url := c.Resource(internal.LibraryItemPath).WithID(item.ID).WithAction("publish")
return c.Do(ctx, url.Request(http.MethodPost, body), nil)
}
// DeleteLibraryItem deletes an existing library item.
func (c *Manager) DeleteLibraryItem(ctx context.Context, item *Item) error {
url := c.Resource(internal.LibraryItemPath).WithID(item.ID)
return c.Do(ctx, url.Request(http.MethodDelete), nil)
}
// ListLibraryItems returns a list of all items in a content library.
func (c *Manager) ListLibraryItems(ctx context.Context, id string) ([]string, error) {
url := c.Resource(internal.LibraryItemPath).WithParam("library_id", id)
var res []string
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// GetLibraryItem returns information on a library item for the given ID.
func (c *Manager) GetLibraryItem(ctx context.Context, id string) (*Item, error) {
url := c.Resource(internal.LibraryItemPath).WithID(id)
var res Item
return &res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// GetLibraryItems returns a list of all the library items for the specified library.
func (c *Manager) GetLibraryItems(ctx context.Context, libraryID string) ([]Item, error) {
ids, err := c.ListLibraryItems(ctx, libraryID)
if err != nil {
return nil, fmt.Errorf("get library items failed for: %s", err)
}
var items []Item
for _, id := range ids {
item, err := c.GetLibraryItem(ctx, id)
if err != nil {
return nil, fmt.Errorf("get library item for %s failed for %s", id, err)
}
items = append(items, *item)
}
return items, nil
}
// FindItem is the search criteria for finding library items.
type FindItem struct {
Cached *bool `json:"cached,omitempty"`
LibraryID string `json:"library_id,omitempty"`
Name string `json:"name,omitempty"`
SourceID string `json:"source_id,omitempty"`
Type string `json:"type,omitempty"`
}
// FindLibraryItems returns the IDs of all the library items that match the
// search criteria.
func (c *Manager) FindLibraryItems(
ctx context.Context, search FindItem) ([]string, error) {
url := c.Resource(internal.LibraryItemPath).WithAction("find")
spec := struct {
Spec FindItem `json:"spec"`
}{search}
var res []string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}

View file

@ -0,0 +1,71 @@
/*
Copyright (c) 2018 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 library
import (
"context"
"net/http"
"github.com/vmware/govmomi/vapi/internal"
"github.com/vmware/govmomi/vapi/rest"
)
// DownloadFile is the specification for the downloadsession
// operations file:add, file:get, and file:list.
type DownloadFile struct {
BytesTransferred int64 `json:"bytes_transferred"`
Checksum *Checksum `json:"checksum_info,omitempty"`
DownloadEndpoint *TransferEndpoint `json:"download_endpoint,omitempty"`
ErrorMessage *rest.LocalizableMessage `json:"error_message,omitempty"`
Name string `json:"name"`
Size int64 `json:"size,omitempty"`
Status string `json:"status"`
}
// GetLibraryItemDownloadSessionFile retrieves information about a specific file that is a part of an download session.
func (c *Manager) GetLibraryItemDownloadSessionFile(ctx context.Context, sessionID string, name string) (*DownloadFile, error) {
url := c.Resource(internal.LibraryItemDownloadSessionFile).WithID(sessionID).WithAction("get")
spec := struct {
Name string `json:"file_name"`
}{name}
var res DownloadFile
err := c.Do(ctx, url.Request(http.MethodPost, spec), &res)
if err != nil {
return nil, err
}
if res.Status == "ERROR" {
return nil, res.ErrorMessage
}
return &res, nil
}
// ListLibraryItemDownloadSessionFile retrieves information about a specific file that is a part of an download session.
func (c *Manager) ListLibraryItemDownloadSessionFile(ctx context.Context, sessionID string) ([]DownloadFile, error) {
url := c.Resource(internal.LibraryItemDownloadSessionFile).WithParam("download_session_id", sessionID)
var res []DownloadFile
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// PrepareLibraryItemDownloadSessionFile retrieves information about a specific file that is a part of an download session.
func (c *Manager) PrepareLibraryItemDownloadSessionFile(ctx context.Context, sessionID string, name string) (*DownloadFile, error) {
url := c.Resource(internal.LibraryItemDownloadSessionFile).WithID(sessionID).WithAction("prepare")
spec := struct {
Name string `json:"file_name"`
}{name}
var res DownloadFile
return &res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}

View file

@ -0,0 +1,165 @@
/*
Copyright (c) 2018 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 library
import (
"context"
"net/http"
"time"
"github.com/vmware/govmomi/vapi/internal"
"github.com/vmware/govmomi/vapi/rest"
)
// Session is used to create an initial update or download session
type Session struct {
ClientProgress int64 `json:"client_progress,omitempty"`
ErrorMessage *rest.LocalizableMessage `json:"error_message,omitempty"`
ExpirationTime *time.Time `json:"expiration_time,omitempty"`
ID string `json:"id,omitempty"`
LibraryItemContentVersion string `json:"library_item_content_version,omitempty"`
LibraryItemID string `json:"library_item_id,omitempty"`
State string `json:"state,omitempty"`
}
// CreateLibraryItemUpdateSession creates a new library item
func (c *Manager) CreateLibraryItemUpdateSession(ctx context.Context, session Session) (string, error) {
url := c.Resource(internal.LibraryItemUpdateSession)
spec := struct {
CreateSpec Session `json:"create_spec"`
}{session}
var res string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}
// GetLibraryItemUpdateSession gets the update session information with status
func (c *Manager) GetLibraryItemUpdateSession(ctx context.Context, id string) (*Session, error) {
url := c.Resource(internal.LibraryItemUpdateSession).WithID(id)
var res Session
return &res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// ListLibraryItemUpdateSession gets the list of update sessions
func (c *Manager) ListLibraryItemUpdateSession(ctx context.Context) ([]string, error) {
url := c.Resource(internal.LibraryItemUpdateSession)
var res []string
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// CancelLibraryItemUpdateSession cancels an update session
func (c *Manager) CancelLibraryItemUpdateSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemUpdateSession).WithID(id).WithAction("cancel")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// CompleteLibraryItemUpdateSession completes an update session
func (c *Manager) CompleteLibraryItemUpdateSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemUpdateSession).WithID(id).WithAction("complete")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// DeleteLibraryItemUpdateSession deletes an update session
func (c *Manager) DeleteLibraryItemUpdateSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemUpdateSession).WithID(id)
return c.Do(ctx, url.Request(http.MethodDelete), nil)
}
// FailLibraryItemUpdateSession fails an update session
func (c *Manager) FailLibraryItemUpdateSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemUpdateSession).WithID(id).WithAction("fail")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// KeepAliveLibraryItemUpdateSession keeps an inactive update session alive.
func (c *Manager) KeepAliveLibraryItemUpdateSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemUpdateSession).WithID(id).WithAction("keep-alive")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// WaitOnLibraryItemUpdateSession blocks until the update session is no longer
// in the ACTIVE state.
func (c *Manager) WaitOnLibraryItemUpdateSession(
ctx context.Context, sessionID string,
interval time.Duration, intervalCallback func()) error {
// Wait until the upload operation is complete to return.
for {
session, err := c.GetLibraryItemUpdateSession(ctx, sessionID)
if err != nil {
return err
}
if session.State != "ACTIVE" {
if session.State == "ERROR" {
return session.ErrorMessage
}
return nil
}
time.Sleep(interval)
if intervalCallback != nil {
intervalCallback()
}
}
}
// CreateLibraryItemDownloadSession creates a new library item
func (c *Manager) CreateLibraryItemDownloadSession(ctx context.Context, session Session) (string, error) {
url := c.Resource(internal.LibraryItemDownloadSession)
spec := struct {
CreateSpec Session `json:"create_spec"`
}{session}
var res string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}
// GetLibraryItemDownloadSession gets the download session information with status
func (c *Manager) GetLibraryItemDownloadSession(ctx context.Context, id string) (*Session, error) {
url := c.Resource(internal.LibraryItemDownloadSession).WithID(id)
var res Session
return &res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// ListLibraryItemDownloadSession gets the list of download sessions
func (c *Manager) ListLibraryItemDownloadSession(ctx context.Context) ([]string, error) {
url := c.Resource(internal.LibraryItemDownloadSession)
var res []string
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
}
// CancelLibraryItemDownloadSession cancels an download session
func (c *Manager) CancelLibraryItemDownloadSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemDownloadSession).WithID(id).WithAction("cancel")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// DeleteLibraryItemDownloadSession deletes an download session
func (c *Manager) DeleteLibraryItemDownloadSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemDownloadSession).WithID(id)
return c.Do(ctx, url.Request(http.MethodDelete), nil)
}
// FailLibraryItemDownloadSession fails an download session
func (c *Manager) FailLibraryItemDownloadSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemDownloadSession).WithID(id).WithAction("fail")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}
// KeepAliveLibraryItemDownloadSession keeps an inactive download session alive.
func (c *Manager) KeepAliveLibraryItemDownloadSession(ctx context.Context, id string) error {
url := c.Resource(internal.LibraryItemDownloadSession).WithID(id).WithAction("keep-alive")
return c.Do(ctx, url.Request(http.MethodPost), nil)
}

View file

@ -0,0 +1,149 @@
/*
Copyright (c) 2018 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 library
import (
"bufio"
"context"
"io"
"net/http"
"strings"
"github.com/vmware/govmomi/vapi/internal"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25/soap"
)
// TransferEndpoint provides information on the source of a library item file.
type TransferEndpoint struct {
URI string `json:"uri,omitempty"`
SSLCertificateThumbprint string `json:"ssl_certificate_thumbprint,omitempty"`
}
// UpdateFile is the specification for the updatesession
// operations file:add, file:get, and file:list.
type UpdateFile struct {
BytesTransferred int64 `json:"bytes_transferred,omitempty"`
Checksum *Checksum `json:"checksum_info,omitempty"`
ErrorMessage *rest.LocalizableMessage `json:"error_message,omitempty"`
Name string `json:"name"`
Size int64 `json:"size,omitempty"`
SourceEndpoint *TransferEndpoint `json:"source_endpoint,omitempty"`
SourceType string `json:"source_type"`
Status string `json:"status,omitempty"`
UploadEndpoint *TransferEndpoint `json:"upload_endpoint,omitempty"`
}
// AddLibraryItemFile adds a file
func (c *Manager) AddLibraryItemFile(ctx context.Context, sessionID string, updateFile UpdateFile) (*UpdateFile, error) {
url := c.Resource(internal.LibraryItemUpdateSessionFile).WithID(sessionID).WithAction("add")
spec := struct {
FileSpec UpdateFile `json:"file_spec"`
}{updateFile}
var res UpdateFile
err := c.Do(ctx, url.Request(http.MethodPost, spec), &res)
if err != nil {
return nil, err
}
if res.Status == "ERROR" {
return nil, res.ErrorMessage
}
return &res, nil
}
// AddLibraryItemFileFromURI adds a file from a remote URI.
func (c *Manager) AddLibraryItemFileFromURI(
ctx context.Context,
sessionID, fileName, uri string) (*UpdateFile, error) {
n, fingerprint, err := c.getContentLengthAndFingerprint(ctx, uri)
if err != nil {
return nil, err
}
info, err := c.AddLibraryItemFile(ctx, sessionID, UpdateFile{
Name: fileName,
SourceType: "PULL",
Size: n,
SourceEndpoint: &TransferEndpoint{
URI: uri,
SSLCertificateThumbprint: fingerprint,
},
})
if err != nil {
return nil, err
}
return info, c.CompleteLibraryItemUpdateSession(ctx, sessionID)
}
// GetLibraryItemUpdateSessionFile retrieves information about a specific file
// that is a part of an update session.
func (c *Manager) GetLibraryItemUpdateSessionFile(ctx context.Context, sessionID string, fileName string) (*UpdateFile, error) {
url := c.Resource(internal.LibraryItemUpdateSessionFile).WithID(sessionID).WithAction("get")
spec := struct {
Name string `json:"file_name"`
}{fileName}
var res UpdateFile
return &res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
}
// getContentLengthAndFingerprint gets the number of bytes returned
// by the URI as well as the SHA1 fingerprint of the peer certificate
// if the URI's scheme is https.
func (c *Manager) getContentLengthAndFingerprint(
ctx context.Context, uri string) (int64, string, error) {
resp, err := c.Head(uri)
if err != nil {
return 0, "", err
}
if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 {
return resp.ContentLength, "", nil
}
fingerprint := c.Thumbprint(resp.Request.URL.Host)
if fingerprint == "" {
if c.DefaultTransport().TLSClientConfig.InsecureSkipVerify {
fingerprint = soap.ThumbprintSHA1(resp.TLS.PeerCertificates[0])
}
}
return resp.ContentLength, fingerprint, nil
}
// ReadManifest converts an ovf manifest to a map of file name -> Checksum.
func ReadManifest(m io.Reader) (map[string]*Checksum, error) {
// expected format: openssl sha1 *.{ovf,vmdk}
c := make(map[string]*Checksum)
scanner := bufio.NewScanner(m)
for scanner.Scan() {
line := strings.SplitN(scanner.Text(), ")=", 2)
if len(line) != 2 {
continue
}
name := strings.SplitN(line[0], "(", 2)
if len(name) != 2 {
continue
}
sum := &Checksum{
Algorithm: strings.TrimSpace(name[0]),
Checksum: strings.TrimSpace(line[1]),
}
c[name[1]] = sum
}
return c, scanner.Err()
}

289
vendor/github.com/vmware/govmomi/vapi/rest/client.go generated vendored Normal file
View file

@ -0,0 +1,289 @@
/*
Copyright (c) 2018 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 rest
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"sync"
"time"
"github.com/vmware/govmomi/vapi/internal"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
)
// Client extends soap.Client to support JSON encoding, while inheriting security features, debug tracing and session persistence.
type Client struct {
mu sync.Mutex
*soap.Client
sessionID string
}
// Session information
type Session struct {
User string `json:"user"`
Created time.Time `json:"created_time"`
LastAccessed time.Time `json:"last_accessed_time"`
}
// LocalizableMessage represents a localizable error
type LocalizableMessage struct {
Args []string `json:"args,omitempty"`
DefaultMessage string `json:"default_message,omitempty"`
ID string `json:"id,omitempty"`
}
func (m *LocalizableMessage) Error() string {
return m.DefaultMessage
}
// NewClient creates a new Client instance.
func NewClient(c *vim25.Client) *Client {
sc := c.Client.NewServiceClient(Path, "")
return &Client{Client: sc}
}
// SessionID is set by calling Login() or optionally with the given id param
func (c *Client) SessionID(id ...string) string {
c.mu.Lock()
defer c.mu.Unlock()
if len(id) != 0 {
c.sessionID = id[0]
}
return c.sessionID
}
type marshaledClient struct {
SoapClient *soap.Client
SessionID string
}
func (c *Client) MarshalJSON() ([]byte, error) {
m := marshaledClient{
SoapClient: c.Client,
SessionID: c.sessionID,
}
return json.Marshal(m)
}
func (c *Client) UnmarshalJSON(b []byte) error {
var m marshaledClient
err := json.Unmarshal(b, &m)
if err != nil {
return err
}
*c = Client{
Client: m.SoapClient,
sessionID: m.SessionID,
}
return nil
}
// Resource helper for the given path.
func (c *Client) Resource(path string) *Resource {
r := &Resource{u: c.URL()}
r.u.Path = Path + path
return r
}
type Signer interface {
SignRequest(*http.Request) error
}
type signerContext struct{}
func (c *Client) WithSigner(ctx context.Context, s Signer) context.Context {
return context.WithValue(ctx, signerContext{}, s)
}
type statusError struct {
res *http.Response
}
func (e *statusError) Error() string {
return fmt.Sprintf("%s %s: %s", e.res.Request.Method, e.res.Request.URL, e.res.Status)
}
// Do sends the http.Request, decoding resBody if provided.
func (c *Client) Do(ctx context.Context, req *http.Request, resBody interface{}) error {
switch req.Method {
case http.MethodPost, http.MethodPatch:
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", "application/json")
if id := c.SessionID(); id != "" {
req.Header.Set(internal.SessionCookieName, id)
}
if s, ok := ctx.Value(signerContext{}).(Signer); ok {
if err := s.SignRequest(req); err != nil {
return err
}
}
return c.Client.Do(ctx, req, func(res *http.Response) error {
switch res.StatusCode {
case http.StatusOK:
case http.StatusNoContent:
case http.StatusBadRequest:
// TODO: structured error types
detail, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
return fmt.Errorf("%s: %s", res.Status, bytes.TrimSpace(detail))
default:
return &statusError{res}
}
if resBody == nil {
return nil
}
switch b := resBody.(type) {
case io.Writer:
_, err := io.Copy(b, res.Body)
return err
default:
val := struct {
Value interface{} `json:"value,omitempty"`
}{
resBody,
}
return json.NewDecoder(res.Body).Decode(&val)
}
})
}
// authHeaders ensures the given map contains a REST auth header
func (c *Client) authHeaders(h map[string]string) map[string]string {
if _, exists := h[internal.SessionCookieName]; exists {
return h
}
if h == nil {
h = make(map[string]string)
}
h[internal.SessionCookieName] = c.SessionID()
return h
}
// Download wraps soap.Client.Download, adding the REST authentication header
func (c *Client) Download(ctx context.Context, u *url.URL, param *soap.Download) (io.ReadCloser, int64, error) {
p := *param
p.Headers = c.authHeaders(p.Headers)
return c.Client.Download(ctx, u, &p)
}
// DownloadFile wraps soap.Client.DownloadFile, adding the REST authentication header
func (c *Client) DownloadFile(ctx context.Context, file string, u *url.URL, param *soap.Download) error {
p := *param
p.Headers = c.authHeaders(p.Headers)
return c.Client.DownloadFile(ctx, file, u, &p)
}
// Upload wraps soap.Client.Upload, adding the REST authentication header
func (c *Client) Upload(ctx context.Context, f io.Reader, u *url.URL, param *soap.Upload) error {
p := *param
p.Headers = c.authHeaders(p.Headers)
return c.Client.Upload(ctx, f, u, &p)
}
// Login creates a new session via Basic Authentication with the given url.Userinfo.
func (c *Client) Login(ctx context.Context, user *url.Userinfo) error {
req := c.Resource(internal.SessionPath).Request(http.MethodPost)
req.Header.Set(internal.UseHeaderAuthn, "true")
if user != nil {
if password, ok := user.Password(); ok {
req.SetBasicAuth(user.Username(), password)
}
}
var id string
err := c.Do(ctx, req, &id)
if err != nil {
return err
}
c.SessionID(id)
return nil
}
func (c *Client) LoginByToken(ctx context.Context) error {
return c.Login(ctx, nil)
}
// Session returns the user's current session.
// Nil is returned if the session is not authenticated.
func (c *Client) Session(ctx context.Context) (*Session, error) {
var s Session
req := c.Resource(internal.SessionPath).WithAction("get").Request(http.MethodPost)
err := c.Do(ctx, req, &s)
if err != nil {
if e, ok := err.(*statusError); ok {
if e.res.StatusCode == http.StatusUnauthorized {
return nil, nil
}
}
return nil, err
}
return &s, nil
}
// Logout deletes the current session.
func (c *Client) Logout(ctx context.Context) error {
req := c.Resource(internal.SessionPath).Request(http.MethodDelete)
return c.Do(ctx, req, nil)
}
// Valid returns whether or not the client is valid and ready for use.
// This should be called after unmarshalling the client.
func (c *Client) Valid() bool {
if c == nil {
return false
}
if c.Client == nil {
return false
}
return true
}
// Path returns rest.Path (see cache.Client)
func (c *Client) Path() string {
return Path
}

89
vendor/github.com/vmware/govmomi/vapi/rest/resource.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
/*
Copyright (c) 2019 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 rest
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
)
const (
Path = "/rest"
)
// Resource wraps url.URL with helpers
type Resource struct {
u *url.URL
}
func (r *Resource) String() string {
return r.u.String()
}
// WithID appends id to the URL.Path
func (r *Resource) WithID(id string) *Resource {
r.u.Path += "/id:" + id
return r
}
// WithAction sets adds action to the URL.RawQuery
func (r *Resource) WithAction(action string) *Resource {
return r.WithParam("~action", action)
}
// WithParam sets adds a parameter to the URL.RawQuery
func (r *Resource) WithParam(name string, value string) *Resource {
r.u.RawQuery = url.Values{
name: []string{value},
}.Encode()
return r
}
// Request returns a new http.Request for the given method.
// An optional body can be provided for POST and PATCH methods.
func (r *Resource) Request(method string, body ...interface{}) *http.Request {
rdr := io.MultiReader() // empty body by default
if len(body) != 0 {
rdr = encode(body[0])
}
req, err := http.NewRequest(method, r.u.String(), rdr)
if err != nil {
panic(err)
}
return req
}
type errorReader struct {
e error
}
func (e errorReader) Read([]byte) (int, error) {
return -1, e.e
}
// encode body as JSON, deferring any errors until io.Reader is used.
func encode(body interface{}) io.Reader {
var b bytes.Buffer
err := json.NewEncoder(&b).Encode(body)
if err != nil {
return errorReader{err}
}
return &b
}