Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vApp template capture support #652

Merged
merged 10 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changes/v2.23.0/652-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* Added public method `VApp.GetParentVDC` to retrieve parent VDC of vApp (previously it was private)
[GH-652]
* Added methods `Catalog.CaptureVappTemplate`, `Catalog.CaptureVappTemplateAsync` and type
`types.CaptureVAppParams` that add support for creating catalog template from existing vApp
[GH-652]
* Added method `Org.GetVAppByHref` to retrieve a vApp by given HREF [GH-652]
* Added methods `VAppTemplate.GetCatalogItemHref` and `VAppTemplate.GetCatalogItemId` that can return
related catalog item ID and HREF [GH-652]
47 changes: 47 additions & 0 deletions govcd/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,53 @@ func (cat *Catalog) UploadOvfByLink(ovfUrl, itemName, description string) (Task,
return task, nil
}

// CaptureVappTemplate captures a vApp template from an existing vApp
func (cat *Catalog) CaptureVappTemplate(captureParams *types.CaptureVAppParams) (*VAppTemplate, error) {
task, err := cat.CaptureVappTemplateAsync(captureParams)
if err != nil {
return nil, err
}

err = task.WaitTaskCompletion()
if err != nil {
return nil, err
}

if task.Task == nil || task.Task.Owner == nil || task.Task.Owner.HREF == "" {
return nil, fmt.Errorf("task does not have Owner HREF populated: %#v", task)
}

// After the task is finished, owner field contains the resulting vApp template
return cat.GetVappTemplateByHref(task.Task.Owner.HREF)
}

// CaptureVappTemplateAsync triggers vApp template capturing task and returns it
//
// Note. If 'CaptureVAppParams.CopyTpmOnInstantiate' is set, it will be unset for VCD versions
// before 10.4.2 as it would break API call
func (cat *Catalog) CaptureVappTemplateAsync(captureParams *types.CaptureVAppParams) (Task, error) {
util.Logger.Printf("[TRACE] Capturing vApp template to catalog %s", cat.Catalog.Name)
if captureParams == nil {
return Task{}, fmt.Errorf("input CaptureVAppParams cannot be nil")
}

captureTemplateHref := cat.client.VCDHREF
captureTemplateHref.Path += fmt.Sprintf("/catalog/%s/action/captureVApp", extractUuid(cat.Catalog.ID))

captureParams.Xmlns = types.XMLNamespaceVCloud
captureParams.XmlnsNs0 = types.XMLNamespaceOVF

util.Logger.Printf("[TRACE] Url for capturing vApp template: %s", captureTemplateHref.String())

if cat.client.APIVCDMaxVersionIs("< 37.2") {
captureParams.CopyTpmOnInstantiate = nil
util.Logger.Println("[TRACE] Explicitly unsetting 'CopyTpmOnInstantiate' because it was not supported before VCD 10.4.2")
}

return cat.client.ExecuteTaskRequest(captureTemplateHref.String(), http.MethodPost,
types.MimeCaptureVappTemplateParams, "error capturing vApp Template: %s", captureParams)
}

// Upload files for vCD created upload links. Different approach then vmdk file are
// chunked (e.g. test.vmdk.000000000, test.vmdk.000000001 or test.vmdk). vmdk files are chunked if
// in description file attribute ChunkSize is not zero.
Expand Down
34 changes: 34 additions & 0 deletions govcd/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1490,3 +1490,37 @@ func (vcd *TestVCD) Test_CatalogCreateCompleteness(check *C) {
err = catalog.Delete(true, true)
check.Assert(err, IsNil)
}

func (vcd *TestVCD) Test_CaptureVapp(check *C) {
fmt.Printf("Running: %s\n", check.TestName())

vapp, vm := createNsxtVAppAndVm(vcd, check)
check.Assert(vapp, NotNil)
check.Assert(vm, NotNil)

// retrieve NSX-T Catalog
cat, err := vcd.org.GetCatalogByName(vcd.config.VCD.Catalog.NsxtBackedCatalogName, false)
check.Assert(err, IsNil)
check.Assert(cat, NotNil)

vAppCaptureParams := &types.CaptureVAppParams{
Name: check.TestName() + "vm-template",
Source: &types.Reference{
HREF: vapp.VApp.HREF,
},
CustomizationSection: types.CaptureVAppParamsCustomizationSection{
Info: "CustomizeOnInstantiate Settings",
CustomizeOnInstantiate: true,
},
CopyTpmOnInstantiate: addrOf(false),
}

templ, err := cat.CaptureVappTemplate(vAppCaptureParams)
check.Assert(err, IsNil)
check.Assert(templ, NotNil)

err = templ.Delete()
check.Assert(err, IsNil)

AddToCleanupList(templ.VAppTemplate.Name, "catalogItem", vcd.org.Org.Name+"|"+vcd.config.VCD.Catalog.NsxtBackedCatalogName, check.TestName())
}
77 changes: 77 additions & 0 deletions govcd/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,83 @@ func (vcd *TestVCD) checkSkipWhenApiToken(check *C) {
}
}

