remove vsphere components and docs

This commit is contained in:
sylviamoss 2021-04-09 14:41:11 +02:00 committed by Megan Marsh
parent a6c5958c67
commit 38fe79948b
182 changed files with 0 additions and 16361 deletions

View File

@ -1,175 +0,0 @@
package clone
import (
"context"
"fmt"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
warnings, errs := b.config.Prepare(raws...)
if errs != nil {
return nil, warnings, errs
}
return nil, warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
state := new(multistep.BasicStateBag)
state.Put("debug", b.config.PackerDebug)
state.Put("hook", hook)
state.Put("ui", ui)
var steps []multistep.Step
steps = append(steps,
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
&commonsteps.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&common.StepRemoteUpload{
Datastore: b.config.Datastore,
Host: b.config.Host,
SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads,
},
&StepCloneVM{
Config: &b.config.CloneConfig,
Location: &b.config.LocationConfig,
Force: b.config.PackerConfig.PackerForce,
},
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&common.StepAddCDRom{
Config: &b.config.CDRomConfig,
},
&common.StepConfigParams{
Config: &b.config.ConfigParamsConfig,
},
)
if b.config.CustomizeConfig != nil {
steps = append(steps, &StepCustomize{
Config: b.config.CustomizeConfig,
})
}
if b.config.Comm.Type != "none" {
steps = append(steps,
&commonsteps.StepCreateFloppy{
Files: b.config.FloppyFiles,
Directories: b.config.FloppyDirectories,
Label: b.config.FloppyLabel,
},
&common.StepAddFloppy{
Config: &b.config.FloppyConfig,
Datastore: b.config.Datastore,
Host: b.config.Host,
SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads,
},
&common.StepHTTPIPDiscover{
HTTPIP: b.config.BootConfig.HTTPIP,
Network: b.config.WaitIpConfig.GetIPNet(),
},
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&common.StepSshKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName),
Comm: &b.config.Comm,
},
&common.StepRun{
Config: &b.config.RunConfig,
SetOrder: false,
},
&common.StepBootCommand{
Config: &b.config.BootConfig,
Ctx: b.config.ctx,
VMName: b.config.VMName,
},
&common.StepWaitForIp{
Config: &b.config.WaitIpConfig,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: common.CommHost(b.config.Comm.Host()),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&commonsteps.StepProvision{},
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
&common.StepRemoveFloppy{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
)
}
steps = append(steps,
&common.StepRemoveCDRom{
Config: &b.config.RemoveCDRomConfig,
},
&common.StepCreateSnapshot{
CreateSnapshot: b.config.CreateSnapshot,
},
&common.StepConvertToTemplate{
ConvertToTemplate: b.config.ConvertToTemplate,
},
)
if b.config.ContentLibraryDestinationConfig != nil {
steps = append(steps, &common.StepImportToContentLibrary{
ContentLibConfig: b.config.ContentLibraryDestinationConfig,
})
}
if b.config.Export != nil {
steps = append(steps, &common.StepExport{
Name: b.config.Export.Name,
Force: b.config.Export.Force,
Images: b.config.Export.Images,
Manifest: b.config.Export.Manifest,
OutputDir: b.config.Export.OutputDir.OutputDir,
Options: b.config.Export.Options,
})
}
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(ctx, state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
if _, ok := state.GetOk("vm"); !ok {
return nil, nil
}
artifact := &common.Artifact{
Name: b.config.VMName,
VM: state.Get("vm").(*driver.VirtualMachineDriver),
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
if b.config.Export != nil {
artifact.Outconfig = &b.config.Export.OutputDir
}
return artifact, nil
}

View File

@ -1,734 +0,0 @@
package clone
import (
"os"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
builderT "github.com/hashicorp/packer/acctest"
"github.com/hashicorp/packer/builder/vsphere/common"
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
"github.com/vmware/govmomi/vim25/types"
)
func TestCloneBuilderAcc_default(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkDefault(t, config["vm_name"].(string), config["host"].(string), "datastore1"),
})
}
func defaultConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"template": "alpine",
"host": "esxi-1.vsphere65.test",
"linked_clone": true, // speed up
"communicator": "none",
}
config["vm_name"] = commonT.NewVMName()
return config
}
func checkDefault(t *testing.T, name string, host string, datastore string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != host {
t.Errorf("Invalid host name: expected '%v', got '%v'", host, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != datastore {
t.Errorf("Invalid datastore name: expected '%v', got '%v'", datastore, dsInfo.Name)
}
return nil
}
}
func TestCloneBuilderAcc_artifact(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkArtifact(t),
})
}
func checkArtifact(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
if len(artifacts) > 1 {
t.Fatal("more than 1 artifact")
}
artifactRaw := artifacts[0]
_, ok := artifactRaw.(*common.Artifact)
if !ok {
t.Fatalf("unknown artifact: %#v", artifactRaw)
}
return nil
}
}
func TestCloneBuilderAcc_folder(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: folderConfig(),
Check: checkFolder(t, "folder1/folder2"),
})
}
func folderConfig() string {
config := defaultConfig()
config["folder"] = "folder1/folder2"
return commonT.RenderConfig(config)
}
func checkFolder(t *testing.T, folder string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("parent")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
f := d.NewFolder(vmInfo.Parent)
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if path != folder {
t.Errorf("Wrong folder. expected: %v, got: %v", folder, path)
}
return nil
}
}
func TestCloneBuilderAcc_resourcePool(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: resourcePoolConfig(),
Check: checkResourcePool(t, "pool1/pool2"),
})
}
func resourcePoolConfig() string {
config := defaultConfig()
config["resource_pool"] = "pool1/pool2"
return commonT.RenderConfig(config)
}
func checkResourcePool(t *testing.T, pool string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("resourcePool")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if path != pool {
t.Errorf("Wrong folder. expected: %v, got: %v", pool, path)
}
return nil
}
}
func TestCloneBuilderAcc_datastore(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: datastoreConfig(),
Check: checkDatastore(t, "datastore1"), // on esxi-1.vsphere65.test
})
}
func datastoreConfig() string {
config := defaultConfig()
config["template"] = "alpine-host4" // on esxi-4.vsphere65.test
config["linked_clone"] = false
return commonT.RenderConfig(config)
}
func checkDatastore(t *testing.T, name string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("datastore")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
n := len(vmInfo.Datastore)
if n != 1 {
t.Fatalf("VM should have 1 datastore, got %v", n)
}
ds := d.NewDatastore(&vmInfo.Datastore[0])
info, err := ds.Info("name")
if err != nil {
t.Fatalf("Cannot read datastore properties: %v", err)
}
if info.Name != name {
t.Errorf("Wrong datastore. expected: %v, got: %v", name, info.Name)
}
return nil
}
}
func TestCloneBuilderAcc_multipleDatastores(t *testing.T) {
t.Skip("test must fail")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: multipleDatastoresConfig(),
})
}
func multipleDatastoresConfig() string {
config := defaultConfig()
config["host"] = "esxi-4.vsphere65.test" // host with 2 datastores
config["linked_clone"] = false
return commonT.RenderConfig(config)
}
func TestCloneBuilderAcc_fullClone(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: fullCloneConfig(),
Check: checkFullClone(t),
})
}
func fullCloneConfig() string {
config := defaultConfig()
config["linked_clone"] = false
return commonT.RenderConfig(config)
}
func checkFullClone(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 1 {
t.Error("Not a full clone")
}
return nil
}
}
func TestCloneBuilderAcc_linkedClone(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: linkedCloneConfig(),
Check: checkLinkedClone(t),
})
}
func linkedCloneConfig() string {
config := defaultConfig()
config["linked_clone"] = true
return commonT.RenderConfig(config)
}
func checkLinkedClone(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 2 {
t.Error("Not a linked clone")
}
return nil
}
}
func TestCloneBuilderAcc_network(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: networkConfig(),
Check: checkNetwork(t, "VM Network 2"),
})
}
func networkConfig() string {
config := defaultConfig()
config["template"] = "alpine-host4"
config["host"] = "esxi-4.vsphere65.test"
config["datastore"] = "datastore4"
config["network"] = "VM Network 2"
return commonT.RenderConfig(config)
}
func checkNetwork(t *testing.T, name string) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("network")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
n := len(vmInfo.Network)
if n != 1 {
t.Fatalf("VM should have 1 network, got %v", n)
}
ds := d.NewNetwork(&vmInfo.Network[0])
info, err := ds.Info("name")
if err != nil {
t.Fatalf("Cannot read network properties: %v", err)
}
if info.Name != name {
t.Errorf("Wrong network. expected: %v, got: %v", name, info.Name)
}
return nil
}
}
func TestCloneBuilderAcc_hardware(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: hardwareConfig(),
Check: checkHardware(t),
})
}
func hardwareConfig() string {
config := defaultConfig()
config["CPUs"] = 2
config["cpu_cores"] = 2
config["CPU_reservation"] = 1000
config["CPU_limit"] = 1500
config["RAM"] = 2048
config["RAM_reservation"] = 1024
config["CPU_hot_plug"] = true
config["RAM_hot_plug"] = true
config["video_ram"] = 8192
return commonT.RenderConfig(config)
}
func checkHardware(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
cpuSockets := vmInfo.Config.Hardware.NumCPU
if cpuSockets != 2 {
t.Errorf("VM should have 2 CPU sockets, got %v", cpuSockets)
}
cpuCores := vmInfo.Config.Hardware.NumCoresPerSocket
if cpuCores != 2 {
t.Errorf("VM should have 2 CPU cores per socket, got %v", cpuCores)
}
cpuReservation := *vmInfo.Config.CpuAllocation.Reservation
if cpuReservation != 1000 {
t.Errorf("VM should have CPU reservation for 1000 Mhz, got %v", cpuReservation)
}
cpuLimit := *vmInfo.Config.CpuAllocation.Limit
if cpuLimit != 1500 {
t.Errorf("VM should have CPU reservation for 1500 Mhz, got %v", cpuLimit)
}
ram := vmInfo.Config.Hardware.MemoryMB
if ram != 2048 {
t.Errorf("VM should have 2048 MB of RAM, got %v", ram)
}
ramReservation := *vmInfo.Config.MemoryAllocation.Reservation
if ramReservation != 1024 {
t.Errorf("VM should have RAM reservation for 1024 MB, got %v", ramReservation)
}
cpuHotAdd := vmInfo.Config.CpuHotAddEnabled
if !*cpuHotAdd {
t.Errorf("VM should have CPU hot add enabled, got %v", cpuHotAdd)
}
memoryHotAdd := vmInfo.Config.MemoryHotAddEnabled
if !*memoryHotAdd {
t.Errorf("VM should have Memory hot add enabled, got %v", memoryHotAdd)
}
l, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM devices: %v", err)
}
v := l.SelectByType((*types.VirtualMachineVideoCard)(nil))
if len(v) != 1 {
t.Errorf("VM should have one video card")
}
if v[0].(*types.VirtualMachineVideoCard).VideoRamSizeInKB != 8192 {
t.Errorf("Video RAM should be equal 8192")
}
return nil
}
}
func TestCloneBuilderAcc_RAMReservation(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: RAMReservationConfig(),
Check: checkRAMReservation(t),
})
}
func RAMReservationConfig() string {
config := defaultConfig()
config["RAM_reserve_all"] = true
return commonT.RenderConfig(config)
}
func checkRAMReservation(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if *vmInfo.Config.MemoryReservationLockedToMax != true {
t.Errorf("VM should have all RAM reserved")
}
return nil
}
}
func TestCloneBuilderAcc_sshPassword(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: sshPasswordConfig(),
Check: checkDefaultBootOrder(t),
})
}
func sshPasswordConfig() string {
config := defaultConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "root"
config["ssh_password"] = "jetbrains"
return commonT.RenderConfig(config)
}
func checkDefaultBootOrder(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.bootOptions")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
order := vmInfo.Config.BootOptions.BootOrder
if order != nil {
t.Errorf("Boot order must be empty")
}
return nil
}
}
func TestCloneBuilderAcc_sshKey(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: sshKeyConfig(),
})
}
func sshKeyConfig() string {
config := defaultConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "root"
config["ssh_private_key_file"] = "../test/test-key.pem"
return commonT.RenderConfig(config)
}
func TestCloneBuilderAcc_snapshot(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: snapshotConfig(),
Check: checkSnapshot(t),
})
}
func snapshotConfig() string {
config := defaultConfig()
config["linked_clone"] = false
config["create_snapshot"] = true
return commonT.RenderConfig(config)
}
func checkSnapshot(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
layers := len(vmInfo.LayoutEx.Disk[0].Chain)
if layers != 2 {
t.Errorf("VM should have a single snapshot. expected 2 disk layers, got %v", layers)
}
return nil
}
}
func TestCloneBuilderAcc_template(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: templateConfig(),
Check: checkTemplate(t),
})
}
func templateConfig() string {
config := defaultConfig()
config["convert_to_template"] = true
return commonT.RenderConfig(config)
}
func checkTemplate(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.template")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Config.Template != true {
t.Error("Not a template")
}
return nil
}
}
func TestCloneBuilderAcc_bootOrder(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: bootOrderConfig(),
Check: checkBootOrder(t),
})
}
func bootOrderConfig() string {
config := defaultConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "root"
config["ssh_password"] = "jetbrains"
config["boot_order"] = "disk,cdrom,floppy"
return commonT.RenderConfig(config)
}
func checkBootOrder(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.bootOptions")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
order := vmInfo.Config.BootOptions.BootOrder
if order == nil {
t.Errorf("Boot order must not be empty")
}
return nil
}
}
func TestCloneBuilderAcc_notes(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: notesConfig(),
Check: checkNotes(t),
})
}
func notesConfig() string {
config := defaultConfig()
config["notes"] = "test"
return commonT.RenderConfig(config)
}
func checkNotes(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packersdk.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.annotation")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
notes := vmInfo.Config.Annotation
if notes != "test" {
t.Errorf("notest should be 'test'")
}
return nil
}
}
func TestCloneBuilderAcc_windows(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
t.Skip("test is too slow")
config := windowsConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
})
}
func windowsConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"vm_name": commonT.NewVMName(),
"template": "windows",
"host": "esxi-1.vsphere65.test",
"linked_clone": true, // speed up
"communicator": "winrm",
"winrm_username": "jetbrains",
"winrm_password": "jetbrains",
}
return config
}

View File

@ -1,15 +0,0 @@
package clone
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestCloneBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}

View File

@ -1,104 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
package clone
import (
packerCommon "github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/vsphere/common"
)
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
commonsteps.HTTPConfig `mapstructure:",squash"`
commonsteps.CDConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CloneConfig `mapstructure:",squash"`
common.LocationConfig `mapstructure:",squash"`
common.HardwareConfig `mapstructure:",squash"`
common.ConfigParamsConfig `mapstructure:",squash"`
common.CDRomConfig `mapstructure:",squash"`
common.RemoveCDRomConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
common.BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
// Create a snapshot when set to `true`, so the VM can be used as a base
// for linked clones. Defaults to `false`.
CreateSnapshot bool `mapstructure:"create_snapshot"`
// Convert VM to a template. Defaults to `false`.
ConvertToTemplate bool `mapstructure:"convert_to_template"`
// Configuration for exporting VM to an ovf file.
// The VM will not be exported if no [Export Configuration](#export-configuration) is specified.
Export *common.ExportConfig `mapstructure:"export"`
// Configuration for importing a VM template or OVF template to a Content Library.
// The template will not be imported if no [Content Library Import Configuration](#content-library-import-configuration) is specified.
// The import doesn't work if [convert_to_template](#convert_to_template) is set to true.
ContentLibraryDestinationConfig *common.ContentLibraryDestinationConfig `mapstructure:"content_library_destination"`
// Customize the cloned VM to configure host, network, or licensing settings. See the [customization options](#customization).
CustomizeConfig *CustomizeConfig `mapstructure:"customize"`
ctx interpolate.Context
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(c, &config.DecodeOpts{
PluginType: common.BuilderId,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
return nil, err
}
// warnings := make([]string, 0)
errs := new(packersdk.MultiError)
errs = packersdk.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, c.CloneConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packersdk.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
errs = packersdk.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
errs = packersdk.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
_, shutdownErrs := c.ShutdownConfig.Prepare(c.Comm)
// shutdownWarnings, shutdownErrs := c.ShutdownConfig.Prepare(c.Comm)
// warnings = append(warnings, shutdownWarnings...)
errs = packersdk.MultiErrorAppend(errs, shutdownErrs...)
if c.Export != nil {
errs = packersdk.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
}
if c.ContentLibraryDestinationConfig != nil {
errs = packersdk.MultiErrorAppend(errs, c.ContentLibraryDestinationConfig.Prepare(&c.LocationConfig)...)
}
if c.CustomizeConfig != nil {
errs = packersdk.MultiErrorAppend(errs, c.CustomizeConfig.Prepare()...)
}
if len(errs.Errors) > 0 {
return nil, errs
}
return nil, nil
}

View File

@ -1,282 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package clone
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server" hcl:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection" hcl:"insecure_connection"`
Datacenter *string `mapstructure:"datacenter" cty:"datacenter" hcl:"datacenter"`
Template *string `mapstructure:"template" cty:"template" hcl:"template"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []common.FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"`
Cluster *string `mapstructure:"cluster" cty:"cluster" hcl:"cluster"`
Host *string `mapstructure:"host" cty:"host" hcl:"host"`
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool" hcl:"resource_pool"`
Datastore *string `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
SetHostForDatastoreUploads *bool `mapstructure:"set_host_for_datastore_uploads" cty:"set_host_for_datastore_uploads" hcl:"set_host_for_datastore_uploads"`
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs" hcl:"CPUs"`
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores" hcl:"cpu_cores"`
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation" hcl:"CPU_reservation"`
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit" hcl:"CPU_limit"`
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug" hcl:"CPU_hot_plug"`
RAM *int64 `mapstructure:"RAM" cty:"RAM" hcl:"RAM"`
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation" hcl:"RAM_reservation"`
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all" hcl:"RAM_reserve_all"`
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug" hcl:"RAM_hot_plug"`
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram" hcl:"video_ram"`
VGPUProfile *string `mapstructure:"vgpu_profile" cty:"vgpu_profile" hcl:"vgpu_profile"`
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV" hcl:"NestedHV"`
Firmware *string `mapstructure:"firmware" cty:"firmware" hcl:"firmware"`
ForceBIOSSetup *bool `mapstructure:"force_bios_setup" cty:"force_bios_setup" hcl:"force_bios_setup"`
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
ToolsSyncTime *bool `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
ToolsUpgradePolicy *bool `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
CdromType *string `mapstructure:"cdrom_type" cty:"cdrom_type" hcl:"cdrom_type"`
ISOPaths []string `mapstructure:"iso_paths" cty:"iso_paths" hcl:"iso_paths"`
RemoveCdrom *bool `mapstructure:"remove_cdrom" cty:"remove_cdrom" hcl:"remove_cdrom"`
FloppyIMGPath *string `mapstructure:"floppy_img_path" cty:"floppy_img_path" hcl:"floppy_img_path"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
BootOrder *string `mapstructure:"boot_order" cty:"boot_order" hcl:"boot_order"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
HTTPIP *string `mapstructure:"http_ip" cty:"http_ip" hcl:"http_ip"`
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout" hcl:"ip_wait_timeout"`
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout" hcl:"ip_settle_timeout"`
WaitAddress *string `mapstructure:"ip_wait_address" cty:"ip_wait_address" hcl:"ip_wait_address"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command" hcl:"shutdown_command"`
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout" hcl:"shutdown_timeout"`
DisableShutdown *bool `mapstructure:"disable_shutdown" cty:"disable_shutdown" hcl:"disable_shutdown"`
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot" hcl:"create_snapshot"`
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template" hcl:"convert_to_template"`
Export *common.FlatExportConfig `mapstructure:"export" cty:"export" hcl:"export"`
ContentLibraryDestinationConfig *common.FlatContentLibraryDestinationConfig `mapstructure:"content_library_destination" cty:"content_library_destination" hcl:"content_library_destination"`
CustomizeConfig *FlatCustomizeConfig `mapstructure:"customize" cty:"customize" hcl:"customize"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"vcenter_server": &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"insecure_connection": &hcldec.AttrSpec{Name: "insecure_connection", Type: cty.Bool, Required: false},
"datacenter": &hcldec.AttrSpec{Name: "datacenter", Type: cty.String, Required: false},
"template": &hcldec.AttrSpec{Name: "template", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"mac_address": &hcldec.AttrSpec{Name: "mac_address", 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())},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*common.FlatDiskConfig)(nil).HCL2Spec())},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", 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},
"host": &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
"resource_pool": &hcldec.AttrSpec{Name: "resource_pool", Type: cty.String, Required: false},
"datastore": &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
"set_host_for_datastore_uploads": &hcldec.AttrSpec{Name: "set_host_for_datastore_uploads", Type: cty.Bool, Required: false},
"CPUs": &hcldec.AttrSpec{Name: "CPUs", Type: cty.Number, Required: false},
"cpu_cores": &hcldec.AttrSpec{Name: "cpu_cores", Type: cty.Number, Required: false},
"CPU_reservation": &hcldec.AttrSpec{Name: "CPU_reservation", Type: cty.Number, Required: false},
"CPU_limit": &hcldec.AttrSpec{Name: "CPU_limit", Type: cty.Number, Required: false},
"CPU_hot_plug": &hcldec.AttrSpec{Name: "CPU_hot_plug", Type: cty.Bool, Required: false},
"RAM": &hcldec.AttrSpec{Name: "RAM", Type: cty.Number, Required: false},
"RAM_reservation": &hcldec.AttrSpec{Name: "RAM_reservation", Type: cty.Number, Required: false},
"RAM_reserve_all": &hcldec.AttrSpec{Name: "RAM_reserve_all", Type: cty.Bool, Required: false},
"RAM_hot_plug": &hcldec.AttrSpec{Name: "RAM_hot_plug", Type: cty.Bool, Required: false},
"video_ram": &hcldec.AttrSpec{Name: "video_ram", Type: cty.Number, Required: false},
"vgpu_profile": &hcldec.AttrSpec{Name: "vgpu_profile", Type: cty.String, Required: false},
"NestedHV": &hcldec.AttrSpec{Name: "NestedHV", Type: cty.Bool, Required: false},
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
"force_bios_setup": &hcldec.AttrSpec{Name: "force_bios_setup", Type: cty.Bool, Required: false},
"configuration_parameters": &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
"tools_sync_time": &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
"tools_upgrade_policy": &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
"cdrom_type": &hcldec.AttrSpec{Name: "cdrom_type", Type: cty.String, Required: false},
"iso_paths": &hcldec.AttrSpec{Name: "iso_paths", Type: cty.List(cty.String), Required: false},
"remove_cdrom": &hcldec.AttrSpec{Name: "remove_cdrom", Type: cty.Bool, Required: false},
"floppy_img_path": &hcldec.AttrSpec{Name: "floppy_img_path", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"http_ip": &hcldec.AttrSpec{Name: "http_ip", Type: cty.String, Required: false},
"ip_wait_timeout": &hcldec.AttrSpec{Name: "ip_wait_timeout", Type: cty.String, Required: false},
"ip_settle_timeout": &hcldec.AttrSpec{Name: "ip_settle_timeout", Type: cty.String, Required: false},
"ip_wait_address": &hcldec.AttrSpec{Name: "ip_wait_address", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
"disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false},
"create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, Required: false},
"convert_to_template": &hcldec.AttrSpec{Name: "convert_to_template", Type: cty.Bool, Required: false},
"export": &hcldec.BlockSpec{TypeName: "export", Nested: hcldec.ObjectSpec((*common.FlatExportConfig)(nil).HCL2Spec())},
"content_library_destination": &hcldec.BlockSpec{TypeName: "content_library_destination", Nested: hcldec.ObjectSpec((*common.FlatContentLibraryDestinationConfig)(nil).HCL2Spec())},
"customize": &hcldec.BlockSpec{TypeName: "customize", Nested: hcldec.ObjectSpec((*FlatCustomizeConfig)(nil).HCL2Spec())},
}
return s
}

View File

@ -1,74 +0,0 @@
package clone
import (
"testing"
"time"
)
func TestCloneConfig_MinimalConfig(t *testing.T) {
c := new(Config)
warns, errs := c.Prepare(minimalConfig())
testConfigOk(t, warns, errs)
}
func TestCloneConfig_MandatoryParameters(t *testing.T) {
params := []string{"vcenter_server", "username", "password", "template", "vm_name", "host"}
for _, param := range params {
raw := minimalConfig()
raw[param] = ""
c := new(Config)
warns, err := c.Prepare(raw)
testConfigErr(t, param, warns, err)
}
}
func TestCloneConfig_Timeout(t *testing.T) {
raw := minimalConfig()
raw["shutdown_timeout"] = "3m"
conf := new(Config)
warns, err := conf.Prepare(raw)
testConfigOk(t, warns, err)
if conf.ShutdownConfig.Timeout != 3*time.Minute {
t.Fatalf("shutdown_timeout sould be equal 3 minutes, got %v", conf.ShutdownConfig.Timeout)
}
}
func TestCloneConfig_RAMReservation(t *testing.T) {
raw := minimalConfig()
raw["RAM_reservation"] = 1000
raw["RAM_reserve_all"] = true
c := new(Config)
warns, err := c.Prepare(raw)
testConfigErr(t, "RAM_reservation", warns, err)
}
func minimalConfig() map[string]interface{} {
return map[string]interface{}{
"vcenter_server": "vcenter.domain.local",
"username": "root",
"password": "vmware",
"template": "ubuntu",
"vm_name": "vm1",
"host": "esxi1.domain.local",
"ssh_username": "root",
"ssh_password": "secret",
}
}
func testConfigOk(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Errorf("Should be no warnings: %#v", warns)
}
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
}
func testConfigErr(t *testing.T, context string, warns []string, err error) {
if len(warns) > 0 {
t.Errorf("Should be no warnings: %#v", warns)
}
if err == nil {
t.Error("An error is not raised for", context)
}
}

View File

@ -1,136 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CloneConfig,vAppConfig
package clone
import (
"context"
"fmt"
"path"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
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 {
// Name of source VM. Path is optional.
Template string `mapstructure:"template"`
// The size of the disk in MB.
DiskSize int64 `mapstructure:"disk_size"`
// Create VM as a linked clone from latest snapshot. Defaults to `false`.
LinkedClone bool `mapstructure:"linked_clone"`
// Set the network in which the VM will be connected to. If no network is
// specified, `host` must be specified to allow Packer to look for the
// available network. If the network is inside a network folder in vCenter,
// you need to provide the full path to the network.
Network string `mapstructure:"network"`
// Sets a custom Mac Address to the network adapter. If set, the [network](#network) must be also specified.
MacAddress string `mapstructure:"mac_address"`
// VM 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"`
StorageConfig common.StorageConfig `mapstructure:",squash"`
}
func (c *CloneConfig) Prepare() []error {
var errs []error
errs = append(errs, c.StorageConfig.Prepare()...)
if c.Template == "" {
errs = append(errs, fmt.Errorf("'template' is required"))
}
if c.LinkedClone == true && c.DiskSize != 0 {
errs = append(errs, fmt.Errorf("'linked_clone' and 'disk_size' cannot be used together"))
}
if c.MacAddress != "" && c.Network == "" {
errs = append(errs, fmt.Errorf("'network' is required when 'mac_address' is specified"))
}
return errs
}
type StepCloneVM struct {
Config *CloneConfig
Location *common.LocationConfig
Force bool
}
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
d := state.Get("driver").(driver.Driver)
vmPath := path.Join(s.Location.Folder, s.Location.VMName)
err := d.PreCleanVM(ui, vmPath, s.Force)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say("Cloning VM...")
template, err := d.FindVM(s.Config.Template)
if err != nil {
state.Put("error", fmt.Errorf("Error finding vm to clone: %s", err))
return multistep.ActionHalt
}
var disks []driver.Disk
for _, disk := range s.Config.StorageConfig.Storage {
disks = append(disks, driver.Disk{
DiskSize: disk.DiskSize,
DiskEagerlyScrub: disk.DiskEagerlyScrub,
DiskThinProvisioned: disk.DiskThinProvisioned,
ControllerIndex: disk.DiskControllerIndex,
})
}
vm, err := template.Clone(ctx, &driver.CloneConfig{
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
LinkedClone: s.Config.LinkedClone,
Network: s.Config.Network,
MacAddress: s.Config.MacAddress,
Annotation: s.Config.Notes,
VAppProperties: s.Config.VAppConfig.Properties,
PrimaryDiskSize: s.Config.DiskSize,
StorageConfig: driver.StorageConfig{
DiskControllerType: s.Config.StorageConfig.DiskControllerType,
Storage: disks,
},
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if vm == nil {
return multistep.ActionHalt
}
state.Put("vm", vm)
return multistep.ActionContinue
}
func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
common.CleanupVM(state)
}

