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

Create Vapp from template or from other vApp #588

Merged
merged 15 commits into from
Jul 14, 2023
1 change: 1 addition & 0 deletions .changes/v2.21.0/588-deprecations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Deprecate method `Vdc.InstantiateVAppTemplate` (wrong implementation and result) in favor of `Vdc.CreateVappFromTemplate` [GH-588]
3 changes: 3 additions & 0 deletions .changes/v2.21.0/588-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Added method `Vdc.CreateVappFromTemplate` to create a vApp from a vApp template containing one or more VMs [GH-588]
* Added method `Vdc.CloneVapp` to create a vApp from another vApp [GH-588]
* Added method `VApp.DiscardSuspendedState` to take a vApp out of suspended state [GH-588]
21 changes: 20 additions & 1 deletion govcd/vapp.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd
Expand Down Expand Up @@ -379,6 +379,7 @@ func (vapp *VApp) Reset() (Task, error) {
"", "error resetting vApp: %s", nil)
}

// Suspend suspends a vApp
func (vapp *VApp) Suspend() (Task, error) {

apiEndpoint := urlParseRequestURI(vapp.VApp.HREF)
Expand All @@ -389,6 +390,24 @@ func (vapp *VApp) Suspend() (Task, error) {
"", "error suspending vApp: %s", nil)
}

// DiscardSuspendedState takes back a vApp from suspension
func (vapp *VApp) DiscardSuspendedState() error {
// Status 3 means that the vApp is suspended
if vapp.VApp.Status != 3 {
return nil
}
apiEndpoint := urlParseRequestURI(vapp.VApp.HREF)
apiEndpoint.Path += "/action/discardSuspendedState"

// Return the task
task, err := vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
"", "error discarding suspended state for vApp: %s", nil)
if err != nil {
return err
}
return task.WaitTaskCompletion()
}