func createNsxtVAppAndVm(vcd *TestVCD, check *C) (*VApp, *VM) {
cat, err := vcd.org.GetCatalogByName(vcd.config.VCD.Catalog.NsxtBackedCatalogName, false)
check.Assert(err, IsNil)
check.Assert(cat, NotNil)
// Populate Catalog Item
catitem, err := cat.GetCatalogItemByName(vcd.config.VCD.Catalog.NsxtCatalogItem, false)
check.Assert(err, IsNil)
check.Assert(catitem, NotNil)
// Get VAppTemplate
vapptemplate, err := catitem.GetVAppTemplate()
check.Assert(err, IsNil)
check.Assert(vapptemplate.VAppTemplate.Children.VM[0].HREF, NotNil)

return createNsxtVAppAndVmFromCustomTemplate(vcd, check, &vapptemplate)
}

func createNsxtVAppAndVmFromCustomTemplate(vcd *TestVCD, check *C, vapptemplate *VAppTemplate) (*VApp, *VM) {
vapp, err := vcd.nsxtVdc.CreateRawVApp(check.TestName(), check.TestName())
check.Assert(err, IsNil)
check.Assert(vapp, NotNil)
// After a successful creation, the entity is added to the cleanup list.
AddToCleanupList(vapp.VApp.Name, "vapp", vcd.nsxtVdc.Vdc.Name, check.TestName())

// Check that vApp is powered-off
vappStatus, err := vapp.GetStatus()
check.Assert(err, IsNil)
check.Assert(vappStatus, Equals, "RESOLVED")

task, err := vapp.PowerOn()
check.Assert(err, IsNil)
check.Assert(task, NotNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)

vappStatus, err = vapp.GetStatus()
check.Assert(err, IsNil)
check.Assert(vappStatus, Equals, "POWERED_ON")

// Once the operation is successful, we won't trigger a failure
// until after the vApp deletion
check.Check(vapp.VApp.Name, Equals, check.TestName())
check.Check(vapp.VApp.Description, Equals, check.TestName())

// Construct VM
vmDef := &types.ReComposeVAppParams{
Ovf: types.XMLNamespaceOVF,
Xsi: types.XMLNamespaceXSI,
Xmlns: types.XMLNamespaceVCloud,
AllEULAsAccepted: true,
// Deploy: false,
Name: vapp.VApp.Name,
// PowerOn: false, // Not touching power state at this phase
SourcedItem: &types.SourcedCompositionItemParam{
Source: &types.Reference{
HREF: vapptemplate.VAppTemplate.Children.VM[0].HREF,
Name: check.TestName() + "-vm-tmpl",
},
VMGeneralParams: &types.VMGeneralParams{
Description: "test-vm-description",
},
InstantiationParams: &types.InstantiationParams{
NetworkConnectionSection: &types.NetworkConnectionSection{},
},
},
}
vm, err := vapp.AddRawVM(vmDef)
check.Assert(err, IsNil)
check.Assert(vm, NotNil)
check.Assert(vm.VM.Name, Equals, vmDef.SourcedItem.Source.Name)

// Refresh vApp to have latest state
err = vapp.Refresh()
check.Assert(err, IsNil)

return vapp, vm
}

// makeVappGroup creates multiple vApps, each with several VMs,
// as defined in `groupDefinition`.
// Returns a list of vApps
Expand Down
14 changes: 14 additions & 0 deletions govcd/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,17 @@ func queryCatalogList(client *Client, filterFields map[string]string) ([]*types.
util.Logger.Printf("[DEBUG] QueryCatalogList returned with : %#v and error: %s", catalogs, err)
return catalogs, nil
}

// GetVappByHref returns a vApp reference by running a VCD API call
// If no valid vApp is found, it returns a nil VApp reference and an error
func (org *Org) GetVAppByHref(vappHref string) (*VApp, error) {
newVapp := NewVApp(org.client)

_, err := org.client.ExecuteRequest(vappHref, http.MethodGet,
"", "error retrieving vApp: %s", nil, newVapp.VApp)

if err != nil {
return nil, err
}
return newVapp, nil
}
8 changes: 4 additions & 4 deletions govcd/vapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type DhcpSettings struct {
}