View File

@ -1,71 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type CloneConfig,vAppConfig"; DO NOT EDIT.
package clone
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/zclconf/go-cty/cty"
)
// FlatCloneConfig is an auto-generated flat version of CloneConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCloneConfig struct {
Template *string `mapstructure:"template" cty:"template" hcl:"template"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []common.FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
}
// FlatMapstructure returns a new FlatCloneConfig.
// FlatCloneConfig is an auto-generated flat version of CloneConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*CloneConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatCloneConfig)
}
// HCL2Spec returns the hcl spec of a CloneConfig.
// This spec is used by HCL to read the fields of CloneConfig.
// The decoded values from this spec will then be applied to a FlatCloneConfig.
func (*FlatCloneConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"template": &hcldec.AttrSpec{Name: "template", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"mac_address": &hcldec.AttrSpec{Name: "mac_address", 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())},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*common.FlatDiskConfig)(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
}

View File

@ -1,251 +0,0 @@
package clone
import (
"bytes"
"context"
"path"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestCreateConfig_Prepare(t *testing.T) {
tc := []struct {
name string
config *CloneConfig
fail bool
expectedErrMsg string
}{
{
name: "Valid config",
config: &CloneConfig{
Template: "template name",
StorageConfig: common.StorageConfig{
DiskControllerType: []string{"test"},
Storage: []common.DiskConfig{
{
DiskSize: 0,
},
},
},
},
fail: true,
expectedErrMsg: "storage[0].'disk_size' is required",
},
{
name: "Storage validate disk_size",
config: &CloneConfig{
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 0,
DiskThinProvisioned: true,
},
},
},
},
fail: true,
expectedErrMsg: "storage[0].'disk_size' is required",
},
{
name: "Storage validate disk_controller_index",
config: &CloneConfig{
StorageConfig: common.StorageConfig{
Storage: []common.DiskConfig{
{
DiskSize: 32768,
DiskControllerIndex: 3,
},
},
},
},
fail: true,
expectedErrMsg: "storage[0].'disk_controller_index' references an unknown disk controller",
},
{
name: "Validate template is set",
config: &CloneConfig{
StorageConfig: common.StorageConfig{
DiskControllerType: []string{"test"},
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "'template' is required",
},
{
name: "Validate LinkedClone and DiskSize set at the same time",
config: &CloneConfig{
Template: "template name",
LinkedClone: true,
DiskSize: 32768,
StorageConfig: common.StorageConfig{
DiskControllerType: []string{"test"},
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "'linked_clone' and 'disk_size' cannot be used together",
},
{
name: "Validate MacAddress and Network not set at the same time",
config: &CloneConfig{
Template: "template name",
MacAddress: "some mac address",
StorageConfig: common.StorageConfig{
DiskControllerType: []string{"test"},
Storage: []common.DiskConfig{
{
DiskSize: 32768,
},
},
},
},
fail: true,
expectedErrMsg: "'network' is required when 'mac_address' is specified",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
errs := c.config.Prepare()
if c.fail {
if len(errs) == 0 {
t.Fatalf("Config preprare should fail")
}
if errs[0].Error() != c.expectedErrMsg {
t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
}
} else {
if len(errs) != 0 {
t.Fatalf("Config preprare should not fail: %s", errs[0])
}
}
})
}
}
func TestStepCreateVM_Run(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)
step := basicStepCloneVM()
step.Force = true
vmPath := path.Join(step.Location.Folder, step.Location.VMName)
vmMock := new(driver.VirtualMachineMock)
driverMock.VM = vmMock
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
t.Fatalf("Should not halt.")
}
// Pre clean VM
if !driverMock.PreCleanVMCalled {
t.Fatalf("driver.PreCleanVM should be called.")
}
if driverMock.PreCleanForce != step.Force {
t.Fatalf("Force PreCleanVM should be %t but was %t.", step.Force, driverMock.PreCleanForce)
}
if driverMock.PreCleanVMPath != vmPath {
t.Fatalf("VM path expected to be %s but was %s", vmPath, driverMock.PreCleanVMPath)
}
if !driverMock.FindVMCalled {
t.Fatalf("driver.FindVM should be called.")
}
if !vmMock.CloneCalled {
t.Fatalf("vm.Clone should be called.")
}
if diff := cmp.Diff(vmMock.CloneConfig, driverCreateConfig(step.Config, step.Location)); diff != "" {
t.Fatalf("wrong driver.CreateConfig: %s", diff)
}
vm, ok := state.GetOk("vm")
if !ok {
t.Fatal("state must contain the VM")
}
if vm != driverMock.VM {
t.Fatalf("state doesn't contain the created VM.")
}
}
func basicStepCloneVM() *StepCloneVM {
step := &StepCloneVM{
Config: createConfig(),
Location: basicLocationConfig(),
}
return step
}
func basicLocationConfig() *common.LocationConfig {
return &common.LocationConfig{
VMName: "test-vm",
Folder: "test-folder",
Cluster: "test-cluster",
Host: "test-host",
ResourcePool: "test-resource-pool",
Datastore: "test-datastore",
}
}
func createConfig() *CloneConfig {
return &CloneConfig{
Template: "template name",
StorageConfig: common.StorageConfig{
DiskControllerType: []string{"pvscsi"},
Storage: []common.DiskConfig{
{
DiskSize: 32768,
DiskThinProvisioned: true,
},
},
},
}
}
func driverCreateConfig(config *CloneConfig, location *common.LocationConfig) *driver.CloneConfig {
var disks []driver.Disk
for _, disk := range config.StorageConfig.Storage {
disks = append(disks, driver.Disk{
DiskSize: disk.DiskSize,
DiskEagerlyScrub: disk.DiskEagerlyScrub,
DiskThinProvisioned: disk.DiskThinProvisioned,
ControllerIndex: disk.DiskControllerIndex,
})
}
return &driver.CloneConfig{
StorageConfig: driver.StorageConfig{
DiskControllerType: config.StorageConfig.DiskControllerType,
Storage: disks,
},
Annotation: config.Notes,
Name: location.VMName,
Folder: location.Folder,
Cluster: location.Cluster,
Host: location.Host,
ResourcePool: location.ResourcePool,
Datastore: location.Datastore,
LinkedClone: config.LinkedClone,
Network: config.Network,
MacAddress: config.MacAddress,
VAppProperties: config.VAppConfig.Properties,
PrimaryDiskSize: config.DiskSize,
}
}

View File

@ -1,276 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CustomizeConfig,LinuxOptions,NetworkInterfaces,NetworkInterface,GlobalDnsSettings,GlobalRoutingSettings
package clone
import (
"context"
"fmt"
"io/ioutil"
"net"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/vmware/govmomi/vim25/types"
)
// A cloned virtual machine can be [customized](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-58E346FF-83AE-42B8-BE58-253641D257BC.html)
// to configure host, network, or licensing settings.
//
// To perform virtual machine customization as a part of the clone process, specify the customize block with the
// respective customization options. Windows guests are customized using Sysprep, which will result in the machine SID being reset.
// Before using customization, check that your source VM meets the [requirements](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-E63B6FAA-8D35-428D-B40C-744769845906.html)
// for guest OS customization on vSphere.
// See the [customization example](#customization-example) for a usage synopsis.
//
// The settings for customize are as follows:
type CustomizeConfig struct {
// Settings to Linux guest OS customization. See [Linux customization settings](#linux-customization-settings).
LinuxOptions *LinuxOptions `mapstructure:"linux_options"`
// Supply your own sysprep.xml file to allow full control of the customization process out-of-band of vSphere.
WindowsSysPrepFile string `mapstructure:"windows_sysprep_file"`
// Configure network interfaces on a per-interface basis that should matched up to the network adapters present in the VM.
// To use DHCP, declare an empty network_interface for each adapter being configured. This field is required.
// See [Network interface settings](#network-interface-settings).
NetworkInterfaces NetworkInterfaces `mapstructure:"network_interface"`
GlobalRoutingSettings `mapstructure:",squash"`
GlobalDnsSettings `mapstructure:",squash"`
}
type LinuxOptions struct {
// The domain name for this machine. This, along with [host_name](#host_name), make up the FQDN of this virtual machine.
Domain string `mapstructure:"domain"`
// The host name for this machine. This, along with [domain](#domain), make up the FQDN of this virtual machine.
Hostname string `mapstructure:"host_name"`
// Tells the operating system that the hardware clock is set to UTC. Default: true.
HWClockUTC config.Trilean `mapstructure:"hw_clock_utc"`
// Sets the time zone. The default is UTC.
Timezone string `mapstructure:"time_zone"`
}
type NetworkInterface struct {
// Network interface-specific DNS server settings for Windows operating systems.
// Ignored on Linux and possibly other operating systems - for those systems, please see the [global DNS settings](#global-dns-settings) section.
DnsServerList []string `mapstructure:"dns_server_list"`
// Network interface-specific DNS search domain for Windows operating systems.
// Ignored on Linux and possibly other operating systems - for those systems, please see the [global DNS settings](#global-dns-settings) section.
DnsDomain string `mapstructure:"dns_domain"`
// The IPv4 address assigned to this network adapter. If left blank or not included, DHCP is used.
Ipv4Address string `mapstructure:"ipv4_address"`
// The IPv4 subnet mask, in bits (example: 24 for 255.255.255.0).
Ipv4NetMask int `mapstructure:"ipv4_netmask"`
// The IPv6 address assigned to this network adapter. If left blank or not included, auto-configuration is used.
Ipv6Address string `mapstructure:"ipv6_address"`
// The IPv6 subnet mask, in bits (example: 32).
Ipv6NetMask int `mapstructure:"ipv6_netmask"`
}
type NetworkInterfaces []NetworkInterface
// The settings here must match the IP/mask of at least one network_interface supplied to customization.
type GlobalRoutingSettings struct {
// The IPv4 default gateway when using network_interface customization on the virtual machine.
Ipv4Gateway string `mapstructure:"ipv4_gateway"`
// The IPv6 default gateway when using network_interface customization on the virtual machine.
Ipv6Gateway string `mapstructure:"ipv6_gateway"`
}
// The following settings configure DNS globally, generally for Linux systems. For Windows systems,
// this is done per-interface, see [network interface](#network_interface) settings.
type GlobalDnsSettings struct {
// The list of DNS servers to configure on a virtual machine.
DnsServerList []string `mapstructure:"dns_server_list"`
// A list of DNS search domains to add to the DNS configuration on the virtual machine.
DnsSuffixList []string `mapstructure:"dns_suffix_list"`
}
type StepCustomize struct {
Config *CustomizeConfig
}
func (c *CustomizeConfig) Prepare() []error {
var errs []error
if c.LinuxOptions == nil && c.WindowsSysPrepFile == "" {
errs = append(errs, fmt.Errorf("customize is empty"))
}
if c.LinuxOptions != nil && c.WindowsSysPrepFile != "" {
errs = append(errs, fmt.Errorf("`linux_options` and `windows_sysprep_text` both set - one must not be included if the other is specified"))
}
if c.LinuxOptions != nil {
if c.LinuxOptions.Hostname == "" {
errs = append(errs, fmt.Errorf("linux options `host_name` is empty"))
}
if c.LinuxOptions.Domain == "" {
errs = append(errs, fmt.Errorf("linux options `domain` is empty"))
}
if c.LinuxOptions.HWClockUTC == config.TriUnset {
c.LinuxOptions.HWClockUTC = config.TriTrue
}
if c.LinuxOptions.Timezone == "" {
c.LinuxOptions.Timezone = "UTC"
}
}
if len(c.NetworkInterfaces) == 0 {
errs = append(errs, fmt.Errorf("one or more `network_interface` must be provided"))
}
return errs
}
func (s *StepCustomize) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
vm := state.Get("vm").(*driver.VirtualMachineDriver)
ui := state.Get("ui").(packersdk.Ui)
identity, err := s.identitySettings()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
nicSettingsMap := s.nicSettingsMap()
globalIpSettings := s.globalIpSettings()
spec := types.CustomizationSpec{
Identity: identity,
NicSettingMap: nicSettingsMap,
GlobalIPSettings: globalIpSettings,
}
ui.Say("Customizing VM...")
err = vm.Customize(spec)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCustomize) identitySettings() (types.BaseCustomizationIdentitySettings, error) {
if s.Config.LinuxOptions != nil {
return &types.CustomizationLinuxPrep{
HostName: &types.CustomizationFixedName{
Name: s.Config.LinuxOptions.Hostname,
},
Domain: s.Config.LinuxOptions.Domain,
TimeZone: s.Config.LinuxOptions.Timezone,
HwClockUTC: s.Config.LinuxOptions.HWClockUTC.ToBoolPointer(),
}, nil
}
if s.Config.WindowsSysPrepFile != "" {
sysPrep, err := ioutil.ReadFile(s.Config.WindowsSysPrepFile)
if err != nil {
return nil, fmt.Errorf("error on reading %s: %s", s.Config.WindowsSysPrepFile, err)
}
return &types.CustomizationSysprepText{
Value: string(sysPrep),
}, nil
}
return nil, fmt.Errorf("no customization identity found")
}
func (s *StepCustomize) nicSettingsMap() []types.CustomizationAdapterMapping {
result := make([]types.CustomizationAdapterMapping, len(s.Config.NetworkInterfaces))
var ipv4gwFound, ipv6gwFound bool
for i := range s.Config.NetworkInterfaces {
var adapter types.CustomizationIPSettings
adapter, ipv4gwFound, ipv6gwFound = s.ipSettings(i, !ipv4gwFound, !ipv6gwFound)
obj := types.CustomizationAdapterMapping{
Adapter: adapter,
}
result[i] = obj
}
return result
}
func (s *StepCustomize) ipSettings(n int, ipv4gwAdd bool, ipv6gwAdd bool) (types.CustomizationIPSettings, bool, bool) {
var v4gwFound, v6gwFound bool
var obj types.CustomizationIPSettings
ipv4Address := s.Config.NetworkInterfaces[n].Ipv4Address
if ipv4Address != "" {
ipv4mask := s.Config.NetworkInterfaces[n].Ipv4NetMask
ipv4Gateway := s.Config.Ipv4Gateway
obj.Ip = &types.CustomizationFixedIp{
IpAddress: ipv4Address,
}
obj.SubnetMask = v4CIDRMaskToDotted(ipv4mask)
// Check for the gateway
if ipv4gwAdd && ipv4Gateway != "" && matchGateway(ipv4Address, ipv4mask, ipv4Gateway) {
obj.Gateway = []string{ipv4Gateway}
v4gwFound = true
}
} else {
obj.Ip = &types.CustomizationDhcpIpGenerator{}
}
obj.DnsServerList = s.Config.NetworkInterfaces[n].DnsServerList
obj.DnsDomain = s.Config.NetworkInterfaces[n].DnsDomain
obj.IpV6Spec, v6gwFound = s.IPSettingsIPV6Address(n, ipv6gwAdd)
return obj, v4gwFound, v6gwFound
}
func v4CIDRMaskToDotted(mask int) string {
m := net.CIDRMask(mask, 32)
a := int(m[0])
b := int(m[1])
c := int(m[2])
d := int(m[3])
return fmt.Sprintf("%d.%d.%d.%d", a, b, c, d)
}
func (s *StepCustomize) IPSettingsIPV6Address(n int, gwAdd bool) (*types.CustomizationIPSettingsIpV6AddressSpec, bool) {
addr := s.Config.NetworkInterfaces[n].Ipv6Address
var gwFound bool
if addr == "" {
return nil, gwFound
}
mask := s.Config.NetworkInterfaces[n].Ipv6NetMask
gw := s.Config.Ipv6Gateway
obj := &types.CustomizationIPSettingsIpV6AddressSpec{
Ip: []types.BaseCustomizationIpV6Generator{
&types.CustomizationFixedIpV6{
IpAddress: addr,
SubnetMask: int32(mask),
},
},
}
if gwAdd && gw != "" && matchGateway(addr, mask, gw) {
obj.Gateway = []string{gw}
gwFound = true
}
return obj, gwFound
}
// matchGateway take an IP, mask, and gateway, and checks to see if the gateway
// is reachable from the IP address.
func matchGateway(a string, m int, g string) bool {
ip := net.ParseIP(a)
gw := net.ParseIP(g)
var mask net.IPMask
if ip.To4() != nil {
mask = net.CIDRMask(m, 32)
} else {
mask = net.CIDRMask(m, 128)
}
if ip.Mask(mask).Equal(gw.Mask(mask)) {
return true
}
return false
}
func (s *StepCustomize) globalIpSettings() types.CustomizationGlobalIPSettings {
return types.CustomizationGlobalIPSettings{
DnsServerList: s.Config.DnsServerList,
DnsSuffixList: s.Config.DnsSuffixList,
}
}
func (s *StepCustomize) Cleanup(_ multistep.StateBag) {}

View File

@ -1,155 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type CustomizeConfig,LinuxOptions,NetworkInterfaces,NetworkInterface,GlobalDnsSettings,GlobalRoutingSettings"; DO NOT EDIT.
package clone
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatCustomizeConfig is an auto-generated flat version of CustomizeConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCustomizeConfig struct {
LinuxOptions *FlatLinuxOptions `mapstructure:"linux_options" cty:"linux_options" hcl:"linux_options"`
WindowsSysPrepFile *string `mapstructure:"windows_sysprep_file" cty:"windows_sysprep_file" hcl:"windows_sysprep_file"`
NetworkInterfaces []FlatNetworkInterface `mapstructure:"network_interface" cty:"network_interface" hcl:"network_interface"`
Ipv4Gateway *string `mapstructure:"ipv4_gateway" cty:"ipv4_gateway" hcl:"ipv4_gateway"`
Ipv6Gateway *string `mapstructure:"ipv6_gateway" cty:"ipv6_gateway" hcl:"ipv6_gateway"`
DnsServerList []string `mapstructure:"dns_server_list" cty:"dns_server_list" hcl:"dns_server_list"`
DnsSuffixList []string `mapstructure:"dns_suffix_list" cty:"dns_suffix_list" hcl:"dns_suffix_list"`
}
// FlatMapstructure returns a new FlatCustomizeConfig.
// FlatCustomizeConfig is an auto-generated flat version of CustomizeConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*CustomizeConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatCustomizeConfig)
}
// HCL2Spec returns the hcl spec of a CustomizeConfig.
// This spec is used by HCL to read the fields of CustomizeConfig.
// The decoded values from this spec will then be applied to a FlatCustomizeConfig.
func (*FlatCustomizeConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"linux_options": &hcldec.BlockSpec{TypeName: "linux_options", Nested: hcldec.ObjectSpec((*FlatLinuxOptions)(nil).HCL2Spec())},
"windows_sysprep_file": &hcldec.AttrSpec{Name: "windows_sysprep_file", Type: cty.String, Required: false},
"network_interface": &hcldec.BlockListSpec{TypeName: "network_interface", Nested: hcldec.ObjectSpec((*FlatNetworkInterface)(nil).HCL2Spec())},
"ipv4_gateway": &hcldec.AttrSpec{Name: "ipv4_gateway", Type: cty.String, Required: false},
"ipv6_gateway": &hcldec.AttrSpec{Name: "ipv6_gateway", Type: cty.String, Required: false},
"dns_server_list": &hcldec.AttrSpec{Name: "dns_server_list", Type: cty.List(cty.String), Required: false},
"dns_suffix_list": &hcldec.AttrSpec{Name: "dns_suffix_list", Type: cty.List(cty.String), Required: false},
}
return s
}
// FlatGlobalDnsSettings is an auto-generated flat version of GlobalDnsSettings.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatGlobalDnsSettings struct {
DnsServerList []string `mapstructure:"dns_server_list" cty:"dns_server_list" hcl:"dns_server_list"`
DnsSuffixList []string `mapstructure:"dns_suffix_list" cty:"dns_suffix_list" hcl:"dns_suffix_list"`
}
// FlatMapstructure returns a new FlatGlobalDnsSettings.
// FlatGlobalDnsSettings is an auto-generated flat version of GlobalDnsSettings.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*GlobalDnsSettings) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatGlobalDnsSettings)
}
// HCL2Spec returns the hcl spec of a GlobalDnsSettings.
// This spec is used by HCL to read the fields of GlobalDnsSettings.
// The decoded values from this spec will then be applied to a FlatGlobalDnsSettings.
func (*FlatGlobalDnsSettings) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"dns_server_list": &hcldec.AttrSpec{Name: "dns_server_list", Type: cty.List(cty.String), Required: false},
"dns_suffix_list": &hcldec.AttrSpec{Name: "dns_suffix_list", Type: cty.List(cty.String), Required: false},
}
return s
}
// FlatGlobalRoutingSettings is an auto-generated flat version of GlobalRoutingSettings.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatGlobalRoutingSettings struct {
Ipv4Gateway *string `mapstructure:"ipv4_gateway" cty:"ipv4_gateway" hcl:"ipv4_gateway"`
Ipv6Gateway *string `mapstructure:"ipv6_gateway" cty:"ipv6_gateway" hcl:"ipv6_gateway"`
}
// FlatMapstructure returns a new FlatGlobalRoutingSettings.
// FlatGlobalRoutingSettings is an auto-generated flat version of GlobalRoutingSettings.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*GlobalRoutingSettings) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatGlobalRoutingSettings)
}
// HCL2Spec returns the hcl spec of a GlobalRoutingSettings.
// This spec is used by HCL to read the fields of GlobalRoutingSettings.
// The decoded values from this spec will then be applied to a FlatGlobalRoutingSettings.
func (*FlatGlobalRoutingSettings) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"ipv4_gateway": &hcldec.AttrSpec{Name: "ipv4_gateway", Type: cty.String, Required: false},
"ipv6_gateway": &hcldec.AttrSpec{Name: "ipv6_gateway", Type: cty.String, Required: false},
}
return s
}
// FlatLinuxOptions is an auto-generated flat version of LinuxOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatLinuxOptions struct {
Domain *string `mapstructure:"domain" cty:"domain" hcl:"domain"`
Hostname *string `mapstructure:"host_name" cty:"host_name" hcl:"host_name"`
HWClockUTC *bool `mapstructure:"hw_clock_utc" cty:"hw_clock_utc" hcl:"hw_clock_utc"`
Timezone *string `mapstructure:"time_zone" cty:"time_zone" hcl:"time_zone"`
}
// FlatMapstructure returns a new FlatLinuxOptions.
// FlatLinuxOptions is an auto-generated flat version of LinuxOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*LinuxOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatLinuxOptions)
}
// HCL2Spec returns the hcl spec of a LinuxOptions.
// This spec is used by HCL to read the fields of LinuxOptions.
// The decoded values from this spec will then be applied to a FlatLinuxOptions.
func (*FlatLinuxOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"domain": &hcldec.AttrSpec{Name: "domain", Type: cty.String, Required: false},
"host_name": &hcldec.AttrSpec{Name: "host_name", Type: cty.String, Required: false},
"hw_clock_utc": &hcldec.AttrSpec{Name: "hw_clock_utc", Type: cty.Bool, Required: false},
"time_zone": &hcldec.AttrSpec{Name: "time_zone", Type: cty.String, Required: false},
}
return s
}
// FlatNetworkInterface is an auto-generated flat version of NetworkInterface.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatNetworkInterface struct {
DnsServerList []string `mapstructure:"dns_server_list" cty:"dns_server_list" hcl:"dns_server_list"`
DnsDomain *string `mapstructure:"dns_domain" cty:"dns_domain" hcl:"dns_domain"`
Ipv4Address *string `mapstructure:"ipv4_address" cty:"ipv4_address" hcl:"ipv4_address"`
Ipv4NetMask *int `mapstructure:"ipv4_netmask" cty:"ipv4_netmask" hcl:"ipv4_netmask"`
Ipv6Address *string `mapstructure:"ipv6_address" cty:"ipv6_address" hcl:"ipv6_address"`
Ipv6NetMask *int `mapstructure:"ipv6_netmask" cty:"ipv6_netmask" hcl:"ipv6_netmask"`
}
// FlatMapstructure returns a new FlatNetworkInterface.
// FlatNetworkInterface is an auto-generated flat version of NetworkInterface.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*NetworkInterface) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatNetworkInterface)
}
// HCL2Spec returns the hcl spec of a NetworkInterface.
// This spec is used by HCL to read the fields of NetworkInterface.
// The decoded values from this spec will then be applied to a FlatNetworkInterface.
func (*FlatNetworkInterface) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"dns_server_list": &hcldec.AttrSpec{Name: "dns_server_list", Type: cty.List(cty.String), Required: false},
"dns_domain": &hcldec.AttrSpec{Name: "dns_domain", Type: cty.String, Required: false},
"ipv4_address": &hcldec.AttrSpec{Name: "ipv4_address", Type: cty.String, Required: false},
"ipv4_netmask": &hcldec.AttrSpec{Name: "ipv4_netmask", Type: cty.Number, Required: false},
"ipv6_address": &hcldec.AttrSpec{Name: "ipv6_address", Type: cty.String, Required: false},
"ipv6_netmask": &hcldec.AttrSpec{Name: "ipv6_netmask", Type: cty.Number, Required: false},
}
return s
}

View File

@ -1,50 +0,0 @@
package common
import (
"os"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
const BuilderId = "jetbrains.vsphere"
type Artifact struct {
Outconfig *OutputConfig
Name string
VM *driver.VirtualMachineDriver
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
}
func (a *Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
if a.Outconfig != nil {
files, _ := a.Outconfig.ListFiles()
return files
}
return []string{}
}
func (a *Artifact) Id() string {
return a.Name
}
func (a *Artifact) String() string {
return a.Name
}
func (a *Artifact) State(name string) interface{} {
return a.StateData[name]
}
func (a *Artifact) Destroy() error {
if a.Outconfig != nil {
os.RemoveAll(a.Outconfig.OutputDir)
}
return a.VM.Destroy()
}

View File

@ -1,30 +0,0 @@
package common
import (
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func CleanupVM(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
_, destroy := state.GetOk("destroy_vm")
if !cancelled && !halted && !destroy {
return
}
ui := state.Get("ui").(packersdk.Ui)
st := state.Get("vm")
if st == nil {
return
}
vm := st.(driver.VirtualMachine)
ui.Say("Destroying VM...")
err := vm.Destroy()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -1,62 +0,0 @@
package common
import (
"bytes"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func cleanupTestState(mockVM driver.VirtualMachine) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("vm", mockVM)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
func Test_CleanupVM(t *testing.T) {
type testCase struct {
Reason string
ExtraState map[string]interface{}
ExpectDestroy bool
}
testCases := []testCase{
{
"if cancelled, we should destroy the VM",
map[string]interface{}{multistep.StateCancelled: true},
true,
},
{
"if halted, we should destroy the VM",
map[string]interface{}{multistep.StateHalted: true},
true,
},
{
"if destroy flag is set, we should destroy the VM",
map[string]interface{}{"destroy_vm": true},
true,
},
{
"if none of the above flags are set, we should not destroy the VM",
map[string]interface{}{},
false,
},
}
for _, tc := range testCases {
mockVM := &driver.VirtualMachineMock{}
state := cleanupTestState(mockVM)
for k, v := range tc.ExtraState {
state.Put(k, v)
}
CleanupVM(state)
if mockVM.DestroyCalled != tc.ExpectDestroy {
t.Fatalf("Problem with cleanup: %s", tc.Reason)
}
}
}

View File

@ -1,19 +0,0 @@
package common
import (
"bytes"
"strings"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func basicStateBag(errorBuffer *strings.Builder) *multistep.BasicStateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
ErrorWriter: errorBuffer,
})
return state
}

View File

@ -1,53 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type LocationConfig
package common
import (
"fmt"
"path"
"strings"
)
type LocationConfig struct {
// Name of the new VM to create.
VMName string `mapstructure:"vm_name"`
// VM folder to create the VM in.
Folder string `mapstructure:"folder"`
// ESXi cluster where target VM is created. See the
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
// section above for more details.
Cluster string `mapstructure:"cluster"`
// ESXi host where target VM is created. A full path must be specified if
// the host is in a folder. For example `folder/host`. See the
// [Working With Clusters And Hosts](#working-with-clusters-and-hosts)
// section above for more details.
Host string `mapstructure:"host"`
// VMWare resource pool. If not set, it will look for the root resource
// pool of the `host` or `cluster`. If a root resource is not found, it
// will then look for a default resource pool.
ResourcePool string `mapstructure:"resource_pool"`
// VMWare datastore. Required if `host` is a cluster, or if `host` has
// multiple datastores.
Datastore string `mapstructure:"datastore"`
// Set this to true if packer should use the host for uploading files
// to the datastore. Defaults to false.
SetHostForDatastoreUploads bool `mapstructure:"set_host_for_datastore_uploads"`
}
func (c *LocationConfig) Prepare() []error {
var errs []error
if c.VMName == "" {
errs = append(errs, fmt.Errorf("'vm_name' is required"))
}
if c.Cluster == "" && c.Host == "" {
errs = append(errs, fmt.Errorf("'host' or 'cluster' is required"))
}
// clean Folder path and remove leading slash as folders are relative within vsphere
c.Folder = path.Clean(c.Folder)
c.Folder = strings.TrimLeft(c.Folder, "/")
return errs
}

View File

@ -1,43 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type LocationConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatLocationConfig is an auto-generated flat version of LocationConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatLocationConfig struct {
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"`
Cluster *string `mapstructure:"cluster" cty:"cluster" hcl:"cluster"`
Host *string `mapstructure:"host" cty:"host" hcl:"host"`
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool" hcl:"resource_pool"`
Datastore *string `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
SetHostForDatastoreUploads *bool `mapstructure:"set_host_for_datastore_uploads" cty:"set_host_for_datastore_uploads" hcl:"set_host_for_datastore_uploads"`
}
// FlatMapstructure returns a new FlatLocationConfig.
// FlatLocationConfig is an auto-generated flat version of LocationConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*LocationConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatLocationConfig)
}
// HCL2Spec returns the hcl spec of a LocationConfig.
// This spec is used by HCL to read the fields of LocationConfig.
// The decoded values from this spec will then be applied to a FlatLocationConfig.
func (*FlatLocationConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"vm_name": &hcldec.AttrSpec{Name: "vm_name", 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},
"host": &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
"resource_pool": &hcldec.AttrSpec{Name: "resource_pool", Type: cty.String, Required: false},
"datastore": &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
"set_host_for_datastore_uploads": &hcldec.AttrSpec{Name: "set_host_for_datastore_uploads", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,15 +0,0 @@
package common
import (
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func CommHost(host string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
if host != "" {
return host, nil
} else {
return state.Get("ip").(string), nil
}
}
}

View File

@ -1,59 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type OutputConfig
package common
import (
"fmt"
"os"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
type OutputConfig struct {
// This setting specifies the directory that
// artifacts from the build, such as the virtual machine files and disks,
// will be output to. The path to the directory may be relative or
// absolute. If relative, the path is relative to the working directory
// packer is executed from. This directory must not exist or, if
// created, must be empty prior to running the builder. By default this is
// "output-BUILDNAME" where "BUILDNAME" is the name of the build.
OutputDir string `mapstructure:"output_directory" required:"false"`
// The permissions to apply to the "output_directory", and to any parent
// directories that get created for output_directory. By default this is
// "0750". You should express the permission as quoted string with a
// leading zero such as "0755" in JSON file, because JSON does not support
// octal value. In Unix-like OS, the actual permission may differ from
// this value because of umask.
DirPerm os.FileMode `mapstructure:"directory_permission" required:"false"`
}
func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error {
if c.OutputDir == "" {
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
}
if c.DirPerm == 0 {
c.DirPerm = 0750
}
return nil
}
// Stolen from output_dir_local.go in vmware builder.
func (c *OutputConfig) ListFiles() ([]string, error) {
files := make([]string, 0, 10)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
}
return files, filepath.Walk(c.OutputDir, visit)
}

View File

@ -1,35 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type OutputConfig"; DO NOT EDIT.
package common
import (
"io/fs"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatOutputConfig is an auto-generated flat version of OutputConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatOutputConfig struct {
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
DirPerm *fs.FileMode `mapstructure:"directory_permission" required:"false" cty:"directory_permission" hcl:"directory_permission"`
}
// FlatMapstructure returns a new FlatOutputConfig.
// FlatOutputConfig is an auto-generated flat version of OutputConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*OutputConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatOutputConfig)
}
// HCL2Spec returns the hcl spec of a OutputConfig.
// This spec is used by HCL to read the fields of OutputConfig.
// The decoded values from this spec will then be applied to a FlatOutputConfig.
func (*FlatOutputConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
"directory_permission": &hcldec.AttrSpec{Name: "directory_permission", Type: cty.Number, Required: false},
}
return s
}

View File

@ -1,81 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CDRomConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type CDRomConfig struct {
// Which controller to use. Example: `sata`. Defaults to `ide`.
CdromType string `mapstructure:"cdrom_type"`
// List of Datastore or Content Library paths to ISO files that will be mounted to the VM.
// Here's an HCL2 example:
// ```hcl
// iso_paths = [
// "[datastore1] ISO/ubuntu.iso",
// "Packer Library Test/ubuntu-16.04.6-server-amd64/ubuntu-16.04.6-server-amd64.iso"
// ]
// ```
ISOPaths []string `mapstructure:"iso_paths"`
}
type StepAddCDRom struct {
Config *CDRomConfig
}
func (c *CDRomConfig) Prepare() []error {
var errs []error
if c.CdromType != "" && c.CdromType != "ide" && c.CdromType != "sata" {
errs = append(errs, fmt.Errorf("'cdrom_type' must be 'ide' or 'sata'"))
}
return errs
}
func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(driver.VirtualMachine)
if s.Config.CdromType == "sata" {
if _, err := vm.FindSATAController(); err == driver.ErrNoSataController {
ui.Say("Adding SATA controller...")
if err := vm.AddSATAController(); err != nil {
state.Put("error", fmt.Errorf("error adding SATA controller: %v", err))
return multistep.ActionHalt
}
}
}
ui.Say("Mounting ISO images...")
if path, ok := state.GetOk("iso_remote_path"); ok {
if err := vm.AddCdrom(s.Config.CdromType, path.(string)); err != nil {
state.Put("error", fmt.Errorf("error mounting an image '%v': %v", path, err))
return multistep.ActionHalt
}
}
// Add our custom CD, if it exists
if cd_path, _ := state.Get("cd_path").(string); cd_path != "" {
s.Config.ISOPaths = append(s.Config.ISOPaths, cd_path)
}
if len(s.Config.ISOPaths) > 0 {
for _, path := range s.Config.ISOPaths {
if err := vm.AddCdrom(s.Config.CdromType, path); err != nil {
state.Put("error", fmt.Errorf("error mounting an image '%v': %v", path, err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepAddCDRom) Cleanup(state multistep.StateBag) {}

View File

@ -1,33 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type CDRomConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatCDRomConfig is an auto-generated flat version of CDRomConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCDRomConfig struct {
CdromType *string `mapstructure:"cdrom_type" cty:"cdrom_type" hcl:"cdrom_type"`
ISOPaths []string `mapstructure:"iso_paths" cty:"iso_paths" hcl:"iso_paths"`
}
// FlatMapstructure returns a new FlatCDRomConfig.
// FlatCDRomConfig is an auto-generated flat version of CDRomConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*CDRomConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatCDRomConfig)
}
// HCL2Spec returns the hcl spec of a CDRomConfig.
// This spec is used by HCL to read the fields of CDRomConfig.
// The decoded values from this spec will then be applied to a FlatCDRomConfig.
func (*FlatCDRomConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"cdrom_type": &hcldec.AttrSpec{Name: "cdrom_type", Type: cty.String, Required: false},
"iso_paths": &hcldec.AttrSpec{Name: "iso_paths", Type: cty.List(cty.String), Required: false},
}
return s
}

View File

@ -1,235 +0,0 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestCDRomConfig_Prepare(t *testing.T) {
// Data validation
tc := []struct {
name string
config *CDRomConfig
fail bool
expectedErrMsg string
}{
{
name: "Should not fail for empty config",
config: new(CDRomConfig),
fail: false,
expectedErrMsg: "",
},
{
name: "Valid cdroom type ide",
config: &CDRomConfig{CdromType: "ide"},
fail: false,
expectedErrMsg: "",
},
{
name: "Valid cdroom type sata",
config: &CDRomConfig{CdromType: "ide"},
fail: false,
expectedErrMsg: "",
},
{
name: "Invalid cdroom type",
config: &CDRomConfig{CdromType: "invalid"},
fail: true,
expectedErrMsg: "'cdrom_type' must be 'ide' or 'sata'",
},
}
for _, c := range tc {
errs := c.config.Prepare()
if c.fail {
if len(errs) == 0 {
t.Fatalf("Config preprare should fail")
}
if errs[0].Error() != c.expectedErrMsg {
t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
}
} else {
if len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
}
}
}
}
func TestStepAddCDRom_Run(t *testing.T) {
tc := []struct {
name string
state *multistep.BasicStateBag
step *StepAddCDRom
vmMock *driver.VirtualMachineMock
expectedAction multistep.StepAction
expectedVmMock *driver.VirtualMachineMock
fail bool
errMessage string
}{
{
name: "CDRom SATA type with all iso paths set",
state: cdAndIsoRemotePathStateBag(),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "sata",
ISOPaths: []string{"iso/path"},
},
},
vmMock: new(driver.VirtualMachineMock),
expectedAction: multistep.ActionContinue,
expectedVmMock: &driver.VirtualMachineMock{
FindSATAControllerCalled: true,
AddCdromCalled: true,
AddCdromCalledTimes: 3,
AddCdromTypes: []string{"sata", "sata", "sata"},
AddCdromPaths: []string{"remote/path", "iso/path", "cd/path"},
},
fail: false,
errMessage: "",
},
{
name: "Add SATA Controller",
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "sata",
},
},
vmMock: &driver.VirtualMachineMock{
FindSATAControllerErr: driver.ErrNoSataController,
},
expectedAction: multistep.ActionContinue,
expectedVmMock: &driver.VirtualMachineMock{
FindSATAControllerCalled: true,
FindSATAControllerErr: driver.ErrNoSataController,
AddSATAControllerCalled: true,
},
fail: false,
errMessage: "",
},
{
name: "Fail to add SATA Controller",
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "sata",
},
},
vmMock: &driver.VirtualMachineMock{
FindSATAControllerErr: driver.ErrNoSataController,
AddSATAControllerErr: fmt.Errorf("AddSATAController error"),
},
expectedAction: multistep.ActionHalt,
expectedVmMock: &driver.VirtualMachineMock{
FindSATAControllerCalled: true,
AddSATAControllerCalled: true,
},
fail: true,
errMessage: fmt.Sprintf("error adding SATA controller: %v", fmt.Errorf("AddSATAController error")),
},
{
name: "IDE CDRom Type and Iso Path set",
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
CdromType: "ide",
ISOPaths: []string{"iso/path"},
},
},
vmMock: new(driver.VirtualMachineMock),
expectedAction: multistep.ActionContinue,
expectedVmMock: &driver.VirtualMachineMock{
AddCdromCalled: true,
AddCdromCalledTimes: 1,
AddCdromTypes: []string{"ide"},
AddCdromPaths: []string{"iso/path"},
},
fail: false,
errMessage: "",
},
{
name: "Fail to add cdrom from ISOPaths",
state: basicStateBag(nil),
step: &StepAddCDRom{
Config: &CDRomConfig{
ISOPaths: []string{"iso/path"},
},
},
vmMock: &driver.VirtualMachineMock{
AddCdromErr: fmt.Errorf("AddCdrom error"),
},
expectedAction: multistep.ActionHalt,
expectedVmMock: &driver.VirtualMachineMock{
AddCdromCalled: true,
AddCdromCalledTimes: 1,
AddCdromTypes: []string{""},
AddCdromPaths: []string{"iso/path"},
},
fail: true,
errMessage: fmt.Sprintf("error mounting an image 'iso/path': %v", fmt.Errorf("AddCdrom error")),
},
{
name: "Fail to add cdrom from state iso_remote_path",
state: isoRemotePathStateBag(),
step: &StepAddCDRom{
Config: new(CDRomConfig),
},
vmMock: &driver.VirtualMachineMock{
AddCdromErr: fmt.Errorf("AddCdrom error"),
},
expectedAction: multistep.ActionHalt,
expectedVmMock: &driver.VirtualMachineMock{
AddCdromCalled: true,
AddCdromCalledTimes: 1,
AddCdromTypes: []string{""},
AddCdromPaths: []string{"remote/path"},
},
fail: true,
errMessage: fmt.Sprintf("error mounting an image 'remote/path': %v", fmt.Errorf("AddCdrom error")),
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
c.state.Put("vm", c.vmMock)
if action := c.step.Run(context.TODO(), c.state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := c.state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
})
}
}
func cdAndIsoRemotePathStateBag() *multistep.BasicStateBag {
state := basicStateBag(nil)
state.Put("iso_remote_path", "remote/path")
state.Put("cd_path", "cd/path")
return state
}
func isoRemotePathStateBag() *multistep.BasicStateBag {
state := basicStateBag(nil)
state.Put("iso_remote_path", "remote/path")
return state
}

