/* 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 vcenter import ( "context" "crypto/sha1" "fmt" "log" "net/http" "path" "github.com/vmware/govmomi/vapi/internal" "github.com/vmware/govmomi/vapi/library" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) // vcenter vm template // The vcenter.vm_template API provides structures and services that will let its client manage VMTX template in Content Library. // http://vmware.github.io/vsphere-automation-sdk-rest/6.7.1/index.html#SVC_com.vmware.vcenter.vm_template.library_items // Template create spec type Template struct { Description string `json:"description,omitempty"` DiskStorage *DiskStorage `json:"disk_storage,omitempty"` DiskStorageOverrides []DiskStorageOverride `json:"disk_storage_overrides,omitempty"` Library string `json:"library,omitempty"` Name string `json:"name,omitempty"` Placement *Placement `json:"placement,omitempty"` SourceVM string `json:"source_vm,omitempty"` VMHomeStorage *DiskStorage `json:"vm_home_storage,omitempty"` } // CPU defines Cores and CPU count type CPU struct { CoresPerSocket int `json:"cores_per_socket,omitempty"` Count int `json:"count,omitempty"` } // DiskInfo defines disk capacity and storage info type DiskInfo struct { Capacity int `json:"capacity,omitempty"` DiskStorage DiskStorage `json:"disk_storage,omitempty"` } // Disks defines the disk information type Disks struct { Key string `json:"key"` Value *DiskInfo `json:"value"` } // Memory defines the memory size in MB type Memory struct { SizeMB int `json:"size_mib,omitempty"` } // NicDetails defines the network adapter details type NicDetails struct { Network string `json:"network,omitempty"` BackingType string `json:"backing_type,omitempty"` MacType string `json:"mac_type,omitempty"` } // Nics defines the network identifier type Nics struct { Key string `json:"key,omitempty"` Value *NicDetails `json:"value,omitempty"` } // TemplateInfo for a VM template contained in an existing library item type TemplateInfo struct { CPU CPU `json:"cpu,omitempty"` Disks []Disks `json:"disks,omitempty"` GuestOS string `json:"guest_OS,omitempty"` Memory Memory `json:"memory,omitempty"` Nics []Nics `json:"nics,omitempty"` VMHomeStorage DiskStorage `json:"vm_home_storage,omitempty"` VmTemplate string `json:"vm_template,omitempty"` } // Placement information used to place the virtual machine template type Placement = library.Placement // StoragePolicy for DiskStorage type StoragePolicy struct { Policy string `json:"policy,omitempty"` Type string `json:"type"` } // DiskStorage defines the storage specification for VM files type DiskStorage struct { Datastore string `json:"datastore,omitempty"` StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"` } // DiskStorageOverride storage specification for individual disks in the virtual machine template type DiskStorageOverride struct { Key string `json:"key"` Value DiskStorage `json:"value"` } // GuestCustomization spec to apply to the deployed VM type GuestCustomization struct { Name string `json:"name,omitempty"` } // HardwareCustomization spec which specifies updates to the deployed VM type HardwareCustomization struct { // TODO } // DeployTemplate specification of how a library VM template clone should be deployed. type DeployTemplate struct { Description string `json:"description,omitempty"` DiskStorage *DiskStorage `json:"disk_storage,omitempty"` DiskStorageOverrides []DiskStorageOverride `json:"disk_storage_overrides,omitempty"` GuestCustomization *GuestCustomization `json:"guest_customization,omitempty"` HardwareCustomization *HardwareCustomization `json:"hardware_customization,omitempty"` Name string `json:"name,omitempty"` Placement *Placement `json:"placement,omitempty"` PoweredOn bool `json:"powered_on"` VMHomeStorage *DiskStorage `json:"vm_home_storage,omitempty"` } // CheckOut specification type CheckOut struct { Name string `json:"name,omitempty"` Placement *Placement `json:"placement,omitempty"` PoweredOn bool `json:"powered_on,omitempty"` } // CheckIn specification type CheckIn struct { Message string `json:"message"` } // CreateTemplate creates a library VMTX item in content library from an existing VM func (c *Manager) CreateTemplate(ctx context.Context, vmtx Template) (string, error) { url := c.Resource(internal.VCenterVMTXLibraryItem) var res string spec := struct { Template `json:"spec"` }{vmtx} return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res) } // GetLibraryTemplateInfo fetches the library template info using template library id func (c *Manager) GetLibraryTemplateInfo(ctx context.Context, libraryItemID string) (*TemplateInfo, error) { url := c.Resource(path.Join(internal.VCenterVMTXLibraryItem, libraryItemID)) var res TemplateInfo err := c.Do(ctx, url.Request(http.MethodGet), &res) if err != nil { return nil, err } return &res, nil } // DeployTemplateLibraryItem deploys a VM as a copy of the source VM template contained in the given library item func (c *Manager) DeployTemplateLibraryItem(ctx context.Context, libraryItemID string, deploy DeployTemplate) (*types.ManagedObjectReference, error) { url := c.Resource(path.Join(internal.VCenterVMTXLibraryItem, libraryItemID)).WithParam("action", "deploy") var res string spec := struct { DeployTemplate `json:"spec"` }{deploy} err := c.Do(ctx, url.Request(http.MethodPost, spec), &res) if err != nil { return nil, err } return &types.ManagedObjectReference{Type: "VirtualMachine", Value: res}, nil } // CheckOut a library item containing a VM template. func (c *Manager) CheckOut(ctx context.Context, libraryItemID string, checkout *CheckOut) (*types.ManagedObjectReference, error) { url := c.Resource(path.Join(internal.VCenterVMTXLibraryItem, libraryItemID, "check-outs")).WithParam("action", "check-out") var res string spec := struct { *CheckOut `json:"spec"` }{checkout} err := c.Do(ctx, url.Request(http.MethodPost, spec), &res) if err != nil { return nil, err } return &types.ManagedObjectReference{Type: "VirtualMachine", Value: res}, nil } // CheckIn a VM into the library item. func (c *Manager) CheckIn(ctx context.Context, libraryItemID string, vm mo.Reference, checkin *CheckIn) (string, error) { p := path.Join(internal.VCenterVMTXLibraryItem, libraryItemID, "check-outs", vm.Reference().Value) url := c.Resource(p).WithParam("action", "check-in") var res string spec := struct { *CheckIn `json:"spec"` }{checkin} return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res) } // TemplateLibrary params for synchronizing subscription library OVF items to VM Template items type TemplateLibrary struct { Source library.Library Destination library.Library Placement Target Include func(library.Item, *library.Item) bool SyncItem func(context.Context, library.Item, *Deploy, *Template) error } func (c *Manager) includeTemplateLibraryItem(src library.Item, dst *library.Item) bool { return dst == nil } // SyncTemplateLibraryItem deploys an Library OVF item from which a VM template (vmtx) Library item is created. // The deployed VM is deleted after being converted to a Library vmtx item. func (c *Manager) SyncTemplateLibraryItem(ctx context.Context, item library.Item, deploy *Deploy, spec *Template) error { destroy := false if spec.SourceVM == "" { ref, err := c.DeployLibraryItem(ctx, item.ID, *deploy) if err != nil { return err } destroy = true spec.SourceVM = ref.Value } _, err := c.CreateTemplate(ctx, *spec) if destroy { // Delete source VM regardless of CreateTemplate result url := c.Resource("/vcenter/vm/" + spec.SourceVM) derr := c.Do(ctx, url.Request(http.MethodDelete), nil) if derr != nil { if err == nil { // Return Delete error if CreateTemplate was successful return derr } // Return CreateTemplate error and just log Delete error log.Printf("destroy %s: %s", spec.SourceVM, derr) } } return err } func vmtxSourceName(l library.Library, item library.Item) string { sum := sha1.Sum([]byte(path.Join(l.Name, item.Name))) return fmt.Sprintf("vmtx-src-%x", sum) } // SyncTemplateLibrary converts TemplateLibrary.Source OVF items to VM Template items within TemplateLibrary.Destination // The optional TemplateLibrary.Include func can be used to filter which items are synced. // By default all items that don't exist in the Destination library are synced. // The optional TemplateLibrary.SyncItem func can be used to change how the item is synced, by default SyncTemplateLibraryItem is used. func (c *Manager) SyncTemplateLibrary(ctx context.Context, l TemplateLibrary, items ...library.Item) error { m := library.NewManager(c.Client) var err error if len(items) == 0 { items, err = m.GetLibraryItems(ctx, l.Source.ID) if err != nil { return err } } templates, err := m.GetLibraryItems(ctx, l.Destination.ID) if err != nil { return err } existing := make(map[string]*library.Item) for i := range templates { existing[templates[i].Name] = &templates[i] } include := l.Include if include == nil { include = c.includeTemplateLibraryItem } sync := l.SyncItem if sync == nil { sync = c.SyncTemplateLibraryItem } for _, item := range items { if item.Type != library.ItemTypeOVF { continue } // Deploy source VM from library ovf item deploy := Deploy{ DeploymentSpec: DeploymentSpec{ Name: vmtxSourceName(l.Destination, item), DefaultDatastoreID: l.Destination.Storage[0].DatastoreID, AcceptAllEULA: true, }, Target: l.Placement, } // Create library vmtx item from source VM storage := &DiskStorage{ Datastore: deploy.DeploymentSpec.DefaultDatastoreID, } spec := Template{ Name: item.Name, Library: l.Destination.ID, DiskStorage: storage, VMHomeStorage: storage, Placement: &Placement{ Folder: deploy.Target.FolderID, ResourcePool: deploy.Target.ResourcePoolID, }, } if !l.Include(item, existing[item.Name]) { continue } if err = sync(ctx, item, &deploy, &spec); err != nil { return err } } return nil }