// Returns the vdc where the vapp resides in.
func (vapp *VApp) getParentVDC() (Vdc, error) {
func (vapp *VApp) GetParentVDC() (Vdc, error) {
for _, link := range vapp.VApp.Link {
if (link.Type == types.MimeVDC || link.Type == types.MimeAdminVDC) && link.Rel == "up" {

Expand Down Expand Up @@ -620,7 +620,7 @@ func (vapp *VApp) ChangeStorageProfile(name string) (Task, error) {
return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization")
}

vdc, err := vapp.getParentVDC()
vdc, err := vapp.GetParentVDC()
if err != nil {
return Task{}, fmt.Errorf("error retrieving parent VDC for vApp %s", vapp.VApp.Name)
}
Expand Down Expand Up @@ -1442,7 +1442,7 @@ func (vapp *VApp) getOrgInfo() (*TenantContext, error) {
return previous, nil
}
var err error
vdc, err := vapp.getParentVDC()
vdc, err := vapp.GetParentVDC()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1514,7 +1514,7 @@ func (vapp *VApp) Rename(newName string) error {
}

func (vapp *VApp) getTenantContext() (*TenantContext, error) {
parentVdc, err := vapp.getParentVDC()
parentVdc, err := vapp.GetParentVDC()
if err != nil {
return nil, err
}
Expand Down
21 changes: 20 additions & 1 deletion govcd/vapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,31 @@ func (vcd *TestVCD) TestGetParentVDC(check *C) {
vapp, err := vcd.vdc.GetVAppByName(vcd.vapp.VApp.Name, false)
check.Assert(err, IsNil)

vdc, err := vapp.getParentVDC()
vdc, err := vapp.GetParentVDC()

check.Assert(err, IsNil)
check.Assert(vdc.Vdc.Name, Equals, vcd.vdc.Vdc.Name)
}

func (vcd *TestVCD) TestGetVappByHref(check *C) {
if vcd.skipVappTests {
check.Skip("Skipping test because vApp was not successfully created at setup")
}
vapp, err := vcd.vdc.GetVAppByName(vcd.vapp.VApp.Name, false)
check.Assert(err, IsNil)

vdc, err := vapp.GetParentVDC()
check.Assert(err, IsNil)

orgVappByHref, err := vcd.org.GetVAppByHref(vapp.VApp.HREF)
check.Assert(err, IsNil)
check.Assert(orgVappByHref.VApp, DeepEquals, vapp.VApp)

vdcVappByHref, err := vdc.GetVAppByHref(vapp.VApp.HREF)
check.Assert(err, IsNil)
check.Assert(vdcVappByHref.VApp, DeepEquals, vapp.VApp)
}

// Tests Powering On and Powering Off a VApp. Also tests Deletion
// of a VApp
func (vcd *TestVCD) Test_PowerOn(check *C) {
Expand Down
25 changes: 23 additions & 2 deletions govcd/vapptemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ package govcd

import (
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
"github.com/vmware/go-vcloud-director/v2/util"
"net/http"
"net/url"

"github.com/vmware/go-vcloud-director/v2/types/v56"
"github.com/vmware/go-vcloud-director/v2/util"
)

type VAppTemplate struct {
Expand Down Expand Up @@ -363,3 +364,23 @@ func (vAppTemplate *VAppTemplate) GetLease() (*types.LeaseSettingsSection, error
}
return &leaseSettings, nil
}

// GetCatalogItemHref looks up Href for catalog item in vApp template
func (vAppTemplate *VAppTemplate) GetCatalogItemHref() (string, error) {
for _, link := range vAppTemplate.VAppTemplate.Link {
if link.Rel == "catalogItem" && link.Type == types.MimeCatalogItem {
return link.HREF, nil
}
}
return "", fmt.Errorf("error finding Catalog Item link in vApp template %s", vAppTemplate.VAppTemplate.ID)
}

// GetCatalogItemId returns ID for catalog item in vApp template
func (vAppTemplate *VAppTemplate) GetCatalogItemId() (string, error) {
href, err := vAppTemplate.GetCatalogItemHref()
if err != nil {
return "", err
}

return fmt.Sprintf("urn:vcloud:catalogitem:%s", extractUuid(href)), nil
}
9 changes: 9 additions & 0 deletions govcd/vapptemplate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package govcd

import (
"fmt"

. "gopkg.in/check.v1"
)

Expand Down Expand Up @@ -45,6 +46,14 @@ func (vcd *TestVCD) Test_RefreshVAppTemplate(check *C) {
check.Assert(oldVAppTemplate.VAppTemplate.ID, Equals, vAppTemplate.VAppTemplate.ID)
check.Assert(oldVAppTemplate.VAppTemplate.Name, Equals, vAppTemplate.VAppTemplate.Name)
check.Assert(oldVAppTemplate.VAppTemplate.HREF, Equals, vAppTemplate.VAppTemplate.HREF)

catalogItemHref, err := vAppTemplate.GetCatalogItemHref()
check.Assert(err, IsNil)
check.Assert(catalogItemHref, Not(Equals), "")

catalogItemId, err := vAppTemplate.GetCatalogItemId()
check.Assert(err, IsNil)
check.Assert(catalogItemId, Not(Equals), "")
}

func (vcd *TestVCD) Test_UpdateAndDeleteVAppTemplateFromOvaFile(check *C) {
Expand Down
2 changes: 1 addition & 1 deletion govcd/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ func (vm *VM) GetParentVdc() (*Vdc, error) {
return nil, fmt.Errorf("could not find parent vApp for VM %s: %s", vm.VM.Name, err)
}

vdc, err := vapp.getParentVDC()
vdc, err := vapp.GetParentVDC()
if err != nil {
return nil, fmt.Errorf("could not find parent vApp for VM %s: %s", vm.VM.Name, err)
}
Expand Down
Loading
Loading