View File

@ -1,111 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type FloppyConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type FloppyConfig struct {
// Datastore path to a floppy image that will be mounted to the VM.
// Example: `[datastore1] ISO/pvscsi-Windows8.flp`.
FloppyIMGPath string `mapstructure:"floppy_img_path"`
// List of local files to be mounted to the VM floppy drive. Can be used to
// make Debian preseed or RHEL kickstart files available to the VM.
FloppyFiles []string `mapstructure:"floppy_files"`
// List of directories to copy files from.
FloppyDirectories []string `mapstructure:"floppy_dirs"`
// The label to use for the floppy disk that
// is attached when the VM is booted. This is most useful for cloud-init,
// Kickstart or other early initialization tools, which can benefit from labelled floppy disks.
// By default, the floppy label will be 'packer'.
FloppyLabel string `mapstructure:"floppy_label"`
}
type StepAddFloppy struct {
Config *FloppyConfig
Datastore string
Host string
SetHostForDatastoreUploads bool
}
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(driver.VirtualMachine)
d := state.Get("driver").(driver.Driver)
if floppyPath, ok := state.GetOk("floppy_path"); ok {
ui.Say("Uploading created floppy image")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
vmDir, err := vm.GetDir()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
uploadPath := fmt.Sprintf("%v/packer-tmp-created-floppy.flp", vmDir)
if err := ds.UploadFile(floppyPath.(string), uploadPath, s.Host, s.SetHostForDatastoreUploads); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("uploaded_floppy_path", uploadPath)
ui.Say("Adding generated Floppy...")
floppyIMGPath := ds.ResolvePath(uploadPath)
err = vm.AddFloppy(floppyIMGPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
if s.Config.FloppyIMGPath != "" {
ui.Say("Adding Floppy image...")
err := vm.AddFloppy(s.Config.FloppyIMGPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepAddFloppy) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packersdk.Ui)
d := state.Get("driver").(driver.Driver)
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image ...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return
}
err = ds.Delete(UploadedFloppyPath.(string))
if err != nil {
state.Put("error", err)
return
}
}
}

View File