func (vapp *VApp) Shutdown() (Task, error) {

apiEndpoint := urlParseRequestURI(vapp.VApp.HREF)
Expand Down
143 changes: 143 additions & 0 deletions govcd/vapp_clone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//go:build vapp || functional || ALL
// +build vapp functional ALL

/*
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
. "gopkg.in/check.v1"
"time"
)

// TestVappfromTemplateAndClone creates a vApp with multiple VMs at once, then clones such vApp into a new one
func (vcd *TestVCD) TestVappfromTemplateAndClone(check *C) {
org, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org)
check.Assert(err, IsNil)
check.Assert(org, NotNil)

vdc, err := org.GetVDCByName(vcd.config.VCD.Nsxt.Vdc, false)
check.Assert(err, IsNil)
check.Assert(vdc, NotNil)

name := check.TestName()
description := "test compose raw vApp with template"

catalog, err := org.GetCatalogByName(vcd.config.VCD.Catalog.NsxtBackedCatalogName, false)
check.Assert(err, IsNil)
vappTemplateName := "three-vms"
vappTemplate, err := catalog.GetVAppTemplateByName(vappTemplateName)
if err != nil {
if ContainsNotFound(err) {
check.Skip(fmt.Sprintf("vApp template %s not found - Make sure there is such template in catalog %s -"+
" Using test_resources/vapp_with_3_vms.ova",
vappTemplateName, vcd.config.VCD.Catalog.NsxtBackedCatalogName))
}
}
check.Assert(err, IsNil)
check.Assert(vappTemplate.VAppTemplate.Children, NotNil)
check.Assert(vappTemplate.VAppTemplate.Children.VM, NotNil)

var def = types.InstantiateVAppTemplateParams{
Name: name,
Deploy: true,
PowerOn: true,
Description: description,
Source: &types.Reference{
HREF: vappTemplate.VAppTemplate.HREF,
ID: vappTemplate.VAppTemplate.ID,
},
IsSourceDelete: false,
AllEULAsAccepted: true,
}

start := time.Now()
printVerbose("creating vapp '%s' from template '%s'\n", name, vappTemplateName)
vapp, err := vdc.CreateVappFromTemplate(&def)
check.Assert(err, IsNil)
printVerbose("** created in %s\n", time.Since(start))

AddToCleanupList(name, "vapp", vdc.Vdc.Name, name)

check.Assert(vapp.VApp.Name, Equals, name)
check.Assert(vapp.VApp.Description, Equals, description)

check.Assert(vapp.VApp.Children, NotNil)
check.Assert(vapp.VApp.Children.VM, NotNil)

cloneName := name + "-clone"
cloneDescription := description + " clone"
var defClone = types.CloneVAppParams{
Name: cloneName,
Deploy: true,
PowerOn: true,
Description: cloneDescription,
Source: &types.Reference{
HREF: vapp.VApp.HREF,
Type: vapp.VApp.Type,
},
IsSourceDelete: addrOf(false),
}

start = time.Now()
printVerbose("cloning vapp '%s' from vapp '%s'\n", cloneName, name)
vapp2, err := vdc.CloneVapp(&defClone)
check.Assert(err, IsNil)
printVerbose("** cloned in %s\n", time.Since(start))

AddToCleanupList(cloneName, "vapp", vdc.Vdc.Name, name)

status, err := vapp2.GetStatus()
check.Assert(err, IsNil)
if status == "SUSPENDED" {
printVerbose("\t discarding suspended state for vApp %s\n", vapp2.VApp.Name)
err = vapp2.DiscardSuspendedState()
check.Assert(err, IsNil)
status, err = vapp2.GetStatus()
check.Assert(err, IsNil)
if status != "POWERED_ON" {
printVerbose("\t powering on vApp %s\n", vapp2.VApp.Name)
task, err := vapp2.PowerOn()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}
}

check.Assert(vapp2.VApp.Name, Equals, cloneName)
check.Assert(vapp2.VApp.Description, Equals, cloneDescription)
check.Assert(vapp.VApp.HREF, Not(Equals), vapp2.VApp.HREF)

vappRemove(vapp, check)
vappRemove(vapp2, check)
}

func vappRemove(vapp *VApp, check *C) {
var task Task
var err error
status, err := vapp.GetStatus()
check.Assert(err, IsNil)
if status == "POWERED_ON" {
printVerbose("powering off vApp '%s'\n", vapp.VApp.Name)
task, err = vapp.Undeploy()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}

printVerbose("removing networks from vApp '%s'\n", vapp.VApp.Name)
task, err = vapp.RemoveAllNetworks()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)

printVerbose("removing vApp '%s'\n", vapp.VApp.Name)
task, err = vapp.Delete()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}
2 changes: 2 additions & 0 deletions govcd/vapptemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func NewVAppTemplate(cli *Client) *VAppTemplate {
}
}

// Deprecated: wrong implementation and result
// Use vdc.CreateVappFromTemplate instead
func (vdc *Vdc) InstantiateVAppTemplate(template *types.InstantiateVAppTemplateParams) error {
vdcHref, err := url.ParseRequestURI(vdc.Vdc.HREF)
if err != nil {
Expand Down
72 changes: 71 additions & 1 deletion govcd/vdc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd
Expand Down Expand Up @@ -1272,3 +1272,73 @@ func (vdc *Vdc) getParentOrg() (organization, error) {
}
return nil, fmt.Errorf("no parent found for VDC %s", vdc.Vdc.Name)
}

// CreateVappFromTemplate instantiates a new vApp from a vApp template
// The template argument must contain at least:
// * Name
// * Source (a reference to the source vApp template)
func (vdc *Vdc) CreateVappFromTemplate(template *types.InstantiateVAppTemplateParams) (*VApp, error) {
vdcHref, err := url.ParseRequestURI(vdc.Vdc.HREF)
if err != nil {
return nil, fmt.Errorf("error getting VDC href: %s", err)
}
vdcHref.Path += "/action/instantiateVAppTemplate"

vapp := NewVApp(vdc.client)

template.Xmlns = types.XMLNamespaceVCloud
template.Ovf = types.XMLNamespaceOVF
template.Deploy = true

_, err = vdc.client.ExecuteRequest(vdcHref.String(), http.MethodPost,
types.MimeInstantiateVappTemplateParams, "error instantiating a new vApp from Template: %s", template, vapp.VApp)
if err != nil {
return nil, err
}

task := NewTask(vdc.client)
for _, taskItem := range vapp.VApp.Tasks.Task {
task.Task = taskItem
err = task.WaitTaskCompletion()
if err != nil {
return nil, fmt.Errorf("error performing task: %s", err)
}
}
err = vapp.Refresh()
return vapp, err
}

// CloneVapp makes a copy of a vApp into a new one
// The sourceVapp argument must contain at least:
// * Name
// * Source (a reference to the source vApp)
func (vdc *Vdc) CloneVapp(sourceVapp *types.CloneVAppParams) (*VApp, error) {
vdcHref, err := url.ParseRequestURI(vdc.Vdc.HREF)
if err != nil {
return nil, fmt.Errorf("error getting VDC href: %s", err)
}
vdcHref.Path += "/action/cloneVApp"

vapp := NewVApp(vdc.client)

sourceVapp.Xmlns = types.XMLNamespaceVCloud
sourceVapp.Ovf = types.XMLNamespaceOVF
sourceVapp.Deploy = true

_, err = vdc.client.ExecuteRequest(vdcHref.String(), http.MethodPost,
types.MimeCloneVapp, "error cloning a vApp : %s", sourceVapp, vapp.VApp)
if err != nil {
return nil, err
}

task := NewTask(vdc.client)
for _, taskItem := range vapp.VApp.Tasks.Task {
task.Task = taskItem
err = task.WaitTaskCompletion()
if err != nil {
return nil, fmt.Errorf("error performing task: %s", err)
}
}
err = vapp.Refresh()
return vapp, err
}
4 changes: 3 additions & 1 deletion types/v56/constants.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package types
Expand Down Expand Up @@ -101,6 +101,8 @@ const (
MimeVM = "application/vnd.vmware.vcloud.vm+xml"
// Mime for instantiate vApp template params
MimeInstantiateVappTemplateParams = "application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml"
// Mime for clone vApp template params
MimeCloneVapp = "application/vnd.vmware.vcloud.cloneVAppParams+xml"
// Mime for product section
MimeProductSection = "application/vnd.vmware.vcloud.productSections+xml"
// Mime for metadata
Expand Down
19 changes: 19 additions & 0 deletions types/v56/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,25 @@ type InstantiateVAppTemplateParams struct {
AllEULAsAccepted bool `xml:"AllEULAsAccepted,omitempty"` // True confirms acceptance of all EULAs in a vApp template. Instantiation fails if this element is missing, empty, or set to false and one or more EulaSection elements are present.
}

// CloneVAppParams is used to copy one vApp into another
type CloneVAppParams struct {
XMLName xml.Name `xml:"CloneVAppParams"`
Ovf string `xml:"xmlns:ovf,attr"`
Xsi string `xml:"xmlns:xsi,attr,omitempty"`
Xmlns string `xml:"xmlns,attr"`
// Attributes
Name string `xml:"name,attr,omitempty"` // Typically used to name or identify the subject of the request. For example, the name of the object being created or modified.
Deploy bool `xml:"deploy,attr"` // True if the vApp should be deployed at instantiation. Defaults to true.
PowerOn bool `xml:"powerOn,attr"` // True if the vApp should be powered-on at instantiation. Defaults to true.
LinkedClone bool `xml:"linkedClone,attr,omitempty"` // Reserved. Unimplemented.
// Elements
Description string `xml:"Description,omitempty"` // Optional description.
InstantiationParams *InstantiationParams `xml:"InstantiationParams,omitempty"` // Instantiation parameters for the composed vApp.
Source *Reference `xml:"Source"` // A reference to a source object such as a vApp or vApp template.
IsSourceDelete *bool `xml:"IsSourceDelete"` // Set to true to delete the source object after the operation completes.
SourcedItem *SourcedCompositionItemParam `xml:"SourcedItem,omitempty"` // Composition item. One of: vApp vAppTemplate VM.
}

// EdgeGateway represents a gateway.
// Element: EdgeGateway
// Type: GatewayType
Expand Down