remove vsphere components and docs
This commit is contained in:
parent
a6c5958c67
commit
38fe79948b
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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 /"]
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"]
|
||||
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"]
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
out/
|
|
@ -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
Loading…
Reference in New Issue