@ -1,37 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type FloppyConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatFloppyConfig is an auto-generated flat version of FloppyConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatFloppyConfig struct {
FloppyIMGPath *string `mapstructure:"floppy_img_path" cty:"floppy_img_path" hcl:"floppy_img_path"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
}
// FlatMapstructure returns a new FlatFloppyConfig.
// FlatFloppyConfig is an auto-generated flat version of FloppyConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*FloppyConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatFloppyConfig)
}
// HCL2Spec returns the hcl spec of a FloppyConfig.
// This spec is used by HCL to read the fields of FloppyConfig.
// The decoded values from this spec will then be applied to a FlatFloppyConfig.
func (*FlatFloppyConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"floppy_img_path": &hcldec.AttrSpec{Name: "floppy_img_path", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,453 +0,0 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestStepAddFloppy_Run(t *testing.T) {
tc := []struct {
name string
floppyPath string
uploadedPath string
step *StepAddFloppy
expectedAction multistep.StepAction
vmMock *driver.VirtualMachineMock
expectedVmMock *driver.VirtualMachineMock
driverMock *driver.DriverMock
expectedDriverMock *driver.DriverMock
dsMock *driver.DatastoreMock
expectedDsMock *driver.DatastoreMock
fail bool
errMessage string
}{
{
name: "Add floppy from state floppy path",
floppyPath: "floppy/path",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionContinue,
vmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
GetDirCalled: true,
AddFloppyCalled: true,
AddFloppyImagePath: "resolved/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
ResolvePathReturn: "resolved/path",
},
expectedDsMock: &driver.DatastoreMock{
UploadFileCalled: true,
UploadFileSrc: "floppy/path",
UploadFileDst: "vm/dir/packer-tmp-created-floppy.flp",
UploadFileHost: "host",
UploadFileSetHost: true,
ResolvePathCalled: true,
ResolvePathReturn: "resolved/path",
},
fail: false,
},
{
name: "State floppy path - find datastore fail",
floppyPath: "floppy/path",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: new(driver.VirtualMachineMock),
driverMock: &driver.DriverMock{
FindDatastoreErr: fmt.Errorf("error finding datastore"),
},
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "error finding datastore",
},
{
name: "State floppy path - vm get dir fail",
floppyPath: "floppy/path",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
GetDirErr: fmt.Errorf("fail to get vm dir"),
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirCalled: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "fail to get vm dir",
},
{
name: "State floppy path - datastore upload file fail",
floppyPath: "floppy/path",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
GetDirCalled: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
UploadFileErr: fmt.Errorf("failed to upload file"),
},
expectedDsMock: &driver.DatastoreMock{
UploadFileCalled: true,
UploadFileSrc: "floppy/path",
UploadFileDst: "vm/dir/packer-tmp-created-floppy.flp",
UploadFileHost: "host",
UploadFileSetHost: true,
},
fail: true,
errMessage: "failed to upload file",
},
{
name: "State floppy path - vm fail to add floppy",
floppyPath: "floppy/path",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepAddFloppy{
Config: new(FloppyConfig),
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: true,
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
AddFloppyErr: fmt.Errorf("failed to add floppy"),
},
expectedVmMock: &driver.VirtualMachineMock{
GetDirResponse: "vm/dir",
GetDirCalled: true,
AddFloppyCalled: true,
AddFloppyImagePath: "resolved/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
ResolvePathReturn: "resolved/path",
},
expectedDsMock: &driver.DatastoreMock{
UploadFileCalled: true,
UploadFileSrc: "floppy/path",
UploadFileDst: "vm/dir/packer-tmp-created-floppy.flp",
UploadFileHost: "host",
UploadFileSetHost: true,
ResolvePathCalled: true,
ResolvePathReturn: "resolved/path",
},
fail: true,
errMessage: "failed to add floppy",
},
{
name: "Add floppy from FloppyIMGPath config",
step: &StepAddFloppy{
Config: &FloppyConfig{
FloppyIMGPath: "floppy/image/path",
},
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
AddFloppyCalled: true,
AddFloppyImagePath: "floppy/image/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Fail to add floppy from FloppyIMGPath config",
step: &StepAddFloppy{
Config: &FloppyConfig{
FloppyIMGPath: "floppy/image/path",
},
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
AddFloppyErr: fmt.Errorf("fail to add floppy"),
},
expectedVmMock: &driver.VirtualMachineMock{
AddFloppyCalled: true,
AddFloppyImagePath: "floppy/image/path",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "fail to add floppy",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
state.Put("vm", c.vmMock)
c.driverMock.DatastoreMock = c.dsMock
state.Put("driver", c.driverMock)
if c.floppyPath != "" {
state.Put("floppy_path", c.floppyPath)
}
if action := c.step.Run(context.TODO(), state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
uploadedPath, _ := state.Get("uploaded_floppy_path").(string)
if uploadedPath != c.uploadedPath {
t.Fatalf("Unexpected uploaded path state %s", uploadedPath)
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
c.expectedDriverMock.DatastoreMock = c.expectedDsMock
if diff := cmp.Diff(c.driverMock, c.expectedDriverMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Driver calls: %s", diff)
}
if diff := cmp.Diff(c.dsMock, c.expectedDsMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Datastore calls: %s", diff)
}
})
}
}
func TestStepAddFloppy_Cleanup(t *testing.T) {
tc := []struct {
name string
uploadedPath string
multistepState string
step *StepAddFloppy
driverMock *driver.DriverMock
expectedDriverMock *driver.DriverMock
dsMock *driver.DatastoreMock
expectedDsMock *driver.DatastoreMock
fail bool
errMessage string
}{
{
name: "State cancelled clean up",
uploadedPath: "uploaded/path",
multistepState: multistep.StateCancelled,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteCalled: true,
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "uploaded/path",
},
fail: false,
},
{
name: "State halted clean up",
uploadedPath: "uploaded/path",
multistepState: multistep.StateHalted,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteCalled: true,
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "uploaded/path",
},
fail: false,
},
{
name: "Don't clean up without uploaded path",
multistepState: multistep.StateHalted,
step: new(StepAddFloppy),
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Don't clean up if state is not halted or canceled",
multistepState: "",
step: new(StepAddFloppy),
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Fail because datastore is not found",
uploadedPath: "uploaded/path",
multistepState: multistep.StateHalted,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: &driver.DriverMock{
FindDatastoreErr: fmt.Errorf("fail to find datastore"),
},
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "fail to find datastore",
},
{
name: "Fail to delete floppy",
uploadedPath: "uploaded/path",
multistepState: multistep.StateHalted,
step: &StepAddFloppy{
Datastore: "datastore",
Host: "host",
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeleteErr: fmt.Errorf("failed to delete floppy"),
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "uploaded/path",
},
fail: true,
errMessage: "failed to delete floppy",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
c.driverMock.DatastoreMock = c.dsMock
state.Put("driver", c.driverMock)
if c.uploadedPath != "" {
state.Put("uploaded_floppy_path", c.uploadedPath)
}
if c.multistepState != "" {
state.Put(c.multistepState, true)
}
c.step.Cleanup(state)
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
c.expectedDriverMock.DatastoreMock = c.expectedDsMock
if diff := cmp.Diff(c.driverMock, c.expectedDriverMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Driver calls: %s", diff)
}
if diff := cmp.Diff(c.dsMock, c.expectedDsMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Datastore calls: %s", diff)
}
})
}
}

View File

@ -1,152 +0,0 @@
//go:generate struct-markdown
package common
import (
"context"
"fmt"
"time"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/vsphere/driver"
"golang.org/x/mobile/event/key"
)
type BootConfig struct {
bootcommand.BootConfig `mapstructure:",squash"`
// The IP address to use for the HTTP server started to serve the `http_directory`.
// If unset, Packer will automatically discover and assign an IP.
HTTPIP string `mapstructure:"http_ip"`
}
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort int
Name string
}
func (c *BootConfig) Prepare(ctx *interpolate.Context) []error {
if c.BootWait == 0 {
c.BootWait = 10 * time.Second
}
return c.BootConfig.Prepare(ctx)
}
type StepBootCommand struct {
Config *BootConfig
VMName string
Ctx interpolate.Context
}
func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
debug := state.Get("debug").(bool)
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.BootCommand == nil {
return multistep.ActionContinue
}
// Wait the for the vm to boot.
if int64(s.Config.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String()))
select {
case <-time.After(s.Config.BootWait):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
var pauseFn multistep.DebugPauseFn
if debug {
pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn)
}
port := state.Get("http_port").(int)
if port > 0 {
ip := state.Get("http_ip").(string)
s.Ctx.Data = &bootCommandTemplateData{
ip,
port,
s.VMName,
}
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
}
var keyAlt, keyCtrl, keyShift bool
sendCodes := func(code key.Code, down bool) error {
switch code {
case key.CodeLeftAlt:
keyAlt = down
case key.CodeLeftControl:
keyCtrl = down
case key.CodeLeftShift:
keyShift = down
}
shift := down
if keyShift {
shift = keyShift
}
_, err := vm.TypeOnKeyboard(driver.KeyInput{
Scancode: code,
Ctrl: keyCtrl,
Alt: keyAlt,
Shift: shift,
})
if err != nil {
// retry once if error
ui.Error(fmt.Errorf("error typing a boot command (code, down) `%d, %t`: %w", code, down, err).Error())
ui.Say("trying key input again")
time.Sleep(s.Config.BootGroupInterval)
_, err = vm.TypeOnKeyboard(driver.KeyInput{
Scancode: code,
Ctrl: keyCtrl,
Alt: keyAlt,
Shift: shift,
})
if err != nil {
return fmt.Errorf("error typing a boot command (code, down) `%d, %t`: %w", code, down, err)
}
}
return nil
}
d := bootcommand.NewUSBDriver(sendCodes, s.Config.BootGroupInterval)
ui.Say("Typing boot command...")
flatBootCommand := s.Config.FlatBootCommand()
command, err := interpolate.Render(flatBootCommand, &s.Ctx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
seq, err := bootcommand.GenerateExpressionSequence(command)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if pauseFn != nil {
pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state)
}
return multistep.ActionContinue
}
func (s *StepBootCommand) Cleanup(_ multistep.StateBag) {}

View File

@ -1,65 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ConfigParamsConfig
package common
import (
"context"
"fmt"
"github.com/vmware/govmomi/vim25/types"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type ConfigParamsConfig struct {
// configuration_parameters is a direct passthrough to the VSphere API's
// ConfigSpec: https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.ConfigSpec.html
ConfigParams map[string]string `mapstructure:"configuration_parameters"`
// Enables time synchronization with the host. Defaults to false.
ToolsSyncTime bool `mapstructure:"tools_sync_time"`
// If sets to true, vSphere will automatically check and upgrade VMware Tools upon a system power cycle.
// If not set, defaults to manual upgrade.
ToolsUpgradePolicy bool `mapstructure:"tools_upgrade_policy"`
}
type StepConfigParams struct {
Config *ConfigParamsConfig
}
func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
configParams := make(map[string]string)
if s.Config.ConfigParams != nil {
configParams = s.Config.ConfigParams
}
var info *types.ToolsConfigInfo
if s.Config.ToolsSyncTime || s.Config.ToolsUpgradePolicy {
info = &types.ToolsConfigInfo{}
if s.Config.ToolsSyncTime {
info.SyncTimeWithHost = &s.Config.ToolsSyncTime
}
if s.Config.ToolsUpgradePolicy {
info.ToolsUpgradePolicy = "UpgradeAtPowerCycle"
}
}
ui.Say("Adding configuration parameters...")
if err := vm.AddConfigParams(configParams, info); err != nil {
state.Put("error", fmt.Errorf("error adding configuration parameters: %v", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepConfigParams) Cleanup(state multistep.StateBag) {}

View File

@ -1,35 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type ConfigParamsConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfigParamsConfig is an auto-generated flat version of ConfigParamsConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfigParamsConfig struct {
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters" hcl:"configuration_parameters"`
ToolsSyncTime *bool `mapstructure:"tools_sync_time" cty:"tools_sync_time" hcl:"tools_sync_time"`
ToolsUpgradePolicy *bool `mapstructure:"tools_upgrade_policy" cty:"tools_upgrade_policy" hcl:"tools_upgrade_policy"`
}
// FlatMapstructure returns a new FlatConfigParamsConfig.
// FlatConfigParamsConfig is an auto-generated flat version of ConfigParamsConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ConfigParamsConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfigParamsConfig)
}
// HCL2Spec returns the hcl spec of a ConfigParamsConfig.
// This spec is used by HCL to read the fields of ConfigParamsConfig.
// The decoded values from this spec will then be applied to a FlatConfigParamsConfig.
func (*FlatConfigParamsConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"configuration_parameters": &hcldec.AttrSpec{Name: "configuration_parameters", Type: cty.Map(cty.String), Required: false},
"tools_sync_time": &hcldec.AttrSpec{Name: "tools_sync_time", Type: cty.Bool, Required: false},
"tools_upgrade_policy": &hcldec.AttrSpec{Name: "tools_upgrade_policy", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,64 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ConnectConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type ConnectConfig struct {
// vCenter server hostname.
VCenterServer string `mapstructure:"vcenter_server"`
// vSphere username.
Username string `mapstructure:"username"`
// vSphere password.
Password string `mapstructure:"password"`
// Do not validate vCenter server's TLS certificate. Defaults to `false`.
InsecureConnection bool `mapstructure:"insecure_connection"`
// VMware datacenter name. Required if there is more than one datacenter in vCenter.
Datacenter string `mapstructure:"datacenter"`
}
func (c *ConnectConfig) Prepare() []error {
var errs []error
if c.VCenterServer == "" {
errs = append(errs, fmt.Errorf("'vcenter_server' is required"))
}
if c.Username == "" {
errs = append(errs, fmt.Errorf("'username' is required"))
}
if c.Password == "" {
errs = append(errs, fmt.Errorf("'password' is required"))
}
return errs
}
type StepConnect struct {
Config *ConnectConfig
}
func (s *StepConnect) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: s.Config.VCenterServer,
Username: s.Config.Username,
Password: s.Config.Password,
InsecureConnection: s.Config.InsecureConnection,
Datacenter: s.Config.Datacenter,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("driver", d)
return multistep.ActionContinue
}
func (s *StepConnect) Cleanup(multistep.StateBag) {}

View File

@ -1,39 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type ConnectConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConnectConfig is an auto-generated flat version of ConnectConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConnectConfig struct {
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server" hcl:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection" hcl:"insecure_connection"`
Datacenter *string `mapstructure:"datacenter" cty:"datacenter" hcl:"datacenter"`
}
// FlatMapstructure returns a new FlatConnectConfig.
// FlatConnectConfig is an auto-generated flat version of ConnectConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ConnectConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConnectConfig)
}
// HCL2Spec returns the hcl spec of a ConnectConfig.
// This spec is used by HCL to read the fields of ConnectConfig.
// The decoded values from this spec will then be applied to a FlatConnectConfig.
func (*FlatConnectConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"vcenter_server": &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"insecure_connection": &hcldec.AttrSpec{Name: "insecure_connection", Type: cty.Bool, Required: false},
"datacenter": &hcldec.AttrSpec{Name: "datacenter", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,68 +0,0 @@
package common
import (
"context"
"fmt"
"net/url"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
// Defining this interface ensures that we use the common step download, or the
// mock created to test this wrapper
type DownloadStep interface {
Run(context.Context, multistep.StateBag) multistep.StepAction
Cleanup(multistep.StateBag)
UseSourceToFindCacheTarget(source string) (*url.URL, string, error)
}
// VSphere has a specialized need -- before we waste time downloading an iso,
// we need to check whether that iso already exists on the remote datastore.
// if it does, we skip the download. This wrapping-step still uses the common
// StepDownload but only if the image isn't already present on the datastore.
type StepDownload struct {
DownloadStep DownloadStep
// These keys are VSphere-specific and used to check the remote datastore.
Url []string
ResultKey string
Datastore string
Host string
}
func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(driver.Driver)
ui := state.Get("ui").(packersdk.Ui)
// Check whether iso is present on remote datastore.
ds, err := driver.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", fmt.Errorf("datastore doesn't exist: %v", err))
return multistep.ActionHalt
}
// loop over URLs to see if any are already present. If they are, store that
// one instate and continue
for _, source := range s.Url {
_, targetPath, err := s.DownloadStep.UseSourceToFindCacheTarget(source)
if err != nil {
state.Put("error", fmt.Errorf("Error getting target path: %s", err))
return multistep.ActionHalt
}
_, remotePath, _, _ := GetRemoteDirectoryAndPath(targetPath, ds)
if exists := ds.FileExists(remotePath); exists {
ui.Say(fmt.Sprintf("File %s already uploaded; continuing", targetPath))
state.Put(s.ResultKey, targetPath)
return multistep.ActionContinue
}
}
// ISO is not present on datastore, so we need to download, then upload it.
// Pass through to the common download step.
return s.DownloadStep.Run(ctx, state)
}
func (s *StepDownload) Cleanup(state multistep.StateBag) {
}

View File

@ -1,85 +0,0 @@
package common
import (
"context"
"net/url"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
/// create mock step
type MockDownloadStep struct {
RunCalled bool
}
func (s *MockDownloadStep) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.RunCalled = true
return multistep.ActionContinue
}
func (s *MockDownloadStep) Cleanup(state multistep.StateBag) {}
func (s *MockDownloadStep) UseSourceToFindCacheTarget(source string) (*url.URL, string, error) {
return nil, "sometarget", nil
}
/// start tests
func downloadStepState(exists bool) *multistep.BasicStateBag {
state := basicStateBag(nil)
dsMock := &driver.DatastoreMock{
FileExistsReturn: exists,
}
driverMock := &driver.DriverMock{
DatastoreMock: dsMock,
}
state.Put("driver", driverMock)
return state
}
func TestStepDownload_Run(t *testing.T) {
testcases := []struct {
name string
filePresent bool
expectedAction multistep.StepAction
expectInternalStepCalled bool
errMessage string
}{
{
name: "Remote iso present; download shouldn't be called",
filePresent: true,
expectedAction: multistep.ActionContinue,
expectInternalStepCalled: false,
errMessage: "",
},
{
name: "Remote iso not present; download should be called",
filePresent: false,
expectedAction: multistep.ActionContinue,
expectInternalStepCalled: true,
errMessage: "",
},
}
for _, tc := range testcases {
internalStep := &MockDownloadStep{}
state := downloadStepState(tc.filePresent)
step := &StepDownload{
DownloadStep: internalStep,
Url: []string{"https://path/to/fake-url.iso"},
Datastore: "datastore-mock",
Host: "fake-host",
}
stepAction := step.Run(context.TODO(), state)
if stepAction != tc.expectedAction {
t.Fatalf("%s: Recieved wrong step action; step exists, should return early.", tc.name)
}
if tc.expectInternalStepCalled != internalStep.RunCalled {
if tc.expectInternalStepCalled {
t.Fatalf("%s: Expected internal download step to be called", tc.name)
} else {
t.Fatalf("%s: Expected internal download step not to be called", tc.name)
}
}
}
}

View File

@ -1,338 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ExportConfig
package common
import (
"bytes"
"context"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/pkg/errors"
"github.com/vmware/govmomi/nfc"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
// You may optionally export an ovf from VSphere to the instance running Packer.
//
// Example usage:
//
// In JSON:
// ```json
// ...
// "vm_name": "example-ubuntu",
// ...
// "export": {
// "force": true,
// "output_directory": "./output_vsphere"
// },
// ```
// In HCL2:
// ```hcl
// # ...
// vm_name = "example-ubuntu"
// # ...
// export {
// force = true
// output_directory = "./output_vsphere"
// }
// ```
// The above configuration would create the following files:
//
// ```text
// ./output_vsphere/example-ubuntu-disk-0.vmdk
// ./output_vsphere/example-ubuntu.mf
// ./output_vsphere/example-ubuntu.ovf
// ```
type ExportConfig struct {
// name of the ovf. defaults to the name of the VM
Name string `mapstructure:"name"`
// overwrite ovf if it exists
Force bool `mapstructure:"force"`
// include iso and img image files that are attached to the VM
Images bool `mapstructure:"images"`
// generate manifest using sha1, sha256, sha512. Defaults to 'sha256'. Use 'none' for no manifest.
Manifest string `mapstructure:"manifest"`
// Directory on the computer running Packer to export files to
OutputDir OutputConfig `mapstructure:",squash"`
// Advanced ovf export options. Options can include:
// * mac - MAC address is exported for all ethernet devices
// * uuid - UUID is exported for all virtual machines
// * extraconfig - all extra configuration options are exported for a virtual machine
// * nodevicesubtypes - resource subtypes for CD/DVD drives, floppy drives, and serial and parallel ports are not exported
//
// For example, adding the following export config option would output the mac addresses for all Ethernet devices in the ovf file:
//
// In JSON:
// ```json
// ...
// "export": {
// "options": ["mac"]
// },
// ```
// In HCL2:
// ```hcl
// ...
// export {
// options = ["mac"]
// }
// ```
Options []string `mapstructure:"options"`
}
var sha = map[string]func() hash.Hash{
"none": nil,
"sha1": sha1.New,
"sha256": sha256.New,
"sha512": sha512.New,
}
func (c *ExportConfig) Prepare(ctx *interpolate.Context, lc *LocationConfig, pc *common.PackerConfig) []error {
var errs *packersdk.MultiError
errs = packersdk.MultiErrorAppend(errs, c.OutputDir.Prepare(ctx, pc)...)
// manifest should default to sha256
if c.Manifest == "" {
c.Manifest = "sha256"
}
if _, ok := sha[c.Manifest]; !ok {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("unknown hash: %s. available options include available options being 'none', 'sha1', 'sha256', 'sha512'", c.Manifest))
}
if c.Name == "" {
c.Name = lc.VMName
}
target := getTarget(c.OutputDir.OutputDir, c.Name)
if !c.Force {
if _, err := os.Stat(target); err == nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("file already exists: %s", target))
}
}
if err := os.MkdirAll(c.OutputDir.OutputDir, c.OutputDir.DirPerm); err != nil {
errs = packersdk.MultiErrorAppend(errs, errors.Wrap(err, "unable to make directory for export"))
}
if errs != nil && len(errs.Errors) > 0 {
return errs.Errors
}
return nil
}
func getTarget(dir string, name string) string {
return filepath.Join(dir, name+".ovf")
}
type StepExport struct {
Name string
Force bool
Images bool
Manifest string
OutputDir string
Options []string
mf bytes.Buffer
}
func (s *StepExport) Cleanup(multistep.StateBag) {
}
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
ui.Message("Starting export...")
lease, err := vm.Export()
if err != nil {
state.Put("error", errors.Wrap(err, "error exporting vm"))
return multistep.ActionHalt
}
info, err := lease.Wait(ctx, nil)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
u := lease.StartUpdater(ctx, info)
defer u.Done()
cdp := types.OvfCreateDescriptorParams{
Name: s.Name,
}
m := vm.NewOvfManager()
if len(s.Options) > 0 {
exportOptions, err := vm.GetOvfExportOptions(m)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
var unknown []string
for _, option := range s.Options {
found := false
for _, exportOpt := range exportOptions {
if exportOpt.Option == option {
found = true
break
}
}
if !found {
unknown = append(unknown, option)
}
cdp.ExportOption = append(cdp.ExportOption, option)
}
// only printing error message because the unknown options are just ignored by vcenter
if len(unknown) > 0 {
ui.Error(fmt.Sprintf("unknown export options %s", strings.Join(unknown, ",")))
}
}
for _, i := range info.Items {
if !s.include(&i) {
continue
}
if !strings.HasPrefix(i.Path, s.Name) {
i.Path = s.Name + "-" + i.Path
}
file := i.File()
ui.Message("Downloading: " + file.Path)
size, err := s.Download(ctx, lease, i)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
// Fix file size descriptor
file.Size = size
ui.Message("Exporting file: " + file.Path)
cdp.OvfFiles = append(cdp.OvfFiles, file)
}
if err = lease.Complete(ctx); err != nil {
state.Put("error", errors.Wrap(err, "unable to complete lease"))
return multistep.ActionHalt
}
desc, err := vm.CreateDescriptor(m, cdp)
if err != nil {
state.Put("error", errors.Wrap(err, "unable to create descriptor"))
return multistep.ActionHalt
}
target := getTarget(s.OutputDir, s.Name)
file, err := os.Create(target)
if err != nil {
state.Put("error", errors.Wrap(err, "unable to create file: "+target))
return multistep.ActionHalt
}
var w io.Writer = file
h, ok := s.newHash()
if ok {
w = io.MultiWriter(file, h)
}
ui.Message("Writing ovf...")
_, err = io.WriteString(w, desc.OvfDescriptor)
if err != nil {
state.Put("error", errors.Wrap(err, "unable to write descriptor"))
return multistep.ActionHalt
}
if err = file.Close(); err != nil {
state.Put("error", errors.Wrap(err, "unable to close descriptor"))
return multistep.ActionHalt
}
if s.Manifest == "none" {
// manifest does not need to be created, return
return multistep.ActionContinue
}
ui.Message("Creating manifest...")
s.addHash(filepath.Base(target), h)
file, err = os.Create(filepath.Join(s.OutputDir, s.Name+".mf"))
if err != nil {
state.Put("error", errors.Wrap(err, "unable to create manifest"))
return multistep.ActionHalt
}
_, err = io.Copy(file, &s.mf)
if err != nil {
state.Put("error", errors.Wrap(err, "unable to write manifest"))
return multistep.ActionHalt
}
err = file.Close()
if err != nil {
state.Put("error", errors.Wrap(err, "unable to close file"))
return multistep.ActionHalt
}
ui.Message("Finished exporting...")
return multistep.ActionContinue
}
func (s *StepExport) include(item *nfc.FileItem) bool {
if s.Images {
return true
}
return filepath.Ext(item.Path) == ".vmdk"
}
func (s *StepExport) newHash() (hash.Hash, bool) {
// check if function is nil to handle the 'none' case
if h, ok := sha[s.Manifest]; ok && h != nil {
return h(), true
}
return nil, false
}
func (s *StepExport) addHash(p string, h hash.Hash) {
_, _ = fmt.Fprintf(&s.mf, "%s(%s)= %x\n", strings.ToUpper(s.Manifest), p, h.Sum(nil))
}
func (s *StepExport) Download(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) (int64, error) {
path := filepath.Join(s.OutputDir, item.Path)
opts := soap.Download{}
if h, ok := s.newHash(); ok {
opts.Writer = h
defer s.addHash(item.Path, h)
}
err := lease.DownloadFile(ctx, path, item, opts)
if err != nil {
return 0, err
}
f, err := os.Stat(path)
if err != nil {
return 0, err
}
return f.Size(), err
}

View File

@ -1,45 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type ExportConfig"; DO NOT EDIT.
package common
import (
"io/fs"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatExportConfig is an auto-generated flat version of ExportConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatExportConfig struct {
Name *string `mapstructure:"name" cty:"name" hcl:"name"`
Force *bool `mapstructure:"force" cty:"force" hcl:"force"`
Images *bool `mapstructure:"images" cty:"images" hcl:"images"`
Manifest *string `mapstructure:"manifest" cty:"manifest" hcl:"manifest"`
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
DirPerm *fs.FileMode `mapstructure:"directory_permission" required:"false" cty:"directory_permission" hcl:"directory_permission"`
Options []string `mapstructure:"options" cty:"options" hcl:"options"`
}
// FlatMapstructure returns a new FlatExportConfig.
// FlatExportConfig is an auto-generated flat version of ExportConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ExportConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatExportConfig)
}
// HCL2Spec returns the hcl spec of a ExportConfig.
// This spec is used by HCL to read the fields of ExportConfig.
// The decoded values from this spec will then be applied to a FlatExportConfig.
func (*FlatExportConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
"force": &hcldec.AttrSpec{Name: "force", Type: cty.Bool, Required: false},
"images": &hcldec.AttrSpec{Name: "images", Type: cty.Bool, Required: false},
"manifest": &hcldec.AttrSpec{Name: "manifest", Type: cty.String, Required: false},
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
"directory_permission": &hcldec.AttrSpec{Name: "directory_permission", Type: cty.Number, Required: false},
"options": &hcldec.AttrSpec{Name: "options", Type: cty.List(cty.String), Required: false},
}
return s
}

View File

@ -1,98 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type HardwareConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type HardwareConfig struct {
// Number of CPU cores.
CPUs int32 `mapstructure:"CPUs"`
// Number of CPU cores per socket.
CpuCores int32 `mapstructure:"cpu_cores"`
// Amount of reserved CPU resources in MHz.
CPUReservation int64 `mapstructure:"CPU_reservation"`
// Upper limit of available CPU resources in MHz.
CPULimit int64 `mapstructure:"CPU_limit"`
// Enable CPU hot plug setting for virtual machine. Defaults to `false`.
CpuHotAddEnabled bool `mapstructure:"CPU_hot_plug"`
// Amount of RAM in MB.
RAM int64 `mapstructure:"RAM"`
// Amount of reserved RAM in MB.
RAMReservation int64 `mapstructure:"RAM_reservation"`
// Reserve all available RAM. Defaults to `false`. Cannot be used together
// with `RAM_reservation`.
RAMReserveAll bool `mapstructure:"RAM_reserve_all"`
// Enable RAM hot plug setting for virtual machine. Defaults to `false`.
MemoryHotAddEnabled bool `mapstructure:"RAM_hot_plug"`
// Amount of video memory in KB.
VideoRAM int64 `mapstructure:"video_ram"`
// vGPU profile for accelerated graphics. See [NVIDIA GRID vGPU documentation](https://docs.nvidia.com/grid/latest/grid-vgpu-user-guide/index.html#configure-vmware-vsphere-vm-with-vgpu)
// for examples of profile names. Defaults to none.
VGPUProfile string `mapstructure:"vgpu_profile"`
// Enable nested hardware virtualization for VM. Defaults to `false`.
NestedHV bool `mapstructure:"NestedHV"`
// Set the Firmware for virtual machine. Supported values: `bios`, `efi` or `efi-secure`. Defaults to `bios`.
Firmware string `mapstructure:"firmware"`
// During the boot, force entry into the BIOS setup screen. Defaults to `false`.
ForceBIOSSetup bool `mapstructure:"force_bios_setup"`
}
func (c *HardwareConfig) Prepare() []error {
var errs []error
if c.RAMReservation > 0 && c.RAMReserveAll != false {
errs = append(errs, fmt.Errorf("'RAM_reservation' and 'RAM_reserve_all' cannot be used together"))
}
if c.Firmware != "" && c.Firmware != "bios" && c.Firmware != "efi" && c.Firmware != "efi-secure" {
errs = append(errs, fmt.Errorf("'firmware' must be '', 'bios', 'efi' or 'efi-secure'"))
}
return errs
}
type StepConfigureHardware struct {
Config *HardwareConfig
}
func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(driver.VirtualMachine)
if *s.Config != (HardwareConfig{}) {
ui.Say("Customizing hardware...")
err := vm.Configure(&driver.HardwareConfig{
CPUs: s.Config.CPUs,
CpuCores: s.Config.CpuCores,
CPUReservation: s.Config.CPUReservation,
CPULimit: s.Config.CPULimit,
RAM: s.Config.RAM,
RAMReservation: s.Config.RAMReservation,
RAMReserveAll: s.Config.RAMReserveAll,
NestedHV: s.Config.NestedHV,
CpuHotAddEnabled: s.Config.CpuHotAddEnabled,
MemoryHotAddEnabled: s.Config.MemoryHotAddEnabled,
VideoRAM: s.Config.VideoRAM,
VGPUProfile: s.Config.VGPUProfile,
Firmware: s.Config.Firmware,
ForceBIOSSetup: s.Config.ForceBIOSSetup,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConfigureHardware) Cleanup(multistep.StateBag) {}

View File

@ -1,57 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type HardwareConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatHardwareConfig is an auto-generated flat version of HardwareConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatHardwareConfig struct {
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs" hcl:"CPUs"`
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores" hcl:"cpu_cores"`
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation" hcl:"CPU_reservation"`
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit" hcl:"CPU_limit"`
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug" hcl:"CPU_hot_plug"`
RAM *int64 `mapstructure:"RAM" cty:"RAM" hcl:"RAM"`
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation" hcl:"RAM_reservation"`
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all" hcl:"RAM_reserve_all"`
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug" hcl:"RAM_hot_plug"`
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram" hcl:"video_ram"`
VGPUProfile *string `mapstructure:"vgpu_profile" cty:"vgpu_profile" hcl:"vgpu_profile"`
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV" hcl:"NestedHV"`
Firmware *string `mapstructure:"firmware" cty:"firmware" hcl:"firmware"`
ForceBIOSSetup *bool `mapstructure:"force_bios_setup" cty:"force_bios_setup" hcl:"force_bios_setup"`
}
// FlatMapstructure returns a new FlatHardwareConfig.
// FlatHardwareConfig is an auto-generated flat version of HardwareConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*HardwareConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatHardwareConfig)
}
// HCL2Spec returns the hcl spec of a HardwareConfig.
// This spec is used by HCL to read the fields of HardwareConfig.
// The decoded values from this spec will then be applied to a FlatHardwareConfig.
func (*FlatHardwareConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"CPUs": &hcldec.AttrSpec{Name: "CPUs", Type: cty.Number, Required: false},
"cpu_cores": &hcldec.AttrSpec{Name: "cpu_cores", Type: cty.Number, Required: false},
"CPU_reservation": &hcldec.AttrSpec{Name: "CPU_reservation", Type: cty.Number, Required: false},
"CPU_limit": &hcldec.AttrSpec{Name: "CPU_limit", Type: cty.Number, Required: false},
"CPU_hot_plug": &hcldec.AttrSpec{Name: "CPU_hot_plug", Type: cty.Bool, Required: false},
"RAM": &hcldec.AttrSpec{Name: "RAM", Type: cty.Number, Required: false},
"RAM_reservation": &hcldec.AttrSpec{Name: "RAM_reservation", Type: cty.Number, Required: false},
"RAM_reserve_all": &hcldec.AttrSpec{Name: "RAM_reserve_all", Type: cty.Bool, Required: false},
"RAM_hot_plug": &hcldec.AttrSpec{Name: "RAM_hot_plug", Type: cty.Bool, Required: false},
"video_ram": &hcldec.AttrSpec{Name: "video_ram", Type: cty.Number, Required: false},
"vgpu_profile": &hcldec.AttrSpec{Name: "vgpu_profile", Type: cty.String, Required: false},
"NestedHV": &hcldec.AttrSpec{Name: "NestedHV", Type: cty.Bool, Required: false},
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
"force_bios_setup": &hcldec.AttrSpec{Name: "force_bios_setup", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,181 +0,0 @@
package common
import (
"context"
"errors"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestHardwareConfig_Prepare(t *testing.T) {
tc := []struct {
name string
config *HardwareConfig
fail bool
expectedErrMsg string
}{
{
name: "Validate empty config",
config: &HardwareConfig{},
fail: false,
},
{
name: "Validate RAMReservation RAMReserveAll cannot be used together",
config: &HardwareConfig{
RAMReservation: 2,
RAMReserveAll: true,
},
fail: true,
expectedErrMsg: "'RAM_reservation' and 'RAM_reserve_all' cannot be used together",
},
{
name: "Invalid firmware",
config: &HardwareConfig{
Firmware: "invalid",
},
fail: true,
expectedErrMsg: "'firmware' must be '', 'bios', 'efi' or 'efi-secure'",
},
{
name: "Validate 'bios' firmware",
config: &HardwareConfig{
Firmware: "bios",
},
fail: false,
},
{
name: "Validate 'efi' firmware",
config: &HardwareConfig{
Firmware: "efi",
},
fail: false,
},
{
name: "Validate 'efi-secure' firmware",
config: &HardwareConfig{
Firmware: "efi-secure",
},
fail: false,
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
errs := c.config.Prepare()
if c.fail {
if len(errs) == 0 {
t.Fatalf("Config preprare should fail")
}
if errs[0].Error() != c.expectedErrMsg {
t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
}
} else {
if len(errs) != 0 {
t.Fatalf("Config preprare should not fail")
}
}
})
}
}
func TestStepConfigureHardware_Run(t *testing.T) {
tc := []struct {
name string
step *StepConfigureHardware
action multistep.StepAction
configureError error
configureCalled bool
hardwareConfig *driver.HardwareConfig
}{
{
name: "Configure hardware",
step: basicStepConfigureHardware(),
action: multistep.ActionContinue,
configureError: nil,
configureCalled: true,
hardwareConfig: driverHardwareConfigFromConfig(basicStepConfigureHardware().Config),
},
{
name: "Don't configure hardware when config is empty",
step: &StepConfigureHardware{Config: &HardwareConfig{}},
action: multistep.ActionContinue,
configureError: nil,
configureCalled: false,
},
{
name: "Halt when configure return error",
step: basicStepConfigureHardware(),
action: multistep.ActionHalt,
configureError: errors.New("failed to configure"),
configureCalled: true,
hardwareConfig: driverHardwareConfigFromConfig(basicStepConfigureHardware().Config),
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
vmMock := new(driver.VirtualMachineMock)
vmMock.ConfigureError = c.configureError
state.Put("vm", vmMock)
action := c.step.Run(context.TODO(), state)
if action != c.action {
t.Fatalf("expected action '%v' but actual action was '%v'", c.action, action)
}
if vmMock.ConfigureCalled != c.configureCalled {
t.Fatalf("expecting vm.Configure called to %t but was %t", c.configureCalled, vmMock.ConfigureCalled)
}
if diff := cmp.Diff(vmMock.ConfigureHardwareConfig, c.hardwareConfig); diff != "" {
t.Fatalf("wrong driver.HardwareConfig: %s", diff)
}
err, ok := state.GetOk("error")
containsError := c.configureError != nil
if containsError != ok {
t.Fatalf("Contain error - expecting %t but was %t", containsError, ok)
}
if containsError {
if !strings.Contains(err.(error).Error(), c.configureError.Error()) {
t.Fatalf("Destroy should fail with error message '%s' but failed with '%s'", c.configureError.Error(), err.(error).Error())
}
}
})
}
}
func basicStepConfigureHardware() *StepConfigureHardware {
return &StepConfigureHardware{
Config: &HardwareConfig{
CPUs: 1,
CpuCores: 1,
CPUReservation: 1,
CPULimit: 4000,
RAM: 1024,
RAMReserveAll: true,
Firmware: "efi-secure",
ForceBIOSSetup: true,
},
}
}
func driverHardwareConfigFromConfig(config *HardwareConfig) *driver.HardwareConfig {
return &driver.HardwareConfig{
CPUs: config.CPUs,
CpuCores: config.CpuCores,
CPUReservation: config.CPUReservation,
CPULimit: config.CPULimit,
RAM: config.RAM,
RAMReservation: config.RAMReservation,
RAMReserveAll: config.RAMReserveAll,
NestedHV: config.NestedHV,
CpuHotAddEnabled: config.CpuHotAddEnabled,
MemoryHotAddEnabled: config.MemoryHotAddEnabled,
VideoRAM: config.VideoRAM,
VGPUProfile: config.VGPUProfile,
Firmware: config.Firmware,
ForceBIOSSetup: config.ForceBIOSSetup,
}
}

View File

@ -1,68 +0,0 @@
package common
import (
"context"
"fmt"
"net"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
// Step to discover the http ip
// which guests use to reach the vm host
// To make sure the IP is set before boot command and http server steps
type StepHTTPIPDiscover struct {
HTTPIP string
Network *net.IPNet
}
func (s *StepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ip, err := getHostIP(s.HTTPIP, s.Network)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("http_ip", ip)
return multistep.ActionContinue
}
func (s *StepHTTPIPDiscover) Cleanup(state multistep.StateBag) {}
func getHostIP(s string, network *net.IPNet) (string, error) {
if s != "" {
if net.ParseIP(s) != nil {
return s, nil
} else {
return "", fmt.Errorf("invalid IP address")
}
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
// look for an IP that is contained in the ip_wait_address range
if network != nil {
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if ok && !ipnet.IP.IsLoopback() {
if network.Contains(ipnet.IP) {
return ipnet.IP.String(), nil
}
}
}
}
// fallback to an ipv4 address if an IP is not found in the range
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", fmt.Errorf("IP not found")
}

View File

@ -1,83 +0,0 @@
package common
import (
"context"
"net"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepHTTPIPDiscover_Run(t *testing.T) {
state := new(multistep.BasicStateBag)
step := new(StepHTTPIPDiscover)
// without setting HTTPIP
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
_, ok := state.GetOk("http_ip")
if !ok {
t.Fatal("should have http_ip")
}
// setting HTTPIP
ip := "10.0.2.2"
step = &StepHTTPIPDiscover{
HTTPIP: ip,
}
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
httpIp, ok := state.GetOk("http_ip")
if !ok {
t.Fatal("should have http_ip")
}
if httpIp != ip {
t.Fatalf("bad: Http ip is %s but was supposed to be %s", httpIp, ip)
}
_, ipNet, err := net.ParseCIDR("0.0.0.0/0")
if err != nil {
t.Fatal("error getting ipNet", err)
}
step = new(StepHTTPIPDiscover)
step.Network = ipNet
// without setting HTTPIP with Network
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
_, ok = state.GetOk("http_ip")
if !ok {
t.Fatal("should have http_ip")
}
// setting HTTPIP with Network
step = &StepHTTPIPDiscover{
HTTPIP: ip,
Network: ipNet,
}
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
httpIp, ok = state.GetOk("http_ip")
if !ok {
t.Fatal("should have http_ip")
}
if httpIp != ip {
t.Fatalf("bad: Http ip is %s but was supposed to be %s", httpIp, ip)
}
}

View File

@ -1,180 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ContentLibraryDestinationConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/vmware/govmomi/vapi/vcenter"
)
// With this configuration Packer creates a library item in a content library whose content is a VM template
// or an OVF template created from the just built VM.
// The template is stored in a existing or newly created library item.
type ContentLibraryDestinationConfig struct {
// Name of the library in which the new library item containing the template should be created/updated.
// The Content Library should be of type Local to allow deploying virtual machines.
Library string `mapstructure:"library"`
// Name of the library item that will be created or updated.
// For VM templates, the name of the item should be different from [vm_name](#vm_name) and
// the default is [vm_name](#vm_name) + timestamp when not set. VM templates will be always imported to a new library item.
// For OVF templates, the name defaults to [vm_name](#vm_name) when not set, and if an item with the same name already
// exists it will be then updated with the new OVF template, otherwise a new item will be created.
//
// ~> **Note**: It's not possible to update existing library items with a new VM template. If updating an existing library
// item is necessary, use an OVF template instead by setting the [ovf](#ovf) option as `true`.
//
Name string `mapstructure:"name"`
// Description of the library item that will be created.
// This option is not used when importing OVF templates.
// Defaults to "Packer imported [vm_name](#vm_name) VM template".
Description string `mapstructure:"description"`
// Cluster onto which the virtual machine template should be placed.
// If cluster and resource_pool are both specified, resource_pool must belong to cluster.
// If cluster and host are both specified, host must be a member of cluster.
// This option is not used when importing OVF templates.
// Defaults to [cluster](#cluster).
Cluster string `mapstructure:"cluster"`
// Virtual machine folder into which the virtual machine template should be placed.
// This option is not used when importing OVF templates.
// Defaults to the same folder as the source virtual machine.
Folder string `mapstructure:"folder"`
// Host onto which the virtual machine template should be placed.
// If host and resource_pool are both specified, resource_pool must belong to host.
// If host and cluster are both specified, host must be a member of cluster.
// This option is not used when importing OVF templates.
// Defaults to [host](#host).
Host string `mapstructure:"host"`
// Resource pool into which the virtual machine template should be placed.
// Defaults to [resource_pool](#resource_pool). if [resource_pool](#resource_pool) is also unset,
// the system will attempt to choose a suitable resource pool for the virtual machine template.
ResourcePool string `mapstructure:"resource_pool"`
// The datastore for the virtual machine template's configuration and log files.
// This option is not used when importing OVF templates.
// Defaults to the storage backing associated with the library specified by library.
Datastore string `mapstructure:"datastore"`
// If set to true, the VM will be destroyed after deploying the template to the Content Library.
// Defaults to `false`.
Destroy bool `mapstructure:"destroy"`
// When set to true, Packer will import and OVF template to the content library item. Defaults to `false`.
Ovf bool `mapstructure:"ovf"`
}
func (c *ContentLibraryDestinationConfig) Prepare(lc *LocationConfig) []error {
var errs *packersdk.MultiError
if c.Library == "" {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("a library name must be provided"))
}
if c.Ovf {
if c.Name == "" {
c.Name = lc.VMName
}
} else {
if c.Name == lc.VMName {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("the content library destination name must be different from the VM name"))
}
if c.Name == "" {
// Add timestamp to the name to differentiate from the original VM
// otherwise vSphere won't be able to create the template which will be imported
name, err := interpolate.Render(lc.VMName+"{{timestamp}}", nil)
if err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("unable to parse content library VM template name: %s", err))
}
c.Name = name
}
if c.Cluster == "" {
c.Cluster = lc.Cluster
}
if c.Host == "" {
c.Host = lc.Host
}
if c.ResourcePool == "" {
c.ResourcePool = lc.ResourcePool
}
if c.Description == "" {
c.Description = fmt.Sprintf("Packer imported %s VM template", lc.VMName)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs.Errors
}
return nil
}
type StepImportToContentLibrary struct {
ContentLibConfig *ContentLibraryDestinationConfig
}
func (s *StepImportToContentLibrary) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
var err error
if s.ContentLibConfig.Ovf {
ui.Say(fmt.Sprintf("Importing VM OVF template %s to Content Library...", s.ContentLibConfig.Name))
err = s.importOvfTemplate(vm)
} else {
ui.Say(fmt.Sprintf("Importing VM template %s to Content Library...", s.ContentLibConfig.Name))
err = s.importVmTemplate(vm)
}
if err != nil {
ui.Error(fmt.Sprintf("Failed to import template %s: %s", s.ContentLibConfig.Name, err.Error()))
state.Put("error", err)
return multistep.ActionHalt
}
if s.ContentLibConfig.Destroy {
state.Put("destroy_vm", s.ContentLibConfig.Destroy)
}
return multistep.ActionContinue
}
func (s *StepImportToContentLibrary) importOvfTemplate(vm *driver.VirtualMachineDriver) error {
ovf := vcenter.OVF{
Spec: vcenter.CreateSpec{
Name: s.ContentLibConfig.Name,
},
Target: vcenter.LibraryTarget{
LibraryID: s.ContentLibConfig.Library,
},
}
return vm.ImportOvfToContentLibrary(ovf)
}
func (s *StepImportToContentLibrary) importVmTemplate(vm *driver.VirtualMachineDriver) error {
template := vcenter.Template{
Name: s.ContentLibConfig.Name,
Description: s.ContentLibConfig.Description,
Library: s.ContentLibConfig.Library,
Placement: &vcenter.Placement{
Cluster: s.ContentLibConfig.Cluster,
Folder: s.ContentLibConfig.Folder,
Host: s.ContentLibConfig.Host,
ResourcePool: s.ContentLibConfig.ResourcePool,
},
}
if s.ContentLibConfig.Datastore != "" {
template.VMHomeStorage = &vcenter.DiskStorage{
Datastore: s.ContentLibConfig.Datastore,
}
}
return vm.ImportToContentLibrary(template)
}
func (s *StepImportToContentLibrary) Cleanup(multistep.StateBag) {
}

