Adds vApp properties config and save public ssh key to a vApp property (#9507)
This commit is contained in:
parent
673858a63c
commit
268e95364f
|
@ -2,6 +2,7 @@ package clone
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/packer/builder/vsphere/common"
|
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||||
|
@ -65,6 +66,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||||
HTTPPortMax: b.config.HTTPPortMax,
|
HTTPPortMax: b.config.HTTPPortMax,
|
||||||
HTTPAddress: b.config.HTTPAddress,
|
HTTPAddress: b.config.HTTPAddress,
|
||||||
},
|
},
|
||||||
|
&common.StepSshKeyPair{
|
||||||
|
Debug: b.config.PackerDebug,
|
||||||
|
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
|
||||||
|
Comm: &b.config.Comm,
|
||||||
|
},
|
||||||
&common.StepRun{
|
&common.StepRun{
|
||||||
Config: &b.config.RunConfig,
|
Config: &b.config.RunConfig,
|
||||||
SetOrder: false,
|
SetOrder: false,
|
||||||
|
|
|
@ -31,6 +31,7 @@ type FlatConfig struct {
|
||||||
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
|
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
|
||||||
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
|
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
|
||||||
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
|
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
|
||||||
|
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
|
||||||
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
|
||||||
Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"`
|
Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"`
|
||||||
Cluster *string `mapstructure:"cluster" cty:"cluster" hcl:"cluster"`
|
Cluster *string `mapstructure:"cluster" cty:"cluster" hcl:"cluster"`
|
||||||
|
@ -147,6 +148,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
|
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
|
||||||
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
||||||
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
|
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
|
||||||
|
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
|
||||||
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
|
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
|
||||||
"folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false},
|
"folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false},
|
||||||
"cluster": &hcldec.AttrSpec{Name: "cluster", Type: cty.String, Required: false},
|
"cluster": &hcldec.AttrSpec{Name: "cluster", Type: cty.String, Required: false},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//go:generate struct-markdown
|
//go:generate struct-markdown
|
||||||
//go:generate mapstructure-to-hcl2 -type CloneConfig
|
//go:generate mapstructure-to-hcl2 -type CloneConfig,vAppConfig
|
||||||
|
|
||||||
package clone
|
package clone
|
||||||
|
|
||||||
|
@ -14,6 +14,17 @@ import (
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type vAppConfig struct {
|
||||||
|
// Set values for the available vApp Properties to supply configuration parameters to a virtual machine cloned from
|
||||||
|
// a template that came from an imported OVF or OVA file.
|
||||||
|
//
|
||||||
|
// -> **Note:** The only supported usage path for vApp properties is for existing user-configurable keys.
|
||||||
|
// These generally come from an existing template that was created from an imported OVF or OVA file.
|
||||||
|
// You cannot set values for vApp properties on virtual machines created from scratch,
|
||||||
|
// virtual machines lacking a vApp configuration, or on property keys that do not exist.
|
||||||
|
Properties map[string]string `mapstructure:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
type CloneConfig struct {
|
type CloneConfig struct {
|
||||||
// Name of source VM. Path is optional.
|
// Name of source VM. Path is optional.
|
||||||
Template string `mapstructure:"template"`
|
Template string `mapstructure:"template"`
|
||||||
|
@ -26,6 +37,10 @@ type CloneConfig struct {
|
||||||
Network string `mapstructure:"network"`
|
Network string `mapstructure:"network"`
|
||||||
// VM notes.
|
// VM notes.
|
||||||
Notes string `mapstructure:"notes"`
|
Notes string `mapstructure:"notes"`
|
||||||
|
// Set the vApp Options to a virtual machine.
|
||||||
|
// See the [vApp Options Configuration](/docs/builders/vmware/vsphere-clone#vapp-options-configuration)
|
||||||
|
// to know the available options and how to use it.
|
||||||
|
VAppConfig vAppConfig `mapstructure:"vapp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloneConfig) Prepare() []error {
|
func (c *CloneConfig) Prepare() []error {
|
||||||
|
@ -67,15 +82,16 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
|
||||||
}
|
}
|
||||||
|
|
||||||
vm, err := template.Clone(ctx, &driver.CloneConfig{
|
vm, err := template.Clone(ctx, &driver.CloneConfig{
|
||||||
Name: s.Location.VMName,
|
Name: s.Location.VMName,
|
||||||
Folder: s.Location.Folder,
|
Folder: s.Location.Folder,
|
||||||
Cluster: s.Location.Cluster,
|
Cluster: s.Location.Cluster,
|
||||||
Host: s.Location.Host,
|
Host: s.Location.Host,
|
||||||
ResourcePool: s.Location.ResourcePool,
|
ResourcePool: s.Location.ResourcePool,
|
||||||
Datastore: s.Location.Datastore,
|
Datastore: s.Location.Datastore,
|
||||||
LinkedClone: s.Config.LinkedClone,
|
LinkedClone: s.Config.LinkedClone,
|
||||||
Network: s.Config.Network,
|
Network: s.Config.Network,
|
||||||
Annotation: s.Config.Notes,
|
Annotation: s.Config.Notes,
|
||||||
|
VAppProperties: s.Config.VAppConfig.Properties,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "mapstructure-to-hcl2 -type CloneConfig"; DO NOT EDIT.
|
// Code generated by "mapstructure-to-hcl2 -type CloneConfig,vAppConfig"; DO NOT EDIT.
|
||||||
package clone
|
package clone
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,11 +9,12 @@ import (
|
||||||
// FlatCloneConfig is an auto-generated flat version of CloneConfig.
|
// FlatCloneConfig is an auto-generated flat version of CloneConfig.
|
||||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
type FlatCloneConfig struct {
|
type FlatCloneConfig struct {
|
||||||
Template *string `mapstructure:"template" cty:"template" hcl:"template"`
|
Template *string `mapstructure:"template" cty:"template" hcl:"template"`
|
||||||
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
|
||||||
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
|
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
|
||||||
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
|
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
|
||||||
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
|
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
|
||||||
|
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatCloneConfig.
|
// FlatMapstructure returns a new FlatCloneConfig.
|
||||||
|
@ -33,6 +34,30 @@ func (*FlatCloneConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
|
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
|
||||||
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
|
||||||
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
|
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
|
||||||
|
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatvAppConfig is an auto-generated flat version of vAppConfig.
|
||||||
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
type FlatvAppConfig struct {
|
||||||
|
Properties map[string]string `mapstructure:"properties" cty:"properties" hcl:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatMapstructure returns a new FlatvAppConfig.
|
||||||
|
// FlatvAppConfig is an auto-generated flat version of vAppConfig.
|
||||||
|
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
func (*vAppConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||||
|
return new(FlatvAppConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCL2Spec returns the hcl spec of a vAppConfig.
|
||||||
|
// This spec is used by HCL to read the fields of vAppConfig.
|
||||||
|
// The decoded values from this spec will then be applied to a FlatvAppConfig.
|
||||||
|
func (*FlatvAppConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
|
s := map[string]hcldec.Spec{
|
||||||
|
"properties": &hcldec.AttrSpec{Name: "properties", Type: cty.Map(cty.String), Required: false},
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||||
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/helper/ssh"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StepSshKeyPair executes the business logic for setting the SSH key pair in
|
||||||
|
// the specified communicator.Config.
|
||||||
|
type StepSshKeyPair struct {
|
||||||
|
Debug bool
|
||||||
|
DebugKeyPath string
|
||||||
|
Comm *communicator.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
if s.Comm.Type != "ssh" || s.Comm.SSHPassword != "" {
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
comment := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||||
|
if s.Comm.SSHPrivateKeyFile != "" {
|
||||||
|
ui.Say("Using existing SSH private key for the communicator...")
|
||||||
|
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
kp, err := ssh.KeyPairFromPrivateKey(ssh.FromPrivateKeyConfig{
|
||||||
|
RawPrivateKeyPemBlock: privateKeyBytes,
|
||||||
|
Comment: comment,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Comm.SSHPrivateKey = privateKeyBytes
|
||||||
|
s.Comm.SSHKeyPairName = kp.Comment
|
||||||
|
s.Comm.SSHTemporaryKeyPairName = kp.Comment
|
||||||
|
s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Comm.SSHAgentAuth {
|
||||||
|
ui.Say("Using local SSH Agent to authenticate connections for the communicator...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating ephemeral key pair for SSH communicator...")
|
||||||
|
|
||||||
|
if s.Comm.SSHTemporaryKeyPairName != "" {
|
||||||
|
comment = s.Comm.SSHTemporaryKeyPairName
|
||||||
|
}
|
||||||
|
|
||||||
|
kp, err := ssh.NewKeyPair(ssh.CreateKeyPairConfig{
|
||||||
|
Comment: comment,
|
||||||
|
Type: ssh.Rsa,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Comm.SSHKeyPairName = kp.Comment
|
||||||
|
s.Comm.SSHTemporaryKeyPairName = kp.Comment
|
||||||
|
s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock
|
||||||
|
s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine
|
||||||
|
s.Comm.SSHClearAuthorizedKeys = true
|
||||||
|
|
||||||
|
vm := state.Get("vm").(*driver.VirtualMachine)
|
||||||
|
err = vm.AddPublicKeys(ctx, string(s.Comm.SSHPublicKey))
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("error saving temporary keypair in the vm: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Created ephemeral SSH key pair for communicator")
|
||||||
|
|
||||||
|
// If we're in debug mode, output the private key to the working
|
||||||
|
// directory.
|
||||||
|
if s.Debug {
|
||||||
|
ui.Message(fmt.Sprintf("Saving communicator private key for debug purposes: %s", s.DebugKeyPath))
|
||||||
|
// Write the key out
|
||||||
|
if err := ioutil.WriteFile(s.DebugKeyPath, kp.PrivateKeyPemBlock, 0600); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepSshKeyPair) Cleanup(state multistep.StateBag) {
|
||||||
|
if s.Debug {
|
||||||
|
if err := os.Remove(s.DebugKeyPath); err != nil {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -26,15 +27,16 @@ type VirtualMachine struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CloneConfig struct {
|
type CloneConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Folder string
|
Folder string
|
||||||
Cluster string
|
Cluster string
|
||||||
Host string
|
Host string
|
||||||
ResourcePool string
|
ResourcePool string
|
||||||
Datastore string
|
Datastore string
|
||||||
LinkedClone bool
|
LinkedClone bool
|
||||||
Network string
|
Network string
|
||||||
Annotation string
|
Annotation string
|
||||||
|
VAppProperties map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HardwareConfig struct {
|
type HardwareConfig struct {
|
||||||
|
@ -315,6 +317,12 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt
|
||||||
configSpec.DeviceChange = append(configSpec.DeviceChange, config)
|
configSpec.DeviceChange = append(configSpec.DeviceChange, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vAppConfig, err := vm.updateVAppConfig(ctx, config.VAppProperties)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configSpec.VAppConfig = vAppConfig
|
||||||
|
|
||||||
task, err := vm.vm.Clone(vm.driver.ctx, folder.folder, config.Name, cloneSpec)
|
task, err := vm.vm.Clone(vm.driver.ctx, folder.folder, config.Name, cloneSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -339,6 +347,80 @@ func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*Virt
|
||||||
return created, nil
|
return created, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vm *VirtualMachine) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
|
||||||
|
if len(newProps) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vProps, _ := vm.Properties(ctx)
|
||||||
|
if vProps.Config.VAppConfig == nil {
|
||||||
|
return nil, fmt.Errorf("this VM lacks a vApp configuration and cannot have vApp properties set on it")
|
||||||
|
}
|
||||||
|
|
||||||
|
allProperties := vProps.Config.VAppConfig.GetVmConfigInfo().Property
|
||||||
|
|
||||||
|
var props []types.VAppPropertySpec
|
||||||
|
for _, p := range allProperties {
|
||||||
|
userValue, setByUser := newProps[p.Id]
|
||||||
|
if !setByUser {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if *p.UserConfigurable == false {
|
||||||
|
return nil, fmt.Errorf("vApp property with userConfigurable=false specified in vapp.properties: %+v", reflect.ValueOf(newProps).MapKeys())
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := types.VAppPropertySpec{
|
||||||
|
ArrayUpdateSpec: types.ArrayUpdateSpec{
|
||||||
|
Operation: types.ArrayUpdateOperationEdit,
|
||||||
|
},
|
||||||
|
Info: &types.VAppPropertyInfo{
|
||||||
|
Key: p.Key,
|
||||||
|
Id: p.Id,
|
||||||
|
Value: userValue,
|
||||||
|
UserConfigurable: p.UserConfigurable,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
props = append(props, prop)
|
||||||
|
|
||||||
|
delete(newProps, p.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newProps) > 0 {
|
||||||
|
return nil, fmt.Errorf("unsupported vApp properties in vapp.properties: %+v", reflect.ValueOf(newProps).MapKeys())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.VmConfigSpec{
|
||||||
|
Property: props,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VirtualMachine) AddPublicKeys(ctx context.Context, publicKeys string) error {
|
||||||
|
newProps := map[string]string{"public-keys": publicKeys}
|
||||||
|
config, err := vm.updateVAppConfig(ctx, newProps)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not possible to save temporary public key: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
confSpec := types.VirtualMachineConfigSpec{VAppConfig: config}
|
||||||
|
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = task.WaitForResult(vm.driver.ctx, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VirtualMachine) Properties(ctx context.Context) (*mo.VirtualMachine, error) {
|
||||||
|
log.Printf("fetching properties for VM %q", vm.vm.InventoryPath)
|
||||||
|
var props mo.VirtualMachine
|
||||||
|
if err := vm.vm.Properties(ctx, vm.vm.Reference(), nil, &props); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &props, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (vm *VirtualMachine) Destroy() error {
|
func (vm *VirtualMachine) Destroy() error {
|
||||||
task, err := vm.vm.Destroy(vm.driver.ctx)
|
task, err := vm.vm.Destroy(vm.driver.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -42,6 +42,43 @@ necessary for this build to succeed and can be found further down the page.
|
||||||
|
|
||||||
@include 'builder/vsphere/clone/CloneConfig-not-required.mdx'
|
@include 'builder/vsphere/clone/CloneConfig-not-required.mdx'
|
||||||
|
|
||||||
|
### vApp Options Configuration
|
||||||
|
|
||||||
|
@include 'builder/vsphere/clone/vAppConfig-not-required.mdx'
|
||||||
|
|
||||||
|
Example of usage:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab heading="JSON">
|
||||||
|
|
||||||
|
```json
|
||||||
|
"vapp": {
|
||||||
|
"properties": {
|
||||||
|
"hostname": "{{ user `hostname`}}",
|
||||||
|
"user-data": "{{ env `USERDATA`}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A `user-data` field requires the content of a yaml file to be encoded with base64. This
|
||||||
|
can be done via environment variable:
|
||||||
|
`export USERDATA=$(gzip -c9 <userdata.yaml | { base64 -w0 2>/dev/null || base64; })`
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
<Tab heading="HCL2">
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vapp {
|
||||||
|
properties = {
|
||||||
|
hostname = var.hostname
|
||||||
|
user-data = base64encode(var.user_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
### Extra Configuration Parameters
|
### Extra Configuration Parameters
|
||||||
|
|
||||||
@include 'builder/vsphere/common/ConfigParamsConfig-not-required.mdx'
|
@include 'builder/vsphere/common/ConfigParamsConfig-not-required.mdx'
|
||||||
|
@ -98,6 +135,23 @@ necessary for this build to succeed and can be found further down the page.
|
||||||
|
|
||||||
@include 'helper/communicator/SSH-not-required.mdx'
|
@include 'helper/communicator/SSH-not-required.mdx'
|
||||||
|
|
||||||
|
@include 'helper/communicator/SSH-Temporary-Key-Pair-not-required.mdx'
|
||||||
|
|
||||||
|
@include 'helper/communicator/SSH-Key-Pair-Name-not-required.mdx'
|
||||||
|
|
||||||
|
@include 'helper/communicator/SSH-Private-Key-File-not-required.mdx'
|
||||||
|
|
||||||
|
@include 'helper/communicator/SSH-Agent-Auth-not-required.mdx'
|
||||||
|
|
||||||
|
-> **NOTE:** Packer uses vApp Options to inject ssh public keys to the Virtual Machine.
|
||||||
|
The [temporary_key_pair_name](/docs/builders/vmware/vsphere-clone#temporary_key_pair_name) will only work
|
||||||
|
if the template being cloned contains the vApp property `public-keys`.
|
||||||
|
If using [ssh_private_key_file](/docs/builders/vmware/vsphere-clone#ssh_private_key_file), provide
|
||||||
|
the public key via [configuration_parameters](/docs/builders/vmware/vsphere-clone#configuration_parameters) or
|
||||||
|
[vApp Options Configuration](/docs/builders/vmware/vsphere-clone#vapp-options-configuration) whenever the `guestinto.userdata`
|
||||||
|
is available. See [VMware Guestinfo datasource](https://github.com/vmware/cloud-init-vmware-guestinfo) for more information
|
||||||
|
about the key.
|
||||||
|
|
||||||
#### Optional WinRM fields:
|
#### Optional WinRM fields:
|
||||||
|
|
||||||
@include 'helper/communicator/WinRM-not-required.mdx'
|
@include 'helper/communicator/WinRM-not-required.mdx'
|
||||||
|
|
|
@ -11,3 +11,7 @@
|
||||||
|
|
||||||
- `notes` (string) - VM notes.
|
- `notes` (string) - VM notes.
|
||||||
|
|
||||||
|
- `vapp` (vAppConfig) - Set the vApp Options to a virtual machine.
|
||||||
|
See the [vApp Options Configuration](/docs/builders/vmware/vsphere-clone#vapp-options-configuration)
|
||||||
|
to know the available options and how to use it.
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!-- Code generated from the comments of the vAppConfig struct in builder/vsphere/clone/step_clone.go; DO NOT EDIT MANUALLY -->
|
||||||
|
|
||||||
|
- `properties` (map[string]string) - Set values for the available vApp Properties to supply configuration parameters to a virtual machine cloned from
|
||||||
|
a template that came from an imported OVF or OVA file.
|
||||||
|
|
||||||
|
-> **Note:** The only supported usage path for vApp properties is for existing user-configurable keys.
|
||||||
|
These generally come from an existing template that was created from an imported OVF or OVA file.
|
||||||
|
You cannot set values for vApp properties on virtual machines created from scratch,
|
||||||
|
virtual machines lacking a vApp configuration, or on property keys that do not exist.
|
||||||
|
|
Loading…
Reference in New Issue