View File

@ -1,49 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type ContentLibraryDestinationConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatContentLibraryDestinationConfig is an auto-generated flat version of ContentLibraryDestinationConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatContentLibraryDestinationConfig struct {
Library *string `mapstructure:"library" cty:"library" hcl:"library"`
Name *string `mapstructure:"name" cty:"name" hcl:"name"`
Description *string `mapstructure:"description" cty:"description" hcl:"description"`
Cluster *string `mapstructure:"cluster" cty:"cluster" hcl:"cluster"`
Folder *string `mapstructure:"folder" cty:"folder" hcl:"folder"`
Host *string `mapstructure:"host" cty:"host" hcl:"host"`
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool" hcl:"resource_pool"`
Datastore *string `mapstructure:"datastore" cty:"datastore" hcl:"datastore"`
Destroy *bool `mapstructure:"destroy" cty:"destroy" hcl:"destroy"`
Ovf *bool `mapstructure:"ovf" cty:"ovf" hcl:"ovf"`
}
// FlatMapstructure returns a new FlatContentLibraryDestinationConfig.
// FlatContentLibraryDestinationConfig is an auto-generated flat version of ContentLibraryDestinationConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ContentLibraryDestinationConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatContentLibraryDestinationConfig)
}
// HCL2Spec returns the hcl spec of a ContentLibraryDestinationConfig.
// This spec is used by HCL to read the fields of ContentLibraryDestinationConfig.
// The decoded values from this spec will then be applied to a FlatContentLibraryDestinationConfig.
func (*FlatContentLibraryDestinationConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"library": &hcldec.AttrSpec{Name: "library", Type: cty.String, Required: false},
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
"description": &hcldec.AttrSpec{Name: "description", Type: cty.String, Required: false},
"cluster": &hcldec.AttrSpec{Name: "cluster", Type: cty.String, Required: false},
"folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false},
"host": &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false},
"resource_pool": &hcldec.AttrSpec{Name: "resource_pool", Type: cty.String, Required: false},
"datastore": &hcldec.AttrSpec{Name: "datastore", Type: cty.String, Required: false},
"destroy": &hcldec.AttrSpec{Name: "destroy", Type: cty.Bool, Required: false},
"ovf": &hcldec.AttrSpec{Name: "ovf", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,117 +0,0 @@
package common
import (
"context"
"fmt"
"log"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type StepRemoteUpload struct {
Datastore string
Host string
SetHostForDatastoreUploads bool
UploadedCustomCD bool
}
func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
d := state.Get("driver").(driver.Driver)
if path, ok := state.GetOk("iso_path"); ok {
// user-supplied boot iso
fullRemotePath, err := s.uploadFile(path.(string), d, ui)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("iso_remote_path", fullRemotePath)
}
if cdPath, ok := state.GetOk("cd_path"); ok {
// Packer-created cd_files disk
fullRemotePath, err := s.uploadFile(cdPath.(string), d, ui)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.UploadedCustomCD = true
state.Put("cd_path", fullRemotePath)
}
return multistep.ActionContinue
}
func GetRemoteDirectoryAndPath(path string, ds driver.Datastore) (string, string, string, string) {
filename := filepath.Base(path)
remotePath := fmt.Sprintf("packer_cache/%s", filename)
remoteDirectory := fmt.Sprintf("[%s] packer_cache/", ds.Name())
fullRemotePath := fmt.Sprintf("%s/%s", remoteDirectory, filename)
return filename, remotePath, remoteDirectory, fullRemotePath
}
func (s *StepRemoteUpload) uploadFile(path string, d driver.Driver, ui packersdk.Ui) (string, error) {
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
return "", fmt.Errorf("datastore doesn't exist: %v", err)
}
filename, remotePath, remoteDirectory, fullRemotePath := GetRemoteDirectoryAndPath(path, ds)
if exists := ds.FileExists(remotePath); exists == true {
ui.Say(fmt.Sprintf("File %s already exists; skipping upload.", fullRemotePath))
return fullRemotePath, nil
}
ui.Say(fmt.Sprintf("Uploading %s to %s", filename, remotePath))
if exists := ds.DirExists(remotePath); exists == false {
log.Printf("Remote directory doesn't exist; creating...")
if err := ds.MakeDirectory(remoteDirectory); err != nil {
return "", err
}
}
if err := ds.UploadFile(path, remotePath, s.Host, s.SetHostForDatastoreUploads); err != nil {
return "", err
}
return fullRemotePath, nil
}
func (s *StepRemoteUpload) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
if !s.UploadedCustomCD {
return
}
UploadedCDPath, ok := state.GetOk("cd_path")
if !ok {
return
}
ui := state.Get("ui").(packersdk.Ui)
d := state.Get("driver").(*driver.VCenterDriver)
ui.Say("Deleting cd_files image from remote datastore ...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
log.Printf("Error finding datastore to delete custom CD; please delete manually: %s", err)
return
}
err = ds.Delete(UploadedCDPath.(string))
if err != nil {
log.Printf("Error deleting custom CD from remote datastore; please delete manually: %s", err)
return
}
}

View File

@ -1,71 +0,0 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestStepRemoteUpload_Run(t *testing.T) {
state := basicStateBag(nil)
dsMock := driver.DatastoreMock{
DirExistsReturn: false,
}
driverMock := driver.NewDriverMock()
driverMock.DatastoreMock = &dsMock
state.Put("driver", driverMock)
state.Put("iso_path", "[datastore] iso/path")
step := &StepRemoteUpload{
Datastore: "datastore",
Host: "host",
SetHostForDatastoreUploads: false,
}
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
t.Fatalf("Should not halt.")
}
if !driverMock.FindDatastoreCalled {
t.Fatalf("driver.FindDatastore should be called.")
}
if !driverMock.DatastoreMock.FileExistsCalled {
t.Fatalf("datastore.FindDatastore should be called.")
}
if !driverMock.DatastoreMock.MakeDirectoryCalled {
t.Fatalf("datastore.MakeDirectory should be called.")
}
if !driverMock.DatastoreMock.UploadFileCalled {
t.Fatalf("datastore.UploadFile should be called.")
}
remotePath, ok := state.GetOk("iso_remote_path")
if !ok {
t.Fatalf("state should contain iso_remote_path")
}
expectedRemovePath := fmt.Sprintf("[%s] packer_cache//path", driverMock.DatastoreMock.Name())
if remotePath != expectedRemovePath {
t.Fatalf("iso_remote_path expected to be %s but was %s", expectedRemovePath, remotePath)
}
}
func TestStepRemoteUpload_SkipRun(t *testing.T) {
state := basicStateBag(nil)
driverMock := driver.NewDriverMock()
state.Put("driver", driverMock)
step := &StepRemoteUpload{}
if action := step.Run(context.TODO(), state); action == multistep.ActionHalt {
t.Fatalf("Should not halt.")
}
if driverMock.FindDatastoreCalled {
t.Fatalf("driver.FindDatastore should not be called.")
}
if _, ok := state.GetOk("iso_remote_path"); ok {
t.Fatalf("state should not contain iso_remote_path")
}
}

View File

@ -1,46 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type RemoveCDRomConfig
package common
import (
"context"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type RemoveCDRomConfig struct {
// Remove CD-ROM devices from template. Defaults to `false`.
RemoveCdrom bool `mapstructure:"remove_cdrom"`
}
type StepRemoveCDRom struct {
Config *RemoveCDRomConfig
}
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(driver.VirtualMachine)
ui.Say("Eject CD-ROM drives...")
err := vm.EjectCdroms()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if s.Config.RemoveCdrom == true {
ui.Say("Deleting CD-ROM drives...")
err := vm.RemoveCdroms()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepRemoveCDRom) Cleanup(state multistep.StateBag) {}

View File

@ -1,31 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type RemoveCDRomConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatRemoveCDRomConfig is an auto-generated flat version of RemoveCDRomConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatRemoveCDRomConfig struct {
RemoveCdrom *bool `mapstructure:"remove_cdrom" cty:"remove_cdrom" hcl:"remove_cdrom"`
}
// FlatMapstructure returns a new FlatRemoveCDRomConfig.
// FlatRemoveCDRomConfig is an auto-generated flat version of RemoveCDRomConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*RemoveCDRomConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatRemoveCDRomConfig)
}
// HCL2Spec returns the hcl spec of a RemoveCDRomConfig.
// This spec is used by HCL to read the fields of RemoveCDRomConfig.
// The decoded values from this spec will then be applied to a FlatRemoveCDRomConfig.
func (*FlatRemoveCDRomConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"remove_cdrom": &hcldec.AttrSpec{Name: "remove_cdrom", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,111 +0,0 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestStepRemoveCDRom_Run(t *testing.T) {
tc := []struct {
name string
step *StepRemoveCDRom
expectedAction multistep.StepAction
vmMock *driver.VirtualMachineMock
expectedVmMock *driver.VirtualMachineMock
fail bool
errMessage string
}{
{
name: "Eject CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{},
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
},
fail: false,
},
{
name: "Failed to eject CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{},
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
EjectCdromsErr: fmt.Errorf("failed to eject cd-rom drives"),
},
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
},
fail: true,
errMessage: "failed to eject cd-rom drives",
},
{
name: "Eject and delete CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{
RemoveCdrom: true,
},
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
RemoveCdromsCalled: true,
},
fail: false,
},
{
name: "Fail to delete CD-ROM drives",
step: &StepRemoveCDRom{
Config: &RemoveCDRomConfig{
RemoveCdrom: true,
},
},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
RemoveCdromsErr: fmt.Errorf("failed to delete cd-rom devices"),
},
expectedVmMock: &driver.VirtualMachineMock{
EjectCdromsCalled: true,
RemoveCdromsCalled: true,
},
fail: true,
errMessage: "failed to delete cd-rom devices",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
state.Put("vm", c.vmMock)
if action := c.step.Run(context.TODO(), state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
})
}
}

View File

@ -1,49 +0,0 @@
package common
import (
"context"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type StepRemoveFloppy struct {
Datastore string
Host string
}
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(driver.VirtualMachine)
d := state.Get("driver").(driver.Driver)
ui.Say("Deleting Floppy drives...")
floppies, err := vm.FloppyDevices()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if err = vm.RemoveDevice(true, floppies...); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if err := ds.Delete(UploadedFloppyPath.(string)); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Remove("uploaded_floppy_path")
}
return multistep.ActionContinue
}
func (s *StepRemoveFloppy) Cleanup(state multistep.StateBag) {}

View File

@ -1,213 +0,0 @@
package common
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func TestStepRemoveFloppy_Run(t *testing.T) {
tc := []struct {
name string
uploadedPath string
step *StepRemoveFloppy
expectedAction multistep.StepAction
vmMock *driver.VirtualMachineMock
expectedVmMock *driver.VirtualMachineMock
driverMock *driver.DriverMock
expectedDriverMock *driver.DriverMock
dsMock *driver.DatastoreMock
expectedDsMock *driver.DatastoreMock
fail bool
errMessage string
}{
{
name: "Remove floppy drives and images",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepRemoveFloppy{
Datastore: "datastore",
Host: "host",
},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "vm/dir/packer-tmp-created-floppy.flp",
},
fail: false,
},
{
name: "No floppy image to remove",
step: &StepRemoveFloppy{},
expectedAction: multistep.ActionContinue,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: false,
},
{
name: "Fail to find floppy devices",
step: &StepRemoveFloppy{},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
FloppyDevicesErr: fmt.Errorf("failed to find floppy devices"),
},
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "failed to find floppy devices",
},
{
name: "Fail to remove floppy devices",
step: &StepRemoveFloppy{},
expectedAction: multistep.ActionHalt,
vmMock: &driver.VirtualMachineMock{
RemoveDeviceErr: fmt.Errorf("failed to remove device"),
},
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: new(driver.DriverMock),
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "failed to remove device",
},
{
name: "Fail to find datastore",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepRemoveFloppy{
Datastore: "datastore",
Host: "host",
},
expectedAction: multistep.ActionHalt,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: &driver.DriverMock{
FindDatastoreErr: fmt.Errorf("failed to find datastore"),
},
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: new(driver.DatastoreMock),
expectedDsMock: new(driver.DatastoreMock),
fail: true,
errMessage: "failed to find datastore",
},
{
name: "Fail to delete floppy image",
uploadedPath: "vm/dir/packer-tmp-created-floppy.flp",
step: &StepRemoveFloppy{
Datastore: "datastore",
Host: "host",
},
expectedAction: multistep.ActionHalt,
vmMock: new(driver.VirtualMachineMock),
expectedVmMock: &driver.VirtualMachineMock{
FloppyDevicesCalled: true,
RemoveDeviceCalled: true,
RemoveDeviceKeepFiles: true,
},
driverMock: new(driver.DriverMock),
expectedDriverMock: &driver.DriverMock{
FindDatastoreCalled: true,
FindDatastoreName: "datastore",
FindDatastoreHost: "host",
},
dsMock: &driver.DatastoreMock{
DeleteErr: fmt.Errorf("failed to delete floppy"),
},
expectedDsMock: &driver.DatastoreMock{
DeleteCalled: true,
DeletePath: "vm/dir/packer-tmp-created-floppy.flp",
},
fail: true,
errMessage: "failed to delete floppy",
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
state := basicStateBag(nil)
state.Put("vm", c.vmMock)
c.driverMock.DatastoreMock = c.dsMock
state.Put("driver", c.driverMock)
if c.uploadedPath != "" {
state.Put("uploaded_floppy_path", c.uploadedPath)
}
if action := c.step.Run(context.TODO(), state); action != c.expectedAction {
t.Fatalf("unexpected action %v", action)
}
err, ok := state.Get("error").(error)
if ok {
if err.Error() != c.errMessage {
t.Fatalf("unexpected error %s", err.Error())
}
} else {
if c.fail {
t.Fatalf("expected to fail but it didn't")
}
}
if !c.fail {
if _, ok := state.GetOk("uploaded_floppy_path"); ok {
t.Fatalf("uploaded_floppy_path should not be in state")
}
}
if diff := cmp.Diff(c.vmMock, c.expectedVmMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected VirtualMachine calls: %s", diff)
}
c.expectedDriverMock.DatastoreMock = c.expectedDsMock
if diff := cmp.Diff(c.driverMock, c.expectedDriverMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Driver calls: %s", diff)
}
if diff := cmp.Diff(c.dsMock, c.expectedDsMock,
cmpopts.IgnoreInterfaces(struct{ error }{})); diff != "" {
t.Fatalf("unexpected Datastore calls: %s", diff)
}
})
}
}

View File

@ -1,80 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type RunConfig
package common
import (
"context"
"strings"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type RunConfig struct {
// Priority of boot devices. Defaults to `disk,cdrom`
BootOrder string `mapstructure:"boot_order"` // example: "floppy,cdrom,ethernet,disk"
}
type StepRun struct {
Config *RunConfig
SetOrder bool
}
func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.BootOrder != "" {
ui.Say("Set boot order...")
order := strings.Split(s.Config.BootOrder, ",")
if err := vm.SetBootOrder(order); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
} else {
if s.SetOrder {
ui.Say("Set boot order temporary...")
if err := vm.SetBootOrder([]string{"disk", "cdrom"}); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
}
ui.Say("Power on VM...")
err := vm.PowerOn()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepRun) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.Config.BootOrder == "" && s.SetOrder {
ui.Say("Clear boot order...")
if err := vm.SetBootOrder([]string{"-"}); err != nil {
state.Put("error", err)
return
}
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui.Say("Power off VM...")
err := vm.PowerOff()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -1,31 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type RunConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatRunConfig is an auto-generated flat version of RunConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatRunConfig struct {
BootOrder *string `mapstructure:"boot_order" cty:"boot_order" hcl:"boot_order"`
}
// FlatMapstructure returns a new FlatRunConfig.
// FlatRunConfig is an auto-generated flat version of RunConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*RunConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatRunConfig)
}
// HCL2Spec returns the hcl spec of a RunConfig.
// This spec is used by HCL to read the fields of RunConfig.
// The decoded values from this spec will then be applied to a FlatRunConfig.
func (*FlatRunConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,109 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ShutdownConfig
package common
import (
"bytes"
"context"
"fmt"
"log"
"time"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type ShutdownConfig struct {
// Specify a VM guest shutdown command. This command will be executed using
// the `communicator`. Otherwise the VMware guest tools are used to gracefully
// shutdown the VM guest.
Command string `mapstructure:"shutdown_command"`
// Amount of time to wait for graceful VM shutdown.
// Defaults to 5m or five minutes.
// This will likely need to be modified if the `communicator` is 'none'.
Timeout time.Duration `mapstructure:"shutdown_timeout"`
// Packer normally halts the virtual machine after all provisioners have
// run when no `shutdown_command` is defined. If this is set to `true`, Packer
// *will not* halt the virtual machine but will assume that you will send the stop
// signal yourself through a preseed.cfg, a script or the final provisioner.
// Packer will wait for a default of five minutes until the virtual machine is shutdown.
// The timeout can be changed using `shutdown_timeout` option.
DisableShutdown bool `mapstructure:"disable_shutdown"`
}
func (c *ShutdownConfig) Prepare(comm communicator.Config) (warnings []string, errs []error) {
if c.Timeout == 0 {
c.Timeout = 5 * time.Minute
}
if comm.Type == "none" && c.Command != "" {
warnings = append(warnings, "The parameter `shutdown_command` is ignored as it requires a `communicator`.")
}
return
}
type StepShutdown struct {
Config *ShutdownConfig
}
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if off, _ := vm.IsPoweredOff(); off {
// Probably power off initiated by last provisioner, though disable_shutdown is not set
ui.Say("VM is already powered off")
return multistep.ActionContinue
}
comm, _ := state.Get("communicator").(packersdk.Communicator)
if comm == nil {
msg := fmt.Sprintf("Please shutdown virtual machine within %s.", s.Config.Timeout)
ui.Message(msg)
} else if s.Config.DisableShutdown {
ui.Say("Automatic shutdown disabled. Please shutdown virtual machine.")
} else if s.Config.Command != "" {
// Communicator is not needed unless shutdown_command is populated
ui.Say("Executing shutdown command...")
log.Printf("Shutdown command: %s", s.Config.Command)
var stdout, stderr bytes.Buffer
cmd := &packersdk.RemoteCmd{
Command: s.Config.Command,
Stdout: &stdout,
Stderr: &stderr,
}
err := comm.Start(ctx, cmd)
if err != nil {
state.Put("error", fmt.Errorf("Failed to send shutdown command: %s", err))
return multistep.ActionHalt
}
} else {
ui.Say("Shutting down VM...")
err := vm.StartShutdown()
if err != nil {
state.Put("error", fmt.Errorf("Cannot shut down VM: %v", err))
return multistep.ActionHalt
}
}
log.Printf("Waiting max %s for shutdown to complete", s.Config.Timeout)
err := vm.WaitForShutdown(ctx, s.Config.Timeout)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}

View File

@ -1,35 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type ShutdownConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatShutdownConfig is an auto-generated flat version of ShutdownConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatShutdownConfig struct {
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command" hcl:"shutdown_command"`
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout" hcl:"shutdown_timeout"`
DisableShutdown *bool `mapstructure:"disable_shutdown" cty:"disable_shutdown" hcl:"disable_shutdown"`
}
// FlatMapstructure returns a new FlatShutdownConfig.
// FlatShutdownConfig is an auto-generated flat version of ShutdownConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ShutdownConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatShutdownConfig)
}
// HCL2Spec returns the hcl spec of a ShutdownConfig.
// This spec is used by HCL to read the fields of ShutdownConfig.
// The decoded values from this spec will then be applied to a FlatShutdownConfig.
func (*FlatShutdownConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"shutdown_command": &hcldec.AttrSpec{Name: "shutdown_command", Type: cty.String, Required: false},
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
"disable_shutdown": &hcldec.AttrSpec{Name: "disable_shutdown", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,32 +0,0 @@
package common
import (
"context"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type StepCreateSnapshot struct {
CreateSnapshot bool
}
func (s *StepCreateSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.CreateSnapshot {
ui.Say("Creating snapshot...")
err := vm.CreateSnapshot("Created by Packer")
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCreateSnapshot) Cleanup(state multistep.StateBag) {}

View File

@ -1,115 +0,0 @@
package common
import (
"context"
"fmt"
"io/ioutil"
"os"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/communicator/ssh"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/uuid"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
// 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").(packersdk.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.VirtualMachineDriver)
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").(packersdk.Ui)
ui.Error(fmt.Sprintf(
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
}
}
}

View File

@ -1,31 +0,0 @@
package common
import (
"context"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type StepConvertToTemplate struct {
ConvertToTemplate bool
}
func (s *StepConvertToTemplate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
if s.ConvertToTemplate {
ui.Say("Convert VM into template...")
err := vm.ConvertToTemplate()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConvertToTemplate) Cleanup(state multistep.StateBag) {}

View File

@ -1,182 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type WaitIpConfig
package common
import (
"context"
"fmt"
"log"
"net"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
type WaitIpConfig struct {
// Amount of time to wait for VM's IP, similar to 'ssh_timeout'.
// Defaults to 30m (30 minutes). See the Golang
// [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation
// for full details.
WaitTimeout time.Duration `mapstructure:"ip_wait_timeout"`
// Amount of time to wait for VM's IP to settle down, sometimes VM may
// report incorrect IP initially, then its recommended to set that
// parameter to apx. 2 minutes. Examples 45s and 10m. Defaults to
// 5s(5 seconds). See the Golang
// [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation
// for full details.
SettleTimeout time.Duration `mapstructure:"ip_settle_timeout"`
// Set this to a CIDR address to cause the service to wait for an address that is contained in
// this network range. Defaults to "0.0.0.0/0" for any ipv4 address. Examples include:
//
// * empty string ("") - remove all filters
// * `0:0:0:0:0:0:0:0/0` - allow only ipv6 addresses
// * `192.168.1.0/24` - only allow ipv4 addresses from 192.168.1.1 to 192.168.1.254
WaitAddress *string `mapstructure:"ip_wait_address"`
ipnet *net.IPNet
// WaitTimeout is a total timeout, so even if VM changes IP frequently and it doesn't settle down we will end waiting.
}
type StepWaitForIp struct {
Config *WaitIpConfig
}
func (c *WaitIpConfig) Prepare() []error {
var errs []error
if c.SettleTimeout == 0 {
c.SettleTimeout = 5 * time.Second
}
if c.WaitTimeout == 0 {
c.WaitTimeout = 30 * time.Minute
}
if c.WaitAddress == nil {
addr := "0.0.0.0/0"
c.WaitAddress = &addr
}
if *c.WaitAddress != "" {
var err error
_, c.ipnet, err = net.ParseCIDR(*c.WaitAddress)
if err != nil {
errs = append(errs, fmt.Errorf("unable to parse \"ip_wait_address\": %w", err))
}
}
return errs
}
func (c *WaitIpConfig) GetIPNet() *net.IPNet {
return c.ipnet
}
func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)
var ip string
var err error
sub, cancel := context.WithCancel(ctx)
waitDone := make(chan bool, 1)
defer func() {
cancel()
}()
go func() {
ui.Say("Waiting for IP...")
ip, err = doGetIp(vm, sub, s.Config)
waitDone <- true
}()
log.Printf("[INFO] Waiting for IP, up to total timeout: %s, settle timeout: %s", s.Config.WaitTimeout, s.Config.SettleTimeout)
timeout := time.After(s.Config.WaitTimeout)
for {
select {
case <-timeout:
cancel()
<-waitDone
if ip != "" {
state.Put("ip", ip)
log.Printf("[WARN] API timeout waiting for IP but one IP was found. Using IP: %s", ip)
return multistep.ActionContinue
}
err := fmt.Errorf("Timeout waiting for IP.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
case <-ctx.Done():
cancel()
log.Println("[WARN] Interrupt detected, quitting waiting for IP.")
return multistep.ActionHalt
case <-waitDone:
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("ip", ip)
ui.Say(fmt.Sprintf("IP address: %v", ip))
return multistep.ActionContinue
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
}
func doGetIp(vm *driver.VirtualMachineDriver, ctx context.Context, c *WaitIpConfig) (string, error) {
var prevIp = ""
var stopTime time.Time
var interval time.Duration
if c.SettleTimeout.Seconds() >= 120 {
interval = 30 * time.Second
} else if c.SettleTimeout.Seconds() >= 60 {
interval = 15 * time.Second
} else if c.SettleTimeout.Seconds() >= 10 {
interval = 5 * time.Second
} else {
interval = 1 * time.Second
}
loop:
ip, err := vm.WaitForIP(ctx, c.ipnet)
if err != nil {
return "", err
}
// Check for ctx cancellation to avoid printing any IP logs at the timeout
select {
case <-ctx.Done():
return ip, fmt.Errorf("IP wait cancelled.")
default:
}
if prevIp == "" || prevIp != ip {
if prevIp == "" {
log.Printf("VM IP aquired: %s", ip)
} else {
log.Printf("VM IP changed from %s to %s", prevIp, ip)
}
prevIp = ip
stopTime = time.Now().Add(c.SettleTimeout)
goto loop
} else {
log.Printf("VM IP is still the same: %s", prevIp)
if time.Now().After(stopTime) {
log.Printf("VM IP seems stable enough: %s", ip)
return ip, nil
}
select {
case <-ctx.Done():
return "", fmt.Errorf("IP wait cancelled")
case <-time.After(interval):
goto loop
}
}
}
func (s *StepWaitForIp) Cleanup(state multistep.StateBag) {}

View File

@ -1,35 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type WaitIpConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatWaitIpConfig is an auto-generated flat version of WaitIpConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatWaitIpConfig struct {
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout" hcl:"ip_wait_timeout"`
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout" hcl:"ip_settle_timeout"`
WaitAddress *string `mapstructure:"ip_wait_address" cty:"ip_wait_address" hcl:"ip_wait_address"`
}
// FlatMapstructure returns a new FlatWaitIpConfig.
// FlatWaitIpConfig is an auto-generated flat version of WaitIpConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*WaitIpConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatWaitIpConfig)
}
// HCL2Spec returns the hcl spec of a WaitIpConfig.
// This spec is used by HCL to read the fields of WaitIpConfig.
// The decoded values from this spec will then be applied to a FlatWaitIpConfig.
func (*FlatWaitIpConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"ip_wait_timeout": &hcldec.AttrSpec{Name: "ip_wait_timeout", Type: cty.String, Required: false},
"ip_settle_timeout": &hcldec.AttrSpec{Name: "ip_settle_timeout", Type: cty.String, Required: false},
"ip_wait_address": &hcldec.AttrSpec{Name: "ip_wait_address", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,118 +0,0 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type StorageConfig,DiskConfig
package common
import (
"fmt"
)
// Defines the disk storage for a VM.
//
// Example that will create a 15GB and a 20GB disk on the VM. The second disk will be thin provisioned:
//
// In JSON:
// ```json
// "storage": [
// {
// "disk_size": 15000
// },
// {
// "disk_size": 20000,
// "disk_thin_provisioned": true
// }
// ],
// ```
// In HCL2:
// ```hcl
// storage {
// disk_size = 15000
// }
// storage {
// disk_size = 20000
// disk_thin_provisioned = true
// }
// ```
//
// Example that creates 2 pvscsi controllers and adds 2 disks to each one:
//
// In JSON:
// ```json
// "disk_controller_type": ["pvscsi", "pvscsi"],
// "storage": [
// {
// "disk_size": 15000,
// "disk_controller_index": 0
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 0
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 1
// },
// {
// "disk_size": 15000,
// "disk_controller_index": 1
// }
// ],
// ```
//
// In HCL2:
// ```hcl
// disk_controller_type = ["pvscsi", "pvscsi"]
// storage {
// disk_size = 15000,
// disk_controller_index = 0
// }
// storage {
// disk_size = 15000
// disk_controller_index = 0
// }
// storage {
// disk_size = 15000
// disk_controller_index = 1
// }
// storage {
// disk_size = 15000
// disk_controller_index = 1
// }
// ```
type DiskConfig struct {
// The size of the disk in MB.
DiskSize int64 `mapstructure:"disk_size" required:"true"`
// Enable VMDK thin provisioning for VM. Defaults to `false`.
DiskThinProvisioned bool `mapstructure:"disk_thin_provisioned"`
// Enable VMDK eager scrubbing for VM. Defaults to `false`.
DiskEagerlyScrub bool `mapstructure:"disk_eagerly_scrub"`
// The assigned disk controller. Defaults to the first one (0)
DiskControllerIndex int `mapstructure:"disk_controller_index"`
}
type StorageConfig struct {
// Set VM disk controller type. Example `lsilogic`, `pvscsi`, `nvme`, or `scsi`. Use a list to define additional controllers.
// Defaults to `lsilogic`. See
// [SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362)
// for additional details.
DiskControllerType []string `mapstructure:"disk_controller_type"`
// Configures a collection of one or more disks to be provisioned along with the VM. See the [Storage Configuration](#storage-configuration).
Storage []DiskConfig `mapstructure:"storage"`
}
func (c *StorageConfig) Prepare() []error {
var errs []error
if len(c.Storage) > 0 {
for i, storage := range c.Storage {
if storage.DiskSize == 0 {
errs = append(errs, fmt.Errorf("storage[%d].'disk_size' is required", i))
}
if storage.DiskControllerIndex >= len(c.DiskControllerType) {
errs = append(errs, fmt.Errorf("storage[%d].'disk_controller_index' references an unknown disk controller", i))
}
}
}
return errs
}

View File

@ -1,62 +0,0 @@
// Code generated by "mapstructure-to-hcl2 -type StorageConfig,DiskConfig"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatDiskConfig is an auto-generated flat version of DiskConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatDiskConfig struct {
DiskSize *int64 `mapstructure:"disk_size" required:"true" cty:"disk_size" hcl:"disk_size"`
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned" hcl:"disk_thin_provisioned"`
DiskEagerlyScrub *bool `mapstructure:"disk_eagerly_scrub" cty:"disk_eagerly_scrub" hcl:"disk_eagerly_scrub"`
DiskControllerIndex *int `mapstructure:"disk_controller_index" cty:"disk_controller_index" hcl:"disk_controller_index"`
}
// FlatMapstructure returns a new FlatDiskConfig.
// FlatDiskConfig is an auto-generated flat version of DiskConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*DiskConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatDiskConfig)
}
// HCL2Spec returns the hcl spec of a DiskConfig.
// This spec is used by HCL to read the fields of DiskConfig.
// The decoded values from this spec will then be applied to a FlatDiskConfig.
func (*FlatDiskConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"disk_thin_provisioned": &hcldec.AttrSpec{Name: "disk_thin_provisioned", Type: cty.Bool, Required: false},
"disk_eagerly_scrub": &hcldec.AttrSpec{Name: "disk_eagerly_scrub", Type: cty.Bool, Required: false},
"disk_controller_index": &hcldec.AttrSpec{Name: "disk_controller_index", Type: cty.Number, Required: false},
}
return s
}
// FlatStorageConfig is an auto-generated flat version of StorageConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatStorageConfig struct {
DiskControllerType []string `mapstructure:"disk_controller_type" cty:"disk_controller_type" hcl:"disk_controller_type"`
Storage []FlatDiskConfig `mapstructure:"storage" cty:"storage" hcl:"storage"`
}
// FlatMapstructure returns a new FlatStorageConfig.
// FlatStorageConfig is an auto-generated flat version of StorageConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*StorageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatStorageConfig)
}
// HCL2Spec returns the hcl spec of a StorageConfig.
// This spec is used by HCL to read the fields of StorageConfig.
// The decoded values from this spec will then be applied to a FlatStorageConfig.
func (*FlatStorageConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.List(cty.String), Required: false},
"storage": &hcldec.BlockListSpec{TypeName: "storage", Nested: hcldec.ObjectSpec((*FlatDiskConfig)(nil).HCL2Spec())},
}
return s
}

View File

@ -1,69 +0,0 @@
package testing
import (
"encoding/json"
"fmt"
"math/rand"
"os"
"testing"
"time"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func NewVMName() string {
rand.Seed(time.Now().UnixNano())
return fmt.Sprintf("test-%v", rand.Intn(1000))
}
func RenderConfig(config map[string]interface{}) string {
t := map[string][]map[string]interface{}{
"builders": {
map[string]interface{}{
"type": "test",
},
},
}
for k, v := range config {
t["builders"][0][k] = v
}
j, _ := json.Marshal(t)
return string(j)
}
func TestConn(t *testing.T) driver.Driver {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",
Username: username,
Password: password,
InsecureConnection: true,
})
if err != nil {
t.Fatal("Cannot connect: ", err)
}
return d
}
func GetVM(t *testing.T, d driver.Driver, artifacts []packersdk.Artifact) driver.VirtualMachine {
artifactRaw := artifacts[0]
artifact, _ := artifactRaw.(*common.Artifact)
vm, err := d.FindVM(artifact.Name)
if err != nil {
t.Fatalf("Cannot find VM: %v", err)
}
return vm
}

View File

@ -1,19 +0,0 @@
package driver
import "github.com/vmware/govmomi/object"
type Cluster struct {
driver *VCenterDriver
cluster *object.ClusterComputeResource
}
func (d *VCenterDriver) FindCluster(name string) (*Cluster, error) {
c, err := d.finder.ClusterComputeResource(d.ctx, name)
if err != nil {
return nil, err
}
return &Cluster{
cluster: c,
driver: d,
}, nil
}

View File

@ -1,235 +0,0 @@
package driver
import (
"fmt"
"path"
"regexp"
"strings"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Datastore interface {
Info(params ...string) (*mo.Datastore, error)
FileExists(path string) bool
DirExists(path string) bool
Name() string
ResolvePath(path string) string
UploadFile(src, dst, host string, setHost bool) error
Delete(path string) error
MakeDirectory(path string) error
Reference() types.ManagedObjectReference
}
type DatastoreDriver struct {
ds *object.Datastore
driver *VCenterDriver
}
func (d *VCenterDriver) NewDatastore(ref *types.ManagedObjectReference) Datastore {
return &DatastoreDriver{
ds: object.NewDatastore(d.client.Client, *ref),
driver: d,
}
}
// If name is an empty string, then resolve host's one
func (d *VCenterDriver) FindDatastore(name string, host string) (Datastore, error) {
if name == "" {
h, err := d.FindHost(host)
if err != nil {
return nil, fmt.Errorf("Error finding host for to get datastore: %s", err)
}
i, err := h.Info("datastore")
if err != nil {
return nil, fmt.Errorf("Error getting datastore info from host: %s", err)
}
if len(i.Datastore) > 1 {
return nil, fmt.Errorf("Host has multiple datastores. Specify it explicitly")
}
ds := d.NewDatastore(&i.Datastore[0])
inf, err := ds.Info("name")
if err != nil {
return nil, fmt.Errorf("Error getting datastore name: %s", err)
}
name = inf.Name
}
ds, err := d.finder.Datastore(d.ctx, name)
if err != nil {
return nil, fmt.Errorf("Error finding datastore with name %s: %s", name, err)
}
return &DatastoreDriver{
ds: ds,
driver: d,
}, nil
}
func (d *VCenterDriver) GetDatastoreName(id string) (string, error) {
obj := types.ManagedObjectReference{
Type: "Datastore",
Value: id,
}
pc := property.DefaultCollector(d.vimClient)
var me mo.ManagedEntity
err := pc.RetrieveOne(d.ctx, obj, []string{"name"}, &me)
if err != nil {
return id, err
}
return me.Name, nil
}
func (ds *DatastoreDriver) Info(params ...string) (*mo.Datastore, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Datastore
err := ds.ds.Properties(ds.driver.ctx, ds.ds.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (ds *DatastoreDriver) DirExists(filepath string) bool {
_, err := ds.ds.Stat(ds.driver.ctx, filepath)
if _, ok := err.(object.DatastoreNoSuchDirectoryError); ok {
return false
}
return true
}
func (ds *DatastoreDriver) FileExists(path string) bool {
_, err := ds.ds.Stat(ds.driver.ctx, path)
return err == nil
}
func (ds *DatastoreDriver) Name() string {
return ds.ds.Name()
}
func (ds *DatastoreDriver) Reference() types.ManagedObjectReference {
return ds.ds.Reference()
}
func (ds *DatastoreDriver) ResolvePath(path string) string {
return ds.ds.Path(path)
}
// The file ID isn't available via the API, so we use DatastoreBrowser to search
func (d *VCenterDriver) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
ref := types.ManagedObjectReference{Type: "Datastore", Value: datastoreID}
ds := object.NewDatastore(d.vimClient, ref)
b, err := ds.Browser(d.ctx)
if err != nil {
return filename, err
}
ext := path.Ext(filename)
pat := strings.Replace(filename, ext, "*"+ext, 1)
spec := types.HostDatastoreBrowserSearchSpec{
MatchPattern: []string{pat},
}
task, err := b.SearchDatastore(d.ctx, dir, &spec)
if err != nil {
return filename, err
}
info, err := task.WaitForResult(d.ctx, nil)
if err != nil {
return filename, err
}
res, ok := info.Result.(types.HostDatastoreBrowserSearchResults)
if !ok {
return filename, fmt.Errorf("search(%s) result type=%T", pat, info.Result)
}
if len(res.File) != 1 {
return filename, fmt.Errorf("search(%s) result files=%d", pat, len(res.File))
}
return res.File[0].GetFileInfo().Path, nil
}
func (ds *DatastoreDriver) UploadFile(src, dst, host string, setHost bool) error {
p := soap.DefaultUpload
ctx := ds.driver.ctx
if setHost && host != "" {
h, err := ds.driver.FindHost(host)
if err != nil {
return err
}
ctx = ds.ds.HostContext(ctx, h.host)
}
return ds.ds.UploadFile(ctx, src, dst, &p)
}
func (ds *DatastoreDriver) Delete(path string) error {
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
if err != nil {
return err
}
fm := ds.ds.NewFileManager(dc, false)
return fm.Delete(ds.driver.ctx, path)
}
func (ds *DatastoreDriver) MakeDirectory(path string) error {
dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath)
if err != nil {
return err
}
fm := ds.ds.NewFileManager(dc, false)
return fm.FileManager.MakeDirectory(ds.driver.ctx, path, dc, true)
}
// Cuts out the datastore prefix
// Example: "[datastore1] file.ext" --> "file.ext"
func RemoveDatastorePrefix(path string) string {
res := object.DatastorePath{}
if hadPrefix := res.FromString(path); hadPrefix {
return res.Path
} else {
return path
}
}
type DatastoreIsoPath struct {
path string
}
func (d *DatastoreIsoPath) Validate() bool {
// Matches:
// [datastore] /dir/subdir/file
// [datastore] dir/subdir/file
// [] /dir/subdir/file
// [data-store] /dir/subdir/file
// dir/subdir/file or dir/subdir/file
matched, _ := regexp.MatchString(`^\s*(\[[^\[\]\/]*\])?\s*[^\[\]]+\s*$`, d.path)
return matched
}
func (d *DatastoreIsoPath) GetFilePath() string {
filePath := d.path
parts := strings.Split(d.path, "]")
if len(parts) > 1 {
// removes datastore name from path
filePath = parts[1]
filePath = strings.TrimSpace(filePath)
}
return filePath
}

View File

@ -1,96 +0,0 @@
package driver
import (
"fmt"
"io/ioutil"
"testing"
"time"
)
func TestDatastoreAcc(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
d := newTestDriver(t)
ds, err := d.FindDatastore("datastore1", "")
if err != nil {
t.Fatalf("Cannot find the default datastore '%v': %v", "datastore1", err)
}
info, err := ds.Info("name")
if err != nil {
t.Fatalf("Cannot read datastore properties: %v", err)
}
if info.Name != "datastore1" {
t.Errorf("Wrong datastore. expected: 'datastore1', got: '%v'", info.Name)
}
}
func TestFileUpload(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
dsName := "datastore1"
hostName := "esxi-1.vsphere65.test"
fileName := fmt.Sprintf("test-%v", time.Now().Unix())
tmpFile, err := ioutil.TempFile("", fileName)
if err != nil {
t.Fatalf("Error creating temp file")
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("Error creating temp file")
}
d := newTestDriver(t)
ds, err := d.FindDatastore(dsName, hostName)
if err != nil {
t.Fatalf("Cannot find datastore '%v': %v", dsName, err)
}
err = ds.UploadFile(tmpFile.Name(), fileName, hostName, true)
if err != nil {
t.Fatalf("Cannot upload file: %v", err)
}
if ds.FileExists(fileName) != true {
t.Fatalf("Cannot find file")
}
err = ds.Delete(fileName)
if err != nil {
t.Fatalf("Cannot delete file: %v", err)
}
}
func TestFileUploadDRS(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
dsName := "datastore3"
hostName := ""
fileName := fmt.Sprintf("test-%v", time.Now().Unix())
tmpFile, err := ioutil.TempFile("", fileName)
if err != nil {
t.Fatalf("Error creating temp file")
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("Error creating temp file")
}
d := newTestDriver(t)
ds, err := d.FindDatastore(dsName, hostName)
if err != nil {
t.Fatalf("Cannot find datastore '%v': %v", dsName, err)
}
err = ds.UploadFile(tmpFile.Name(), fileName, hostName, false)
if err != nil {
t.Fatalf("Cannot upload file: %v", err)
}
if ds.FileExists(fileName) != true {
t.Fatalf("Cannot find file")
}
err = ds.Delete(fileName)
if err != nil {
t.Fatalf("Cannot delete file: %v", err)
}
}

View File

@ -1,81 +0,0 @@
package driver
import (
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type DatastoreMock struct {
FileExistsCalled bool
FileExistsReturn bool
DirExistsCalled bool
DirExistsReturn bool
NameReturn string
MakeDirectoryCalled bool
ResolvePathCalled bool
ResolvePathReturn string
DeleteCalled bool
DeletePath string
DeleteErr error
UploadFileCalled bool
UploadFileSrc string
UploadFileDst string
UploadFileHost string
UploadFileSetHost bool
UploadFileErr error
}
func (ds *DatastoreMock) Info(params ...string) (*mo.Datastore, error) {
return nil, nil
}
func (ds *DatastoreMock) FileExists(path string) bool {
ds.FileExistsCalled = true
return ds.FileExistsReturn
}
func (ds *DatastoreMock) DirExists(path string) bool {
ds.DirExistsCalled = true
return ds.DirExistsReturn
}
func (ds *DatastoreMock) Name() string {
if ds.NameReturn == "" {
return "datastore-mock"
}
return ds.NameReturn
}
func (ds *DatastoreMock) Reference() types.ManagedObjectReference {
return types.ManagedObjectReference{}
}
func (ds *DatastoreMock) ResolvePath(path string) string {
ds.ResolvePathCalled = true
return ds.ResolvePathReturn
}
func (ds *DatastoreMock) UploadFile(src, dst, host string, setHost bool) error {
ds.UploadFileCalled = true
ds.UploadFileSrc = src
ds.UploadFileDst = dst
ds.UploadFileHost = host
ds.UploadFileSetHost = setHost
return ds.UploadFileErr
}
func (ds *DatastoreMock) Delete(path string) error {
ds.DeleteCalled = true
ds.DeletePath = path
return ds.DeleteErr
}
func (ds *DatastoreMock) MakeDirectory(path string) error {
ds.MakeDirectoryCalled = true
return nil
}

View File

@ -1,174 +0,0 @@
package driver
import (
"testing"
"github.com/vmware/govmomi/simulator"
)
func TestDatastoreIsoPath(t *testing.T) {
tc := []struct {
isoPath string
filePath string
valid bool
}{
{
isoPath: "[datastore] dir/subdir/file",
filePath: "dir/subdir/file",
valid: true,
},
{
isoPath: "[] dir/subdir/file",
filePath: "dir/subdir/file",
valid: true,
},
{
isoPath: "dir/subdir/file",
filePath: "dir/subdir/file",
valid: true,
},
{
isoPath: "[datastore] /dir/subdir/file",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "/dir/subdir/file [datastore] ",
valid: false,
},
{
isoPath: "[datastore][] /dir/subdir/file",
valid: false,
},
{
isoPath: "[data/store] /dir/subdir/file",
valid: false,
},
{
isoPath: "[data store] /dir/sub dir/file",
filePath: "/dir/sub dir/file",
valid: true,
},
{
isoPath: " [datastore] /dir/subdir/file",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "[datastore] /dir/subdir/file",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "[datastore] /dir/subdir/file ",
filePath: "/dir/subdir/file",
valid: true,
},
{
isoPath: "[привѣ́тъ] /привѣ́тъ/привѣ́тъ/привѣ́тъ",
filePath: "/привѣ́тъ/привѣ́тъ/привѣ́тъ",
valid: true,
},
// Test case for #9846
{
isoPath: "[ISO-StorageLun9] Linux/rhel-8.0-x86_64-dvd.iso",
filePath: "Linux/rhel-8.0-x86_64-dvd.iso",
valid: true,
},
}
for i, c := range tc {
dsIsoPath := &DatastoreIsoPath{path: c.isoPath}
if dsIsoPath.Validate() != c.valid {
t.Fatalf("%d Expecting %s to be %t but was %t", i, c.isoPath, c.valid, !c.valid)
}
if !c.valid {
continue
}
filePath := dsIsoPath.GetFilePath()
if filePath != c.filePath {
t.Fatalf("%d Expecting %s but got %s", i, c.filePath, filePath)
}
}
}
func TestVCenterDriver_FindDatastore(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
_, datastore := sim.ChooseSimulatorPreCreatedDatastore()
_, host := sim.ChooseSimulatorPreCreatedHost()
tc := []struct {
name string
datastore string
host string
fail bool
errMessage string
}{
{
name: "should find datastore when name is provided",
datastore: datastore.Name,
fail: false,
},
{
name: "should find datastore when only host is provided",
host: host.Name,
fail: false,
},
{
name: "should not find invalid datastore",
datastore: "invalid",
fail: true,
},
{
name: "should not find invalid host",
host: "invalid",
fail: true,
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
ds, err := sim.driver.FindDatastore(c.datastore, c.host)
if c.fail {
if err == nil {
t.Fatalf("expected to fail")
}
if c.errMessage != "" && err.Error() != c.errMessage {
t.Fatalf("unexpected error message %s", err.Error())
}
} else {
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if ds == nil {
t.Fatalf("expected to find datastore")
}
}
})
}
}
func TestVCenterDriver_MultipleDatastoreError(t *testing.T) {
model := simulator.ESX()
model.Datastore = 2
sim, err := NewCustomVCenterSimulator(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
_, host := sim.ChooseSimulatorPreCreatedHost()
_, err = sim.driver.FindDatastore("", host.Name)
if err == nil {
t.Fatalf("expected to fail")
}
if err.Error() != "Host has multiple datastores. Specify it explicitly" {
t.Fatalf("unexpected error message %s", err.Error())
}
}

View File

@ -1,85 +0,0 @@
package driver
import (
"errors"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
)
type Disk struct {
DiskSize int64
DiskEagerlyScrub bool
DiskThinProvisioned bool
ControllerIndex int
}
type StorageConfig struct {
DiskControllerType []string // example: "scsi", "pvscsi", "nvme", "lsilogic"
Storage []Disk
}
func (c *StorageConfig) AddStorageDevices(existingDevices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
newDevices := object.VirtualDeviceList{}
// Create new controller based on existing devices list and add it to the new devices list
// to confirm creation
var controllers []types.BaseVirtualController
for _, controllerType := range c.DiskControllerType {
var device types.BaseVirtualDevice
var err error
if controllerType == "nvme" {
device, err = existingDevices.CreateNVMEController()
} else {
device, err = existingDevices.CreateSCSIController(controllerType)
}
if err != nil {
return nil, err
}
existingDevices = append(existingDevices, device)
newDevices = append(newDevices, device)
controller, err := existingDevices.FindDiskController(existingDevices.Name(device))
if err != nil {
return nil, err
}
controllers = append(controllers, controller)
}
for _, dc := range c.Storage {
disk := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Key: existingDevices.NewKey(),
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(dc.DiskThinProvisioned),
EagerlyScrub: types.NewBool(dc.DiskEagerlyScrub),
},
},
CapacityInKB: dc.DiskSize * 1024,
}
existingDevices.AssignController(disk, controllers[dc.ControllerIndex])
existingDevices = append(existingDevices, disk)
newDevices = append(newDevices, disk)
}
return newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
}
func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) {
var disks []*types.VirtualDisk
for _, device := range devices {
switch d := device.(type) {
case *types.VirtualDisk:
disks = append(disks, d)
}
}
switch len(disks) {
case 0:
return nil, errors.New("VM has no disks")
case 1:
return disks[0], nil
}
return nil, errors.New("VM has multiple disks")
}

View File

@ -1,46 +0,0 @@
package driver
import (
"testing"
"github.com/vmware/govmomi/object"
)
func TestAddStorageDevices(t *testing.T) {
config := &StorageConfig{
DiskControllerType: []string{"pvscsi"},
Storage: []Disk{
{
DiskSize: 3072,
DiskThinProvisioned: true,
ControllerIndex: 0,
},
{
DiskSize: 20480,
DiskThinProvisioned: true,
ControllerIndex: 0,
},
},
}
noExistingDevices := object.VirtualDeviceList{}
storageConfigSpec, err := config.AddStorageDevices(noExistingDevices)
if err != nil {
t.Fatalf("unexpected erro: %q", err.Error())
}
if len(storageConfigSpec) != 3 {
t.Fatalf("Expecting VirtualDeviceList to have 3 storage devices but had %d", len(storageConfigSpec))
}
existingDevices := object.VirtualDeviceList{}
device, err := existingDevices.CreateNVMEController()
existingDevices = append(existingDevices, device)
storageConfigSpec, err = config.AddStorageDevices(existingDevices)
if err != nil {
t.Fatalf("unexpected erro: %q", err.Error())
}
if len(storageConfigSpec) != 3 {
t.Fatalf("Expecting VirtualDeviceList to have 3 storage devices but had %d", len(storageConfigSpec))
}
}

View File

@ -1,129 +0,0 @@
package driver
import (
"context"
"fmt"
"net/url"
"time"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vapi/library"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Driver interface {
NewVM(ref *types.ManagedObjectReference) VirtualMachine
FindVM(name string) (VirtualMachine, error)
FindCluster(name string) (*Cluster, error)
PreCleanVM(ui packersdk.Ui, vmPath string, force bool) error
CreateVM(config *CreateConfig) (VirtualMachine, error)
NewDatastore(ref *types.ManagedObjectReference) Datastore
FindDatastore(name string, host string) (Datastore, error)
GetDatastoreName(id string) (string, error)
GetDatastoreFilePath(datastoreID, dir, filename string) (string, error)
NewFolder(ref *types.ManagedObjectReference) *Folder
FindFolder(name string) (*Folder, error)
NewHost(ref *types.ManagedObjectReference) *Host
FindHost(name string) (*Host, error)
NewNetwork(ref *types.ManagedObjectReference) *Network
FindNetwork(name string) (*Network, error)
FindNetworks(name string) ([]*Network, error)
NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool
FindResourcePool(cluster string, host string, name string) (*ResourcePool, error)
FindContentLibraryByName(name string) (*Library, error)
FindContentLibraryItem(libraryId string, name string) (*library.Item, error)
FindContentLibraryFileDatastorePath(isoPath string) (string, error)
}
type VCenterDriver struct {
// context that controls the authenticated sessions used to run the VM commands
ctx context.Context
client *govmomi.Client
vimClient *vim25.Client
restClient *RestClient
finder *find.Finder
datacenter *object.Datacenter
}
type ConnectConfig struct {
VCenterServer string
Username string
Password string
InsecureConnection bool
Datacenter string
}
func NewDriver(config *ConnectConfig) (Driver, error) {
ctx := context.TODO()
vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer))
if err != nil {
return nil, err
}
credentials := url.UserPassword(config.Username, config.Password)
vcenterUrl.User = credentials
soapClient := soap.NewClient(vcenterUrl, config.InsecureConnection)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
}
vimClient.RoundTripper = session.KeepAlive(vimClient.RoundTripper, 10*time.Minute)
client := &govmomi.Client{
Client: vimClient,
SessionManager: session.NewManager(vimClient),
}
err = client.SessionManager.Login(ctx, credentials)
if err != nil {
return nil, err
}
finder := find.NewFinder(client.Client, false)
datacenter, err := finder.DatacenterOrDefault(ctx, config.Datacenter)
if err != nil {
return nil, err
}
finder.SetDatacenter(datacenter)
d := &VCenterDriver{
ctx: ctx,
client: client,
vimClient: vimClient,
restClient: &RestClient{
client: rest.NewClient(vimClient),
credentials: credentials,
},
datacenter: datacenter,
finder: finder,
}
return d, nil
}
// The rest.Client requires vCenter.
// RestClient is to modularize the rest.Client session and use it only when is necessary.
// This will allow users without vCenter to use the other features that doesn't use the rest.Client.
// To use the client login/logout must be done to create an authenticated session.
type RestClient struct {
client *rest.Client
credentials *url.Userinfo
}
func (r *RestClient) Login(ctx context.Context) error {
return r.client.Login(ctx, r.credentials)
}
func (r *RestClient) Logout(ctx context.Context) error {
return r.client.Logout(ctx)
}

View File

@ -1,119 +0,0 @@
package driver
import (
"fmt"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/vmware/govmomi/vapi/library"
"github.com/vmware/govmomi/vim25/types"
)
type DriverMock struct {
FindDatastoreCalled bool
DatastoreMock *DatastoreMock
FindDatastoreName string
FindDatastoreHost string
FindDatastoreErr error
PreCleanShouldFail bool
PreCleanVMCalled bool
PreCleanForce bool
PreCleanVMPath string
CreateVMShouldFail bool
CreateVMCalled bool
CreateConfig *CreateConfig
VM VirtualMachine
FindVMCalled bool
FindVMName string
}
func NewDriverMock() *DriverMock {
return new(DriverMock)
}
func (d *DriverMock) FindDatastore(name string, host string) (Datastore, error) {
d.FindDatastoreCalled = true
if d.DatastoreMock == nil {
d.DatastoreMock = new(DatastoreMock)
}
d.FindDatastoreName = name
d.FindDatastoreHost = host
return d.DatastoreMock, d.FindDatastoreErr
}
func (d *DriverMock) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
return nil
}
func (d *DriverMock) FindVM(name string) (VirtualMachine, error) {
d.FindVMCalled = true
if d.VM == nil {
d.VM = new(VirtualMachineMock)
}
d.FindVMName = name
return d.VM, d.FindDatastoreErr
}
func (d *DriverMock) FindCluster(name string) (*Cluster, error) {
return nil, nil
}
func (d *DriverMock) PreCleanVM(ui packersdk.Ui, vmPath string, force bool) error {
d.PreCleanVMCalled = true
if d.PreCleanShouldFail {
return fmt.Errorf("pre clean failed")
}
d.PreCleanForce = true
d.PreCleanVMPath = vmPath
return nil
}
func (d *DriverMock) CreateVM(config *CreateConfig) (VirtualMachine, error) {
d.CreateVMCalled = true
if d.CreateVMShouldFail {
return nil, fmt.Errorf("create vm failed")
}
d.CreateConfig = config
d.VM = new(VirtualMachineDriver)
return d.VM, nil
}
func (d *DriverMock) NewDatastore(ref *types.ManagedObjectReference) Datastore { return nil }
func (d *DriverMock) GetDatastoreName(id string) (string, error) { return "", nil }
func (d *DriverMock) GetDatastoreFilePath(datastoreID, dir, filename string) (string, error) {
return "", nil
}
func (d *DriverMock) NewFolder(ref *types.ManagedObjectReference) *Folder { return nil }
func (d *DriverMock) FindFolder(name string) (*Folder, error) { return nil, nil }
func (d *DriverMock) NewHost(ref *types.ManagedObjectReference) *Host { return nil }
func (d *DriverMock) FindHost(name string) (*Host, error) { return nil, nil }
func (d *DriverMock) NewNetwork(ref *types.ManagedObjectReference) *Network { return nil }
func (d *DriverMock) FindNetwork(name string) (*Network, error) { return nil, nil }
func (d *DriverMock) FindNetworks(name string) ([]*Network, error) { return nil, nil }
func (d *DriverMock) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool { return nil }
func (d *DriverMock) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
return nil, nil
}
func (d *DriverMock) FindContentLibraryByName(name string) (*Library, error) { return nil, nil }
func (d *DriverMock) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) {
return nil, nil
}
func (d *DriverMock) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
return "", nil
}

View File

@ -1,171 +0,0 @@
package driver
import (
"context"
"crypto/tls"
"fmt"
"math/rand"
"net/http"
"net/url"
"os"
"testing"
"time"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vapi/rest"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
)
// Defines whether acceptance tests should be run
const TestHostName = "esxi-1.vsphere65.test"
func newTestDriver(t *testing.T) Driver {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
d, err := NewDriver(&ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",
Username: username,
Password: password,
InsecureConnection: true,
})
if err != nil {
t.Fatalf("Cannot connect: %v", err)
}
return d
}
func newVMName() string {
rand.Seed(time.Now().UTC().UnixNano())
return fmt.Sprintf("test-%v", rand.Intn(1000))
}
type VCenterSimulator struct {
model *simulator.Model
server *simulator.Server
driver *VCenterDriver
}
func NewCustomVCenterSimulator(model *simulator.Model) (*VCenterSimulator, error) {
sim := new(VCenterSimulator)
sim.model = model
server, err := sim.NewSimulatorServer()
if err != nil {
sim.Close()
return nil, err
}
sim.server = server
driver, err := sim.NewSimulatorDriver()
if err != nil {
sim.Close()
return nil, err
}
sim.driver = driver
return sim, nil
}
func NewVCenterSimulator() (*VCenterSimulator, error) {
model := simulator.VPX()
model.Machine = 1
return NewCustomVCenterSimulator(model)
}
func (s *VCenterSimulator) Close() {
if s.model != nil {
s.model.Remove()
}
if s.server != nil {
s.server.Close()
}
}
//Simulator shortcut to choose any pre created VM.
func (s *VCenterSimulator) ChooseSimulatorPreCreatedVM() (VirtualMachine, *simulator.VirtualMachine) {
machine := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
ref := machine.Reference()
vm := s.driver.NewVM(&ref)
return vm, machine
}
//Simulator shortcut to choose any pre created Datastore.
func (s *VCenterSimulator) ChooseSimulatorPreCreatedDatastore() (Datastore, *simulator.Datastore) {
ds := simulator.Map.Any("Datastore").(*simulator.Datastore)
ref := ds.Reference()
datastore := s.driver.NewDatastore(&ref)
return datastore, ds
}
//Simulator shortcut to choose any pre created Host.
func (s *VCenterSimulator) ChooseSimulatorPreCreatedHost() (*Host, *simulator.HostSystem) {
h := simulator.Map.Any("HostSystem").(*simulator.HostSystem)
ref := h.Reference()
host := s.driver.NewHost(&ref)
return host, h
}
func (s *VCenterSimulator) NewSimulatorServer() (*simulator.Server, error) {
err := s.model.Create()
if err != nil {
return nil, err
}
s.model.Service.RegisterEndpoints = true
s.model.Service.TLS = new(tls.Config)
s.model.Service.ServeMux = http.NewServeMux()
return s.model.Service.NewServer(), nil
}
func (s *VCenterSimulator) NewSimulatorDriver() (*VCenterDriver, error) {
ctx := context.TODO()
user := &url.Userinfo{}
s.server.URL.User = user
soapClient := soap.NewClient(s.server.URL, true)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
}
vimClient.RoundTripper = session.KeepAlive(vimClient.RoundTripper, 10*time.Minute)
client := &govmomi.Client{
Client: vimClient,
SessionManager: session.NewManager(vimClient),
}
err = client.SessionManager.Login(ctx, user)
if err != nil {
return nil, err
}
finder := find.NewFinder(client.Client, false)
datacenter, err := finder.DatacenterOrDefault(ctx, "")
if err != nil {
return nil, err
}
finder.SetDatacenter(datacenter)
d := &VCenterDriver{
ctx: ctx,
client: client,
vimClient: vimClient,
restClient: &RestClient{
client: rest.NewClient(vimClient),
credentials: user,
},
datacenter: datacenter,
finder: finder,
}
return d, nil
}

View File

@ -1,93 +0,0 @@
package driver
import (
"fmt"
"path"
"strings"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Folder struct {
driver *VCenterDriver
folder *object.Folder
}
func (d *VCenterDriver) NewFolder(ref *types.ManagedObjectReference) *Folder {
return &Folder{
folder: object.NewFolder(d.client.Client, *ref),
driver: d,
}
}
func (d *VCenterDriver) FindFolder(name string) (*Folder, error) {
if name != "" {
// create folders if they don't exist
parent := ""
parentFolder, err := d.finder.Folder(d.ctx, path.Join(d.datacenter.InventoryPath, "vm"))
if err != nil {
return nil, err
}
folders := strings.Split(name, "/")
for _, folder := range folders {
parent = path.Join(parent, folder)
f, err := d.finder.Folder(d.ctx, path.Join(d.datacenter.InventoryPath, "vm", parent))
if _, ok := err.(*find.NotFoundError); ok {
f, err = parentFolder.CreateFolder(d.ctx, folder)
}
if err != nil {
return nil, err
}
parentFolder = f
}
}
f, err := d.finder.Folder(d.ctx, path.Join(d.datacenter.InventoryPath, "vm", name))
if err != nil {
return nil, err
}
return &Folder{
folder: f,
driver: d,
}, nil
}
func (f *Folder) Info(params ...string) (*mo.Folder, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Folder
err := f.folder.Properties(f.driver.ctx, f.folder.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (f *Folder) Path() (string, error) {
info, err := f.Info("name", "parent")
if err != nil {
return "", err
}
if info.Parent.Type == "Datacenter" {
return "", nil
} else {
parent := f.driver.NewFolder(info.Parent)
path, err := parent.Path()
if err != nil {
return "", err
}
if path == "" {
return info.Name, nil
} else {
return fmt.Sprintf("%v/%v", path, info.Name), nil
}
}
}

View File

@ -1,19 +0,0 @@
package driver
import "testing"
func TestFolderAcc(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
d := newTestDriver(t)
f, err := d.FindFolder("folder1/folder2")
if err != nil {
t.Fatalf("Cannot find the default folder '%v': %v", "folder1/folder2", err)
}
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if path != "folder1/folder2" {
t.Errorf("Wrong folder. expected: 'folder1/folder2', got: '%v'", path)
}
}

View File

@ -1,45 +0,0 @@
package driver
import (
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Host struct {
driver *VCenterDriver
host *object.HostSystem
}
func (d *VCenterDriver) NewHost(ref *types.ManagedObjectReference) *Host {
return &Host{
host: object.NewHostSystem(d.client.Client, *ref),
driver: d,
}
}
func (d *VCenterDriver) FindHost(name string) (*Host, error) {
h, err := d.finder.HostSystem(d.ctx, name)
if err != nil {
return nil, err
}
return &Host{
host: h,
driver: d,
}, nil
}
func (h *Host) Info(params ...string) (*mo.HostSystem, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.HostSystem
err := h.host.Properties(h.driver.ctx, h.host.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@ -1,22 +0,0 @@
package driver
import (
"testing"
)
func TestHostAcc(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
d := newTestDriver(t)
host, err := d.FindHost(TestHostName)
if err != nil {
t.Fatalf("Cannot find the default host '%v': %v", "datastore1", err)
}
info, err := host.Info("name")
if err != nil {
t.Fatalf("Cannot read host properties: %v", err)
}
if info.Name != TestHostName {
t.Errorf("Wrong host name: expected '%v', got: '%v'", TestHostName, info.Name)
}
}

View File

@ -1,113 +0,0 @@
package driver
import (
"fmt"
"log"
"path"
"strings"
"github.com/vmware/govmomi/vapi/library"
)
type Library struct {
driver *VCenterDriver
library *library.Library
}
func (d *VCenterDriver) FindContentLibraryByName(name string) (*Library, error) {
lm := library.NewManager(d.restClient.client)
l, err := lm.GetLibraryByName(d.ctx, name)
if err != nil {
return nil, err
}
return &Library{
library: l,
driver: d,
}, nil
}
func (d *VCenterDriver) FindContentLibraryItem(libraryId string, name string) (*library.Item, error) {
lm := library.NewManager(d.restClient.client)
items, err := lm.GetLibraryItems(d.ctx, libraryId)
if err != nil {
return nil, err
}
for _, item := range items {
if item.Name == name {
return &item, nil
}
}
return nil, fmt.Errorf("Item %s not found", name)
}
func (d *VCenterDriver) FindContentLibraryFileDatastorePath(isoPath string) (string, error) {
log.Printf("Check if ISO path is a Content Library path")
err := d.restClient.Login(d.ctx)
if err != nil {
log.Printf("vCenter client not available. ISO path not identified as a Content Library path")
return isoPath, err
}
libraryFilePath := &LibraryFilePath{path: isoPath}
err = libraryFilePath.Validate()
if err != nil {
log.Printf("ISO path not identified as a Content Library path")
return isoPath, err
}
libraryName := libraryFilePath.GetLibraryName()
itemName := libraryFilePath.GetLibraryItemName()
isoFile := libraryFilePath.GetFileName()
lib, err := d.FindContentLibraryByName(libraryName)
if err != nil {
log.Printf("ISO path not identified as a Content Library path")
return isoPath, err
}
log.Printf("ISO path identified as a Content Library path")
log.Printf("Finding the equivalent datastore path for the Content Library ISO file path")
libItem, err := d.FindContentLibraryItem(lib.library.ID, itemName)
if err != nil {
log.Printf("[WARN] Couldn't find item %s: %s", itemName, err.Error())
return isoPath, err
}
datastoreName, err := d.GetDatastoreName(lib.library.Storage[0].DatastoreID)
if err != nil {
log.Printf("[WARN] Couldn't find datastore name for library %s", libraryName)
return isoPath, err
}
libItemDir := fmt.Sprintf("[%s] contentlib-%s/%s", datastoreName, lib.library.ID, libItem.ID)
isoFilePath, err := d.GetDatastoreFilePath(lib.library.Storage[0].DatastoreID, libItemDir, isoFile)
if err != nil {
log.Printf("[WARN] Couldn't find datastore ID path for %s", isoFile)
return isoPath, err
}
_ = d.restClient.Logout(d.ctx)
return path.Join(libItemDir, isoFilePath), nil
}
type LibraryFilePath struct {
path string
}
func (l *LibraryFilePath) Validate() error {
l.path = strings.TrimLeft(l.path, "/")
parts := strings.Split(l.path, "/")
if len(parts) != 3 {
return fmt.Errorf("Not a valid Content Library File path. The path must contain the nanmes for the library, item and file.")
}
return nil
}
func (l *LibraryFilePath) GetLibraryName() string {
return strings.Split(l.path, "/")[0]
}
func (l *LibraryFilePath) GetLibraryItemName() string {
return strings.Split(l.path, "/")[1]
}
func (l *LibraryFilePath) GetFileName() string {
return strings.Split(l.path, "/")[2]
}

View File

@ -1,62 +0,0 @@
package driver
import "testing"
func TestLibraryFilePath(t *testing.T) {
tc := []struct {
filePath string
libraryName string
libraryItemName string
fileName string
valid bool
}{
{
filePath: "lib/item/file",
libraryName: "lib",
libraryItemName: "item",
fileName: "file",
valid: true,
},
{
filePath: "/lib/item/file",
libraryName: "lib",
libraryItemName: "item",
fileName: "file",
valid: true,
},
{
filePath: "/lib/item/filedir/file",
valid: false,
},
{
filePath: "/lib/item",
valid: false,
},
{
filePath: "/lib",
valid: false,
},
}
for _, c := range tc {
libraryFilePath := &LibraryFilePath{path: c.filePath}
if err := libraryFilePath.Validate(); err != nil {
if c.valid {
t.Fatalf("Expecting %s to be valid", c.filePath)
}
continue
}
libraryName := libraryFilePath.GetLibraryName()
if libraryName != c.libraryName {
t.Fatalf("Expecting %s but got %s", c.libraryName, libraryName)
}
libraryItemName := libraryFilePath.GetLibraryItemName()
if libraryItemName != c.libraryItemName {
t.Fatalf("Expecting %s but got %s", c.libraryItemName, libraryItemName)
}
fileName := libraryFilePath.GetFileName()
if fileName != c.fileName {
t.Fatalf("Expecting %s but got %s", c.fileName, fileName)
}
}
}

View File

@ -1,77 +0,0 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Network struct {
driver *VCenterDriver
network object.NetworkReference
}
func (d *VCenterDriver) NewNetwork(ref *types.ManagedObjectReference) *Network {
return &Network{
network: object.NewNetwork(d.client.Client, *ref),
driver: d,
}
}
func (d *VCenterDriver) FindNetwork(name string) (*Network, error) {
n, err := d.finder.Network(d.ctx, name)
if err != nil {
return nil, err
}
return &Network{
network: n,
driver: d,
}, nil
}
func (d *VCenterDriver) FindNetworks(name string) ([]*Network, error) {
ns, err := d.finder.NetworkList(d.ctx, name)
if err != nil {
return nil, err
}
var networks []*Network
for _, n := range ns {
networks = append(networks, &Network{
network: n,
driver: d,
})
}
return networks, nil
}
func (n *Network) Info(params ...string) (*mo.Network, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Network
network, ok := n.network.(*object.Network)
if !ok {
return nil, fmt.Errorf("unexpected %t network object type", n.network)
}
err := network.Properties(n.driver.ctx, network.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
type MultipleNetworkFoundError struct {
path string
append string
}
func (e *MultipleNetworkFoundError) Error() string {
return fmt.Sprintf("path '%s' resolves to multiple networks. %s", e.path, e.append)
}

View File

@ -1,91 +0,0 @@
package driver
import (
"fmt"
"log"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type ResourcePool struct {
pool *object.ResourcePool
driver *VCenterDriver
}
func (d *VCenterDriver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool {
return &ResourcePool{
pool: object.NewResourcePool(d.client.Client, *ref),
driver: d,
}
}
func (d *VCenterDriver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
var res string
if cluster != "" {
res = cluster
} else {
res = host
}
resourcePath := fmt.Sprintf("%v/Resources/%v", res, name)
p, err := d.finder.ResourcePool(d.ctx, resourcePath)
if err != nil {
log.Printf("[WARN] %s not found. Looking for default resource pool.", resourcePath)
dp, dperr := d.finder.DefaultResourcePool(d.ctx)
if _, ok := dperr.(*find.NotFoundError); ok {
// VirtualApp extends ResourcePool, so it should support VirtualApp types.
vapp, verr := d.finder.VirtualApp(d.ctx, name)
if verr != nil {
return nil, err
}
dp = vapp.ResourcePool
} else if dperr != nil {
return nil, err
}
p = dp
}
return &ResourcePool{
pool: p,
driver: d,
}, nil
}
func (p *ResourcePool) Info(params ...string) (*mo.ResourcePool, error) {
var params2 []string
if len(params) == 0 {
params2 = []string{"*"}
} else {
params2 = params
}
var info mo.ResourcePool
err := p.pool.Properties(p.driver.ctx, p.pool.Reference(), params2, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (p *ResourcePool) Path() (string, error) {
poolInfo, err := p.Info("name", "parent")
if err != nil {
return "", err
}
if poolInfo.Parent.Type == "ComputeResource" {
return "", nil
} else {
parent := p.driver.NewResourcePool(poolInfo.Parent)
parentPath, err := parent.Path()
if err != nil {
return "", err
}
if parentPath == "" {
return poolInfo.Name, nil
} else {
return fmt.Sprintf("%v/%v", parentPath, poolInfo.Name), nil
}
}
}

View File

@ -1,20 +0,0 @@
package driver
import "testing"
func TestResourcePoolAcc(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
d := newTestDriver(t)
p, err := d.FindResourcePool("", "esxi-1.vsphere65.test", "pool1/pool2")
if err != nil {
t.Fatalf("Cannot find the default resource pool '%v': %v", "pool1/pool2", err)
}
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if path != "pool1/pool2" {
t.Errorf("Wrong folder. expected: 'pool1/pool2', got: '%v'", path)
}
}

View File

@ -1,71 +0,0 @@
package driver
import (
"testing"
"github.com/vmware/govmomi/simulator"
)
func TestVCenterDriver_FindResourcePool(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
res, err := sim.driver.FindResourcePool("", "DC0_H0", "")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if res == nil {
t.Fatalf("resource pool should not be nil")
}
expectedResourcePool := "Resources"
if res.pool.Name() != expectedResourcePool {
t.Fatalf("resource name expected %s but was %s", expectedResourcePool, res.pool.Name())
}
}
func TestVCenterDriver_FindResourcePoolStandaloneESX(t *testing.T) {
// standalone ESX host without any vCenter
model := simulator.ESX()
defer model.Remove()
opts := simulator.VPX()
model.Datastore = opts.Datastore
model.Machine = opts.Machine
model.Autostart = opts.Autostart
model.DelayConfig.Delay = opts.DelayConfig.Delay
model.DelayConfig.MethodDelay = opts.DelayConfig.MethodDelay
model.DelayConfig.DelayJitter = opts.DelayConfig.DelayJitter
sim, err := NewCustomVCenterSimulator(model)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
res, err := sim.driver.FindResourcePool("", "localhost.localdomain", "")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if res == nil {
t.Fatalf("resource pool should not be nil")
}
expectedResourcePool := "Resources"
if res.pool.Name() != expectedResourcePool {
t.Fatalf("resource name expected %s but was %s", expectedResourcePool, res.pool.Name())
}
// Invalid resource name should look for default resource pool
res, err = sim.driver.FindResourcePool("", "localhost.localdomain", "invalid")
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if res == nil {
t.Fatalf("resource pool should not be nil")
}
if res.pool.Name() != expectedResourcePool {
t.Fatalf("resource name expected %s but was %s", expectedResourcePool, res.pool.Name())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +0,0 @@
package driver
import (
"errors"
"github.com/vmware/govmomi/vim25/types"
)
var (
ErrNoSataController = errors.New("no available SATA controller")
)
func (vm *VirtualMachineDriver) AddSATAController() error {
sata := &types.VirtualAHCIController{}
return vm.addDevice(sata)
}
func (vm *VirtualMachineDriver) FindSATAController() (*types.VirtualAHCIController, error) {
l, err := vm.Devices()
if err != nil {
return nil, err
}
c := l.PickController((*types.VirtualAHCIController)(nil))
if c == nil {
return nil, ErrNoSataController
}
return c.(*types.VirtualAHCIController), nil
}
func (vm *VirtualMachineDriver) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) {
l, err := vm.Devices()
if err != nil {
return nil, err
}
device := &types.VirtualCdrom{}
l.AssignController(device, c)
device.Backing = &types.VirtualCdromAtapiBackingInfo{
VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{},
}
device.Connectable = &types.VirtualDeviceConnectInfo{
AllowGuestControl: true,
Connected: true,
StartConnected: true,
}
return device, nil
}
func (vm *VirtualMachineDriver) RemoveCdroms() error {
devices, err := vm.Devices()
if err != nil {
return err
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
if err = vm.RemoveDevice(true, cdroms...); err != nil {
return err
}
sata := devices.SelectByType((*types.VirtualAHCIController)(nil))
if err = vm.RemoveDevice(true, sata...); err != nil {
return err
}
return nil
}
func (vm *VirtualMachineDriver) EjectCdroms() error {
devices, err := vm.Devices()
if err != nil {
return err
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
for _, cd := range cdroms {
c := cd.(*types.VirtualCdrom)
c.Backing = &types.VirtualCdromRemotePassthroughBackingInfo{}
c.Connectable = &types.VirtualDeviceConnectInfo{}
err := vm.vm.EditDevice(vm.driver.ctx, c)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,167 +0,0 @@
package driver
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/vmware/govmomi/vim25/types"
)
func TestVirtualMachineDriver_FindAndAddSATAController(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
vm, _ := sim.ChooseSimulatorPreCreatedVM()
_, err = vm.FindSATAController()
if err != nil && !strings.Contains(err.Error(), "no available SATA controller") {
t.Fatalf("unexpected error: %s", err.Error())
}
if err == nil {
t.Fatalf("vm should not have sata controller")
}
if err := vm.AddSATAController(); err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
sc, err := vm.FindSATAController()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if sc == nil {
t.Fatalf("SATA controller wasn't added properly")
}
}
func TestVirtualMachineDriver_CreateAndRemoveCdrom(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
vm, _ := sim.ChooseSimulatorPreCreatedVM()
// Add SATA Controller
if err := vm.AddSATAController(); err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
// Verify if controller was created
sc, err := vm.FindSATAController()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if sc == nil {
t.Fatalf("SATA controller wasn't added properly")
}
// Create CDROM
controller := sc.GetVirtualController()
cdrom, err := vm.CreateCdrom(controller)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if cdrom == nil {
t.Fatalf("CDrom wasn't created properly")
}
// Verify if CDROM was created
devices, err := vm.Devices()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
if len(cdroms) != 1 {
t.Fatalf("unexpected numbers of cdrom: %d", len(cdroms))
}
// Remove CDROM
err = vm.RemoveCdroms()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
// Verify if CDROM was removed
devices, err = vm.Devices()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
cdroms = devices.SelectByType((*types.VirtualCdrom)(nil))
if len(cdroms) != 0 {
t.Fatalf("unexpected numbers of cdrom: %d", len(cdroms))
}
}
func TestVirtualMachineDriver_EjectCdrom(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
vm, _ := sim.ChooseSimulatorPreCreatedVM()
// Add SATA Controller
if err := vm.AddSATAController(); err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
// Verify if controller was created
sc, err := vm.FindSATAController()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if sc == nil {
t.Fatalf("SATA controller wasn't added properly")
}
// Create CDROM
controller := sc.GetVirtualController()
cdrom, err := vm.CreateCdrom(controller)
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
if cdrom == nil {
t.Fatalf("CDrom wasn't created properly")
}
// Verify if CDROM was created
devices, err := vm.Devices()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
if len(cdroms) != 1 {
t.Fatalf("unexpected numbers of cdrom: %d", len(cdroms))
}
// Remove CDROM
err = vm.EjectCdroms()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
// Verify if CDROM was removed
devices, err = vm.Devices()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
cdroms = devices.SelectByType((*types.VirtualCdrom)(nil))
if len(cdroms) != 1 {
t.Fatalf("unexpected numbers of cdrom: %d", len(cdroms))
}
cd, ok := cdroms[0].(*types.VirtualCdrom)
if !ok {
t.Fatalf("Wrong cdrom type")
}
if diff := cmp.Diff(cd.Backing, &types.VirtualCdromRemotePassthroughBackingInfo{}); diff != "" {
t.Fatalf("Wrong cdrom backing info: %s", diff)
}
if diff := cmp.Diff(cd.Connectable, &types.VirtualDeviceConnectInfo{}); diff != "" {
t.Fatalf("Wrong cdrom connect info: %s", diff)
}
}

View File

@ -1,312 +0,0 @@
package driver
import (
"context"
"log"
"net"
"testing"
"time"
)
func TestVMAcc_clone(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
testCases := []struct {
name string
config *CloneConfig
checkFunction func(*testing.T, VirtualMachine, *CloneConfig)
}{
{"Default", &CloneConfig{}, cloneDefaultCheck},
{"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck},
{"Folder", &CloneConfig{LinkedClone: true, Folder: "folder1/folder2"}, cloneFolderCheck},
{"ResourcePool", &CloneConfig{LinkedClone: true, ResourcePool: "pool1/pool2"}, cloneResourcePoolCheck},
{"Configure", &CloneConfig{LinkedClone: true}, configureCheck},
{"Configure_RAMReserveAll", &CloneConfig{LinkedClone: true}, configureRAMReserveAllCheck},
{"StartAndStop", &CloneConfig{LinkedClone: true}, startAndStopCheck},
{"Template", &CloneConfig{LinkedClone: true}, templateCheck},
{"Snapshot", &CloneConfig{}, snapshotCheck},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.config.Host = TestHostName
tc.config.Name = newVMName()
templateName := "alpine"
d := newTestDriver(t)
template, err := d.FindVM(templateName) // Don't destroy this VM!
if err != nil {
t.Fatalf("Cannot find template vm '%v': %v", templateName, err)
}
log.Printf("[DEBUG] Clonning VM")
vm, err := template.Clone(context.TODO(), tc.config)
if err != nil {
t.Fatalf("Cannot clone vm '%v': %v", templateName, err)
}
defer destroyVM(t, vm, tc.config.Name)
log.Printf("[DEBUG] Running check function")
tc.checkFunction(t, vm, tc.config)
})
}
}
func cloneDefaultCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
d := vm.(*VirtualMachineDriver).driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
t.Errorf("Cannot find created vm '%v': %v", config.Name, err)
}
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != config.Name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", config.Name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != TestHostName {
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != "datastore1" {
t.Errorf("Invalid datastore name: expected '%v', got '%v'", "datastore1", dsInfo.Name)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 1 {
t.Error("Not a full clone")
}
}
func configureCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
log.Printf("[DEBUG] Configuring the vm")
hwConfig := &HardwareConfig{
CPUs: 2,
CPUReservation: 1000,
CPULimit: 1500,
RAM: 2048,
RAMReservation: 1024,
MemoryHotAddEnabled: true,
CpuHotAddEnabled: true,
}
err := vm.Configure(hwConfig)
if err != nil {
t.Fatalf("Failed to configure VM: %v", err)
}
log.Printf("[DEBUG] Running checks")
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
cpuSockets := vmInfo.Config.Hardware.NumCPU
if cpuSockets != hwConfig.CPUs {
t.Errorf("VM should have %v CPU sockets, got %v", hwConfig.CPUs, cpuSockets)
}
cpuReservation := *vmInfo.Config.CpuAllocation.Reservation
if cpuReservation != hwConfig.CPUReservation {
t.Errorf("VM should have CPU reservation for %v Mhz, got %v", hwConfig.CPUReservation, cpuReservation)
}
cpuLimit := *vmInfo.Config.CpuAllocation.Limit
if cpuLimit != hwConfig.CPULimit {
t.Errorf("VM should have CPU reservation for %v Mhz, got %v", hwConfig.CPULimit, cpuLimit)
}
ram := vmInfo.Config.Hardware.MemoryMB
if int64(ram) != hwConfig.RAM {
t.Errorf("VM should have %v MB of RAM, got %v", hwConfig.RAM, ram)
}
ramReservation := *vmInfo.Config.MemoryAllocation.Reservation
if ramReservation != hwConfig.RAMReservation {
t.Errorf("VM should have RAM reservation for %v MB, got %v", hwConfig.RAMReservation, ramReservation)
}
cpuHotAdd := vmInfo.Config.CpuHotAddEnabled
if *cpuHotAdd != hwConfig.CpuHotAddEnabled {
t.Errorf("VM should have CPU hot add set to %v, got %v", hwConfig.CpuHotAddEnabled, cpuHotAdd)
}
memoryHotAdd := vmInfo.Config.MemoryHotAddEnabled
if *memoryHotAdd != hwConfig.MemoryHotAddEnabled {
t.Errorf("VM should have Memroy hot add set to %v, got %v", hwConfig.MemoryHotAddEnabled, memoryHotAdd)
}
}
func configureRAMReserveAllCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
log.Printf("[DEBUG] Configuring the vm")
err := vm.Configure(&HardwareConfig{RAMReserveAll: true})
if err != nil {
t.Fatalf("Failed to configure VM: %v", err)
}
log.Printf("[DEBUG] Running checks")
vmInfo, err := vm.Info("config")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if *vmInfo.Config.MemoryReservationLockedToMax != true {
t.Errorf("VM should have all RAM reserved")
}
}
func cloneLinkedCloneCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if len(vmInfo.LayoutEx.Disk[0].Chain) != 2 {
t.Error("Not a linked clone")
}
}
func cloneFolderCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
vmInfo, err := vm.Info("parent")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
f := vm.(*VirtualMachineDriver).driver.NewFolder(vmInfo.Parent)
path, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if path != config.Folder {
t.Errorf("Wrong folder. expected: %v, got: %v", config.Folder, path)
}
}
func cloneResourcePoolCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
vmInfo, err := vm.Info("resourcePool")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
p := vm.(*VirtualMachineDriver).driver.NewResourcePool(vmInfo.ResourcePool)
path, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if path != config.ResourcePool {
t.Errorf("Wrong folder. expected: %v, got: %v", config.ResourcePool, path)
}
}
func startAndStopCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
stopper := startVM(t, vm, config.Name)
defer stopper()
switch ip, err := vm.WaitForIP(context.TODO(), nil); {
case err != nil:
t.Errorf("Cannot obtain IP address from created vm '%v': %v", config.Name, err)
case net.ParseIP(ip) == nil:
t.Errorf("'%v' is not a valid ip address", ip)
}
err := vm.StartShutdown()
if err != nil {
t.Fatalf("Failed to initiate guest shutdown: %v", err)
}
log.Printf("[DEBUG] Waiting max 1m0s for shutdown to complete")
err = vm.WaitForShutdown(context.TODO(), 1*time.Minute)
if err != nil {
t.Fatalf("Failed to wait for giest shutdown: %v", err)
}
}
func snapshotCheck(t *testing.T, vm VirtualMachine, config *CloneConfig) {
stopper := startVM(t, vm, config.Name)
defer stopper()
err := vm.CreateSnapshot("test-snapshot")
if err != nil {
t.Fatalf("Failed to create snapshot: %v", err)
}
vmInfo, err := vm.Info("layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
layers := len(vmInfo.LayoutEx.Disk[0].Chain)
if layers != 2 {
t.Errorf("VM should have a single snapshot. expected 2 disk layers, got %v", layers)
}
}
func templateCheck(t *testing.T, vm VirtualMachine, _ *CloneConfig) {
err := vm.ConvertToTemplate()
if err != nil {
t.Fatalf("Failed to convert to template: %v", err)
}
vmInfo, err := vm.Info("config.template")
if err != nil {
t.Errorf("Cannot read VM properties: %v", err)
} else if !vmInfo.Config.Template {
t.Error("Not a template")
}
}
func startVM(t *testing.T, vm VirtualMachine, vmName string) (stopper func()) {
log.Printf("[DEBUG] Starting the vm")
if err := vm.PowerOn(); err != nil {
t.Fatalf("Cannot start vm '%v': %v", vmName, err)
}
return func() {
log.Printf("[DEBUG] Powering off the vm")
if err := vm.PowerOff(); err != nil {
t.Errorf("Cannot power off started vm '%v': %v", vmName, err)
}
}
}
func destroyVM(t *testing.T, vm VirtualMachine, vmName string) {
log.Printf("[DEBUG] Deleting the VM")
if err := vm.Destroy(); err != nil {
t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err)
}
// Check that the clone is no longer exists
if _, err := vm.(*VirtualMachineDriver).driver.FindVM(vmName); err == nil {
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName)
}
}

View File

@ -1,92 +0,0 @@
package driver
import (
"log"
"testing"
)
func TestVMAcc_create(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
testCases := []struct {
name string
config *CreateConfig
checkFunction func(*testing.T, VirtualMachine, *CreateConfig)
}{
{"MinimalConfiguration", &CreateConfig{}, createDefaultCheck},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.config.Host = TestHostName
tc.config.Name = newVMName()
d := newTestDriver(t)
log.Printf("[DEBUG] Creating VM")
vm, err := d.CreateVM(tc.config)
if err != nil {
t.Fatalf("Cannot create VM: %v", err)
}
defer destroyVM(t, vm, tc.config.Name)
log.Printf("[DEBUG] Running check function")
tc.checkFunction(t, vm, tc.config)
})
}
}
func createDefaultCheck(t *testing.T, vm VirtualMachine, config *CreateConfig) {
d := vm.(*VirtualMachineDriver).driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
t.Errorf("Cannot find created vm '%v': %v", config.Name, err)
}
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != config.Name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", config.Name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != TestHostName {
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != "datastore1" {
t.Errorf("Invalid datastore name: expected 'datastore1', got '%v'", dsInfo.Name)
}
}

View File

@ -1,39 +0,0 @@
package driver
import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/mobile/event/key"
)
type KeyInput struct {
Scancode key.Code
Alt bool
Ctrl bool
Shift bool
}
func (vm *VirtualMachineDriver) TypeOnKeyboard(input KeyInput) (int32, error) {
var spec types.UsbScanCodeSpec
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
UsbHidCode: int32(input.Scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &input.Shift,
},
})
req := &types.PutUsbScanCodes{
This: vm.vm.Reference(),
Spec: spec,
}
resp, err := methods.PutUsbScanCodes(vm.driver.ctx, vm.driver.client.RoundTripper, req)
if err != nil {
return 0, err
}
return resp.Returnval, nil
}

View File

@ -1,235 +0,0 @@
package driver
import (
"context"
"net"
"time"
"github.com/vmware/govmomi/nfc"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/ovf"
"github.com/vmware/govmomi/vapi/vcenter"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type VirtualMachineMock struct {
DestroyError error
DestroyCalled bool
ConfigureError error
ConfigureCalled bool
ConfigureHardwareConfig *HardwareConfig
FindSATAControllerCalled bool
FindSATAControllerErr error
AddSATAControllerCalled bool
AddSATAControllerErr error
AddCdromCalled bool
AddCdromCalledTimes int
AddCdromErr error
AddCdromTypes []string
AddCdromPaths []string
GetDirCalled bool
GetDirResponse string
GetDirErr error
AddFloppyCalled bool
AddFloppyImagePath string
AddFloppyErr error
FloppyDevicesErr error
FloppyDevicesReturn object.VirtualDeviceList
FloppyDevicesCalled bool
RemoveDeviceErr error
RemoveDeviceCalled bool
RemoveDeviceKeepFiles bool
RemoveDeviceDevices []types.BaseVirtualDevice
EjectCdromsCalled bool
EjectCdromsErr error
RemoveCdromsCalled bool
RemoveCdromsErr error
CloneCalled bool
CloneConfig *CloneConfig
CloneError error
}
func (vm *VirtualMachineMock) Info(params ...string) (*mo.VirtualMachine, error) {
return nil, nil
}
func (vm *VirtualMachineMock) Devices() (object.VirtualDeviceList, error) {
return object.VirtualDeviceList{}, nil
}
func (vm *VirtualMachineMock) FloppyDevices() (object.VirtualDeviceList, error) {
vm.FloppyDevicesCalled = true
return vm.FloppyDevicesReturn, vm.FloppyDevicesErr
}
func (vm *VirtualMachineMock) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
vm.CloneCalled = true
vm.CloneConfig = config
return vm, vm.CloneError
}
func (vm *VirtualMachineMock) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
return nil, nil
}
func (vm *VirtualMachineMock) AddPublicKeys(ctx context.Context, publicKeys string) error {
return nil
}
func (vm *VirtualMachineMock) Properties(ctx context.Context) (*mo.VirtualMachine, error) {
return nil, nil
}
func (vm *VirtualMachineMock) Destroy() error {
vm.DestroyCalled = true
if vm.DestroyError != nil {
return vm.DestroyError
}
return nil
}
func (vm *VirtualMachineMock) Configure(config *HardwareConfig) error {
vm.ConfigureCalled = true
vm.ConfigureHardwareConfig = config
if vm.ConfigureError != nil {
return vm.ConfigureError
}
return nil
}
func (vm *VirtualMachineMock) Customize(spec types.CustomizationSpec) error {
return nil
}
func (vm *VirtualMachineMock) ResizeDisk(diskSize int64) ([]types.BaseVirtualDeviceConfigSpec, error) {
return nil, nil
}
func (vm *VirtualMachineMock) PowerOn() error {
return nil
}
func (vm *VirtualMachineMock) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) {
return "", nil
}
func (vm *VirtualMachineMock) PowerOff() error {
return nil
}
func (vm *VirtualMachineMock) IsPoweredOff() (bool, error) {
return false, nil
}
func (vm *VirtualMachineMock) StartShutdown() error {
return nil
}
func (vm *VirtualMachineMock) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
return nil
}
func (vm *VirtualMachineMock) CreateSnapshot(name string) error {
return nil
}
func (vm *VirtualMachineMock) ConvertToTemplate() error {
return nil
}
func (vm *VirtualMachineMock) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
return nil
}
func (vm *VirtualMachineMock) ImportToContentLibrary(template vcenter.Template) error {
return nil
}
func (vm *VirtualMachineMock) GetDir() (string, error) {
vm.GetDirCalled = true
return vm.GetDirResponse, vm.GetDirErr
}
func (vm *VirtualMachineMock) AddCdrom(cdromType string, isoPath string) error {
vm.AddCdromCalledTimes++
vm.AddCdromCalled = true
vm.AddCdromTypes = append(vm.AddCdromTypes, cdromType)
vm.AddCdromPaths = append(vm.AddCdromPaths, isoPath)
return vm.AddCdromErr
}
func (vm *VirtualMachineMock) AddFloppy(imgPath string) error {
vm.AddFloppyCalled = true
vm.AddFloppyImagePath = imgPath
return vm.AddFloppyErr
}
func (vm *VirtualMachineMock) SetBootOrder(order []string) error {
return nil
}
func (vm *VirtualMachineMock) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
vm.RemoveDeviceCalled = true
vm.RemoveDeviceKeepFiles = keepFiles
vm.RemoveDeviceDevices = device
return vm.RemoveDeviceErr
}
func (vm *VirtualMachineMock) addDevice(device types.BaseVirtualDevice) error {
return nil
}
func (vm *VirtualMachineMock) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error {
return nil
}
func (vm *VirtualMachineMock) Export() (*nfc.Lease, error) {
return nil, nil
}
func (vm *VirtualMachineMock) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
return nil, nil
}
func (vm *VirtualMachineMock) NewOvfManager() *ovf.Manager {
return nil
}
func (vm *VirtualMachineMock) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
return nil, nil
}
func (vm *VirtualMachineMock) AddSATAController() error {
vm.AddSATAControllerCalled = true
return vm.AddSATAControllerErr
}
func (vm *VirtualMachineMock) FindSATAController() (*types.VirtualAHCIController, error) {
vm.FindSATAControllerCalled = true
return nil, vm.FindSATAControllerErr
}
func (vm *VirtualMachineMock) CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error) {
return nil, nil
}
func (vm *VirtualMachineMock) RemoveCdroms() error {
vm.RemoveCdromsCalled = true
return vm.RemoveCdromsErr
}
func (vm *VirtualMachineMock) EjectCdroms() error {
vm.EjectCdromsCalled = true
return vm.EjectCdromsErr
}

View File

@ -1,187 +0,0 @@
package driver
import (
"context"
"testing"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
// ReconfigureFail changes the behavior of simulator.VirtualMachine
type ReconfigureFail struct {
*simulator.VirtualMachine
}
// Override simulator.VirtualMachine.ReconfigVMTask to inject faults
func (vm *ReconfigureFail) ReconfigVMTask(req *types.ReconfigVM_Task) soap.HasFault {
task := simulator.CreateTask(req.This, "reconfigure", func(*simulator.Task) (types.AnyType, types.BaseMethodFault) {
return nil, &types.TaskInProgress{}
})
return &methods.ReconfigVM_TaskBody{
Res: &types.ReconfigVM_TaskResponse{
Returnval: task.Run(),
},
}
}
func TestVirtualMachineDriver_Configure(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
vm, machine := sim.ChooseSimulatorPreCreatedVM()
// Happy test
hardwareConfig := &HardwareConfig{
CPUs: 1,
CpuCores: 1,
CPUReservation: 2500,
CPULimit: 1,
RAM: 1024,
RAMReserveAll: true,
VideoRAM: 512,
VGPUProfile: "grid_m10-8q",
Firmware: "efi-secure",
ForceBIOSSetup: true,
}
if err = vm.Configure(hardwareConfig); err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
//Fail test
//Wrap the existing vm object with the mocked reconfigure task which will return a fault
simulator.Map.Put(&ReconfigureFail{machine})
if err = vm.Configure(&HardwareConfig{}); err == nil {
t.Fatalf("Configure should fail")
}
}
func TestVirtualMachineDriver_CreateVMWithMultipleDisks(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
_, datastore := sim.ChooseSimulatorPreCreatedDatastore()
config := &CreateConfig{
Name: "mock name",
Host: "DC0_H0",
Datastore: datastore.Name,
NICs: []NIC{
{
Network: "VM Network",
NetworkCard: "vmxnet3",
},
},
StorageConfig: StorageConfig{
DiskControllerType: []string{"pvscsi"},
Storage: []Disk{
{
DiskSize: 3072,
DiskThinProvisioned: true,
ControllerIndex: 0,
},
{
DiskSize: 20480,
DiskThinProvisioned: true,
ControllerIndex: 0,
},
},
},
}
vm, err := sim.driver.CreateVM(config)
if err != nil {
t.Fatalf("unexpected error %s", err.Error())
}
devices, err := vm.Devices()
if err != nil {
t.Fatalf("unexpected error %s", err.Error())
}
var disks []*types.VirtualDisk
for _, device := range devices {
switch d := device.(type) {
case *types.VirtualDisk:
disks = append(disks, d)
}
}
if len(disks) != 2 {
t.Fatalf("unexpected number of devices")
}
}
func TestVirtualMachineDriver_CloneWithPrimaryDiskResize(t *testing.T) {
sim, err := NewVCenterSimulator()
if err != nil {
t.Fatalf("should not fail: %s", err.Error())
}
defer sim.Close()
_, datastore := sim.ChooseSimulatorPreCreatedDatastore()
vm, _ := sim.ChooseSimulatorPreCreatedVM()
config := &CloneConfig{
Name: "mock name",
Host: "DC0_H0",
Datastore: datastore.Name,
PrimaryDiskSize: 204800,
StorageConfig: StorageConfig{
DiskControllerType: []string{"pvscsi"},
Storage: []Disk{
{
DiskSize: 3072,
DiskThinProvisioned: true,
ControllerIndex: 0,
},
{
DiskSize: 20480,
DiskThinProvisioned: true,
ControllerIndex: 0,
},
},
},
}
clonedVM, err := vm.Clone(context.TODO(), config)
if err != nil {
t.Fatalf("unexpected error %s", err.Error())
}
devices, err := clonedVM.Devices()
if err != nil {
t.Fatalf("unexpected error %s", err.Error())
}
var disks []*types.VirtualDisk
for _, device := range devices {
switch d := device.(type) {
case *types.VirtualDisk:
disks = append(disks, d)
}
}
if len(disks) != 3 {
t.Fatalf("unexpected number of devices")
}
if disks[0].CapacityInKB != config.PrimaryDiskSize*1024 {
t.Fatalf("unexpected disk size for primary disk: %d", disks[0].CapacityInKB)
}
if disks[1].CapacityInKB != config.StorageConfig.Storage[0].DiskSize*1024 {
t.Fatalf("unexpected disk size for primary disk: %d", disks[1].CapacityInKB)
}
if disks[2].CapacityInKB != config.StorageConfig.Storage[1].DiskSize*1024 {
t.Fatalf("unexpected disk size for primary disk: %d", disks[2].CapacityInKB)
}
}

View File

@ -1,64 +0,0 @@
{
"builders": [
{
"type": "vsphere-iso",
"CPUs": 1,
"RAM": 512,
"RAM_reserve_all": true,
"boot_command": [
"root<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"setup-alpine -f /media/floppy/answerfile<enter>",
"<wait5>",
"jetbrains<enter>",
"jetbrains<enter>",
"<wait5>",
"y<enter>",
"<wait10><wait10><wait10><wait10>",
"reboot<enter>",
"<wait10><wait10>",
"root<enter>",
"jetbrains<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"/media/floppy/SETUP.SH<enter>"
],
"boot_wait": "15s",
"disk_controller_type": "pvscsi",
"floppy_files": [
"{{template_dir}}/answerfile",
"{{template_dir}}/setup.sh"
],
"guest_os_type": "other3xLinux64Guest",
"host": "esxi-1.vsphere65.test",
"insecure_connection": true,
"iso_paths": [
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"
],
"network_adapters": [
{
"network_card": "vmxnet3"
}
],
"password": "jetbrains",
"ssh_password": "jetbrains",
"ssh_username": "root",
"storage": [
{
"disk_size": 1024,
"disk_thin_provisioned": true
}
],
"username": "root",
"vcenter_server": "vcenter.vsphere65.test",
"vm_name": "alpine-{{timestamp}}"
}
],
"provisioners": [
{
"inline": [
"ls /"
],
"type": "shell"
}
]
}

View File

@ -1,44 +0,0 @@
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-iso" "autogenerated_1" {
CPUs = 1
RAM = 512
RAM_reserve_all = true
boot_command = ["root<enter><wait>", "mount -t vfat /dev/fd0 /media/floppy<enter><wait>", "setup-alpine -f /media/floppy/answerfile<enter>", "<wait5>", "jetbrains<enter>", "jetbrains<enter>", "<wait5>", "y<enter>", "<wait10><wait10><wait10><wait10>", "reboot<enter>", "<wait10><wait10>", "root<enter>", "jetbrains<enter><wait>", "mount -t vfat /dev/fd0 /media/floppy<enter><wait>", "/media/floppy/SETUP.SH<enter>"]
boot_wait = "15s"
disk_controller_type = ["pvscsi"]
floppy_files = ["${path.root}/answerfile", "${path.root}/setup.sh"]
guest_os_type = "other3xLinux64Guest"
host = "esxi-1.vsphere65.test"
insecure_connection = true
iso_paths = ["[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"]
network_adapters {
network_card = "vmxnet3"
}
password = "jetbrains"
ssh_password = "jetbrains"
ssh_username = "root"
storage {
disk_size = 1024
disk_thin_provisioned = true
}
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "alpine-${local.timestamp}"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-iso.autogenerated_1"]
provisioner "shell" {
inline = ["ls /"]
}
}

View File

@ -1,15 +0,0 @@
KEYMAPOPTS="us us"
HOSTNAMEOPTS="-n alpine"
INTERFACESOPTS="auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
hostname alpine
"
TIMEZONEOPTS="-z UTC"
PROXYOPTS="none"
APKREPOSOPTS="http://mirror.yandex.ru/mirrors/alpine/v3.8/main"
SSHDOPTS="-c openssh"
NTPOPTS="-c none"
DISKOPTS="-m sys /dev/sda"

View File

@ -1,19 +0,0 @@
#!/bin/sh
set -ex
apk add libressl
apk add open-vm-tools
rc-update add open-vm-tools
/etc/init.d/open-vm-tools start
cat >/usr/local/bin/shutdown <<EOF
#!/bin/sh
poweroff
EOF
chmod +x /usr/local/bin/shutdown
sed -i "/#PermitRootLogin/c\PermitRootLogin yes" /etc/ssh/sshd_config
mkdir ~/.ssh
wget https://raw.githubusercontent.com/jetbrains-infra/packer-builder-vsphere/master/test/test-key.pub -O ~/.ssh/authorized_keys
/etc/init.d/sshd restart

View File

@ -1,18 +0,0 @@
{
"builders": [
{
"type": "vsphere-clone",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"template": "alpine",
"vm_name": "alpine-clone-{{timestamp}}",
"host": "esxi-1.vsphere65.test",
"communicator": "none"
}
]
}

View File

@ -1,25 +0,0 @@
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-clone" "example_clone" {
communicator = "none"
host = "esxi-1.vsphere65.test"
insecure_connection = "true"
password = "jetbrains"
template = "alpine"
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "alpine-clone-${local.timestamp}"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-clone.example_clone"]
}

View File

@ -1,26 +0,0 @@
package main
import (
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
)
func main() {
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",
Username: "root",
Password: "jetbrains",
InsecureConnection: true,
})
if err != nil {
panic(err)
}
ds, err := d.FindDatastore("", "esxi-1.vsphere65.test")
if err != nil {
panic(err)
}
fmt.Println(ds.Name())
}

View File

@ -1,49 +0,0 @@
{
"builders": [
{
"type": "vsphere-iso",
"CPUs": 1,
"RAM": 4096,
"boot_command": [
"<enter><wait5>",
"<leftCtrlOn><f2><leftCtrlOff>u<enter>t<enter><wait5>",
"/Volumes/setup/setup.sh<enter>"
],
"boot_wait": "4m",
"cdrom_type": "sata",
"configuration_parameters": {
"ich7m.present": "TRUE",
"smc.present": "TRUE"
},
"guest_os_type": "darwin16_64Guest",
"host": "esxi-mac.vsphere65.test",
"insecure_connection": "true",
"iso_checksum": "file:///{{template_dir}}/setup/out/sha256sums",
"iso_paths": [
"[datastore-mac] ISO/macOS 10.13.3.iso",
"[datastore-mac] ISO/VMware Tools/10.2.0/darwin.iso"
],
"iso_urls": [
"{{template_dir}}/setup/out/setup.iso"
],
"network_adapters": [
{
"network_card": "e1000e"
}
],
"password": "jetbrains",
"ssh_password": "jetbrains",
"ssh_username": "jetbrains",
"storage": [
{
"disk_size": 32768,
"disk_thin_provisioned": true
}
],
"usb_controller": true,
"username": "root",
"vcenter_server": "vcenter.vsphere65.test",
"vm_name": "macos-packer"
}
]
}

View File

@ -1,43 +0,0 @@
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-iso" "example_osx" {
CPUs = 1
RAM = 4096
boot_command = ["<enter><wait5>", "<leftCtrlOn><f2><leftCtrlOff>u<enter>t<enter><wait5>", "/Volumes/setup/setup.sh<enter>"]
boot_wait = "4m"
cdrom_type = "sata"
configuration_parameters = {
"ich7m.present" = "TRUE"
"smc.present" = "TRUE"
}
guest_os_type = "darwin16_64Guest"
host = "esxi-mac.vsphere65.test"
insecure_connection = "true"
iso_checksum = "file:///${path.root}/setup/out/sha256sums"
iso_paths = ["[datastore-mac] ISO/macOS 10.13.3.iso", "[datastore-mac] ISO/VMware Tools/10.2.0/darwin.iso"]
iso_urls = ["${path.root}/setup/out/setup.iso"]
network_adapters {
network_card = "e1000e"
}
password = "jetbrains"
ssh_password = "jetbrains"
ssh_username = "jetbrains"
storage {
disk_size = 32768
disk_thin_provisioned = true
}
usb_controller = ["usb"]
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "macos-packer"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-iso.example_osx"]
}

View File

@ -1 +0,0 @@
out/

View File

@ -1,15 +0,0 @@
#!/bin/sh
set -eux
# Based on
# https://gist.github.com/agentsim/00cc38c693e7d0e1b36a2080870d955b#gistcomment-2304505
mkdir -p out
hdiutil create -o out/HighSierra.cdr -size 5530m -layout SPUD -fs HFS+J
hdiutil attach out/HighSierra.cdr.dmg -noverify -mountpoint /Volumes/install_build
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/install_build --nointeraction
hdiutil detach /Volumes/Install\ macOS\ High\ Sierra
hdiutil convert out/HighSierra.cdr.dmg -format UDTO -o out/HighSierra.iso
mv out/HighSierra.iso.cdr out/HighSierra.iso
rm out/HighSierra.cdr.dmg

Some files were not shown because too many files have changed in this diff Show More