Merge pull request #8480 from hashicorp/merge-vsphere-builder

Merge the vSphere builder
This commit is contained in:
Megan Marsh 2020-01-15 14:11:20 -08:00 committed by GitHub
commit fac320d290
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
178 changed files with 15816 additions and 2442 deletions

View File

@ -0,0 +1,99 @@
package clone
import (
"context"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
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 packer.Ui, hook packer.Hook) (packer.Artifact, error) {
state := new(multistep.BasicStateBag)
state.Put("hook", hook)
state.Put("ui", ui)
var steps []multistep.Step
steps = append(steps,
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
&StepCloneVM{
Config: &b.config.CloneConfig,
Location: &b.config.LocationConfig,
Force: b.config.PackerConfig.PackerForce,
},
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&common.StepConfigParams{
Config: &b.config.ConfigParamsConfig,
},
)
if b.config.Comm.Type != "none" {
steps = append(steps,
&common.StepRun{
Config: &b.config.RunConfig,
SetOrder: false,
},
&common.StepWaitForIp{
Config: &b.config.WaitIpConfig,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: common.CommHost(b.config.Comm.SSHHost),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&packerCommon.StepProvision{},
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
)
}
steps = append(steps,
&common.StepCreateSnapshot{
CreateSnapshot: b.config.CreateSnapshot,
},
&common.StepConvertToTemplate{
ConvertToTemplate: b.config.ConvertToTemplate,
},
)
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
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.VirtualMachine),
}
return artifact, nil
}

View File

@ -0,0 +1,733 @@
package clone
import (
"github.com/hashicorp/packer/builder/vsphere/common"
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi/vim25/types"
"os"
"testing"
)
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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.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 []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.annotation")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
notes := vmInfo.Config.Annotation
if notes != "test" {
t.Errorf("notest should be 'test'")
}
return nil
}
}
func TestCloneBuilderAcc_windows(t *testing.T) {
t.Skip("Acceptance tests not configured yet.")
t.Skip("test is too slow")
config := windowsConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
})
}
func windowsConfig() map[string]interface{} {
username := os.Getenv("VSPHERE_USERNAME")
if username == "" {
username = "root"
}
password := os.Getenv("VSPHERE_PASSWORD")
if password == "" {
password = "jetbrains"
}
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": username,
"password": password,
"insecure_connection": true,
"vm_name": commonT.NewVMName(),
"template": "windows",
"host": "esxi-1.vsphere65.test",
"linked_clone": true, // speed up
"communicator": "winrm",
"winrm_username": "jetbrains",
"winrm_password": "jetbrains",
}
return config
}

View File

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

View File

@ -0,0 +1,62 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
package clone
import (
"github.com/hashicorp/packer/builder/vsphere/common"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CloneConfig `mapstructure:",squash"`
common.LocationConfig `mapstructure:",squash"`
common.HardwareConfig `mapstructure:",squash"`
common.ConfigParamsConfig `mapstructure:",squash"`
common.RunConfig `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"`
ctx interpolate.Context
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
return nil, err
}
errs := new(packer.MultiError)
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.CloneConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
if len(errs.Errors) > 0 {
return nil, errs
}
return nil, nil
}

View File

@ -0,0 +1,192 @@
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package clone
import (
"github.com/hashicorp/hcl/v2/hcldec"
"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"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username"`
Password *string `mapstructure:"password" cty:"password"`
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
Template *string `mapstructure:"template" cty:"template"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone"`
Network *string `mapstructure:"network" cty:"network"`
Notes *string `mapstructure:"notes" cty:"notes"`
VMName *string `mapstructure:"vm_name" cty:"vm_name"`
Folder *string `mapstructure:"folder" cty:"folder"`
Cluster *string `mapstructure:"cluster" cty:"cluster"`
Host *string `mapstructure:"host" cty:"host"`
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
Datastore *string `mapstructure:"datastore" cty:"datastore"`
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs"`
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters"`
BootOrder *string `mapstructure:"boot_order" cty:"boot_order"`
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout"`
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
Type *string `mapstructure:"communicator" cty:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"`
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
}
// 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_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.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(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},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
"folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false},
"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},
"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},
"NestedHV": &hcldec.AttrSpec{Name: "NestedHV", Type: cty.Bool, Required: false},
"configuration_parameters": &hcldec.BlockAttrsSpec{TypeName: "configuration_parameters", ElementType: cty.String, Required: false},
"boot_order": &hcldec.AttrSpec{Name: "boot_order", 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},
"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},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_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_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_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_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_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},
"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},
}
return s
}

View File

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

View File

@ -0,0 +1,124 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CloneConfig
package clone
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
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 network VM will be connected to.
Network string `mapstructure:"network"`
// VM notes.
Notes string `mapstructure:"notes"`
}
func (c *CloneConfig) Prepare() []error {
var errs []error
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"))
}
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").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
vm, err := d.FindVM(s.Location.VMName)
if s.Force == false && err == nil {
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it", s.Location.VMName))
return multistep.ActionHalt
} else if s.Force == true && err == nil {
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", s.Location.VMName))
err := vm.Destroy()
if err != nil {
state.Put("error", fmt.Errorf("error destroying %s: %v", s.Location.VMName, err))
}
}
ui.Say("Cloning VM...")
template, err := d.FindVM(s.Config.Template)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
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,
Annotation: s.Config.Notes,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if vm == nil {
return multistep.ActionHalt
}
state.Put("vm", vm)
if s.Config.DiskSize > 0 {
err = vm.ResizeDisk(s.Config.DiskSize)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
st := state.Get("vm")
if st == nil {
return
}
vm := st.(*driver.VirtualMachine)
ui.Say("Destroying VM...")
err := vm.Destroy()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -0,0 +1,38 @@
// Code generated by "mapstructure-to-hcl2 -type CloneConfig"; DO NOT EDIT.
package clone
import (
"github.com/hashicorp/hcl/v2/hcldec"
"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"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone"`
Network *string `mapstructure:"network" cty:"network"`
Notes *string `mapstructure:"notes" cty:"notes"`
}
// 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},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
}
return s
}

View File

@ -0,0 +1,36 @@
package common
import (
"github.com/hashicorp/packer/builder/vsphere/driver"
)
const BuilderId = "jetbrains.vsphere"
type Artifact struct {
Name string
VM *driver.VirtualMachine
}
func (a *Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
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 nil
}
func (a *Artifact) Destroy() error {
return a.VM.Destroy()
}

View File

@ -0,0 +1,39 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type LocationConfig
package common
import "fmt"
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
// [Working with Clusters](#working-with-clusters).
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
// `Specifying Clusters and Hosts` section above for more details.
Host string `mapstructure:"host"`
// VMWare resource pool. Defaults to the root resource pool of the
// `host` or `cluster`.
ResourcePool string `mapstructure:"resource_pool"`
// VMWare datastore. Required if `host` is a cluster, or if `host` has
// multiple datastores.
Datastore string `mapstructure:"datastore"`
}
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"))
}
return errs
}

View File

@ -0,0 +1,40 @@
// 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"`
Folder *string `mapstructure:"folder" cty:"folder"`
Cluster *string `mapstructure:"cluster" cty:"cluster"`
Host *string `mapstructure:"host" cty:"host"`
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
Datastore *string `mapstructure:"datastore" cty:"datastore"`
}
// 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},
}
return s
}

View File

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

View File

@ -0,0 +1,38 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ConfigParamsConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type ConfigParamsConfig struct {
// Custom parameters.
ConfigParams map[string]string `mapstructure:"configuration_parameters"`
}
type StepConfigParams struct {
Config *ConfigParamsConfig
}
func (s *StepConfigParams) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.ConfigParams != nil {
ui.Say("Adding configuration parameters...")
if err := vm.AddConfigParams(s.Config.ConfigParams); err != nil {
state.Put("error", fmt.Errorf("error adding configuration parameters: %v", err))
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConfigParams) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,30 @@
// 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"`
}
// 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.BlockAttrsSpec{TypeName: "configuration_parameters", ElementType: cty.String, Required: false},
}
return s
}

View File

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

View File

@ -0,0 +1,38 @@
// 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"`
Username *string `mapstructure:"username" cty:"username"`
Password *string `mapstructure:"password" cty:"password"`
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
}
// FlatMapstructure returns a new FlatConnectConfig.
// FlatConnectConfig is an auto-generated flat version of ConnectConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*ConnectConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConnectConfig)
}
// HCL2Spec returns the hcl spec of a ConnectConfig.
// This spec is used by HCL to read the fields of ConnectConfig.
// The decoded values from this spec will then be applied to a FlatConnectConfig.
func (*FlatConnectConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"vcenter_server": &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"insecure_connection": &hcldec.AttrSpec{Name: "insecure_connection", Type: cty.Bool, Required: false},
"datacenter": &hcldec.AttrSpec{Name: "datacenter", Type: cty.String, Required: false},
}
return s
}

View File

@ -0,0 +1,83 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type HardwareConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type HardwareConfig struct {
// Number of CPU sockets.
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 MB.
VideoRAM int64 `mapstructure:"video_ram"`
// Enable nested hardware virtualization for VM. Defaults to `false`.
NestedHV bool `mapstructure:"NestedHV"`
}
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"))
}
return errs
}
type StepConfigureHardware struct {
Config *HardwareConfig
}
func (s *StepConfigureHardware) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.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,
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepConfigureHardware) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,50 @@
// 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"`
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
}
// 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},
"NestedHV": &hcldec.AttrSpec{Name: "NestedHV", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -0,0 +1,79 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type RunConfig
package common
import (
"context"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"strings"
)
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").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
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").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.BootOrder == "" && s.SetOrder {
ui.Say("Clear boot order...")
if err := vm.SetBootOrder([]string{"-"}); err != nil {
state.Put("error", err)
return
}
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui.Say("Power off VM...")
err := vm.PowerOff()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -0,0 +1,30 @@
// 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"`
}
// FlatMapstructure returns a new FlatRunConfig.
// FlatRunConfig is an auto-generated flat version of RunConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*RunConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatRunConfig)
}
// HCL2Spec returns the hcl spec of a RunConfig.
// This spec is used by HCL to read the fields of RunConfig.
// The decoded values from this spec will then be applied to a FlatRunConfig.
func (*FlatRunConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.String, Required: false},
}
return s
}

View File

@ -0,0 +1,80 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type ShutdownConfig
package common
import (
"bytes"
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"log"
"time"
)
type ShutdownConfig struct {
// Specify a VM guest shutdown command. VMware guest tools are used by
// default.
Command string `mapstructure:"shutdown_command"`
// Amount of time to wait for graceful VM shutdown. Examples 45s and 10m.
// Defaults to 5m(5 minutes).
Timeout time.Duration `mapstructure:"shutdown_timeout"`
}
func (c *ShutdownConfig) Prepare() []error {
var errs []error
if c.Timeout == 0 {
c.Timeout = 5 * time.Minute
}
return errs
}
type StepShutdown struct {
Config *ShutdownConfig
}
func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.Command != "" {
ui.Say("Executing shutdown command...")
log.Printf("Shutdown command: %s", s.Config.Command)
var stdout, stderr bytes.Buffer
cmd := &packer.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("Shut down VM...")
err := vm.StartShutdown()
if err != nil {
state.Put("error", fmt.Errorf("Cannot shut down VM: %v", err))
return multistep.ActionHalt
}
}
log.Printf("Waiting max %s for shutdown to complete", s.Config.Timeout)
err := vm.WaitForShutdown(ctx, s.Config.Timeout)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,32 @@
// 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"`
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
}
// 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},
}
return s
}

View File

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

View File

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

View File

@ -0,0 +1,142 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type WaitIpConfig
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"log"
"time"
)
type WaitIpConfig struct {
// Amount of time to wait for VM's IP, similar to 'ssh_timeout'.
// Defaults to 30m (30 minutes). See the Goang
// [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"`
// 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
}
return errs
}
func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
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:
err := fmt.Errorf("Timeout waiting for IP.")
state.Put("error", err)
ui.Error(err.Error())
cancel()
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.VirtualMachine, 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)
if err != nil {
return "", err
}
if prevIp == "" || prevIp != ip {
if prevIp == "" {
log.Printf("VM IP aquired: %s", ip)
} else {
log.Printf("VM IP changed from %s to %s", prevIp, ip)
}
prevIp = ip
stopTime = time.Now().Add(c.SettleTimeout)
goto loop
} else {
log.Printf("VM IP is still the same: %s", prevIp)
if time.Now().After(stopTime) {
log.Printf("VM IP seems stable enough: %s", ip)
return ip, nil
}
select {
case <-ctx.Done():
return "", fmt.Errorf("IP wait cancelled")
case <-time.After(interval):
goto loop
}
}
}
func (s *StepWaitForIp) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,32 @@
// 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"`
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
}
// 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},
}
return s
}

View File

@ -0,0 +1,68 @@
package testing
import (
"encoding/json"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/packer"
"math/rand"
"os"
"testing"
"time"
)
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 []packer.Artifact) *driver.VirtualMachine {
artifactRaw := artifacts[0]
artifact, _ := artifactRaw.(*common.Artifact)
vm, err := d.FindVM(artifact.Name)
if err != nil {
t.Fatalf("Cannot find VM: %v", err)
}
return vm
}

View File

@ -0,0 +1,129 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Datastore struct {
ds *object.Datastore
driver *Driver
}
func (d *Driver) NewDatastore(ref *types.ManagedObjectReference) *Datastore {
return &Datastore{
ds: object.NewDatastore(d.client.Client, *ref),
driver: d,
}
}
// If name is an empty string, then resolve host's one
func (d *Driver) FindDatastore(name string, host string) (*Datastore, error) {
if name == "" {
h, err := d.FindHost(host)
if err != nil {
return nil, err
}
i, err := h.Info("datastore")
if err != nil {
return nil, 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, err
}
name = inf.Name
}
ds, err := d.finder.Datastore(d.ctx, name)
if err != nil {
return nil, err
}
return &Datastore{
ds: ds,
driver: d,
}, nil
}
func (ds *Datastore) 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 *Datastore) FileExists(path string) bool {
_, err := ds.ds.Stat(ds.driver.ctx, path)
return err == nil
}
func (ds *Datastore) Name() string {
return ds.ds.Name()
}
func (ds *Datastore) ResolvePath(path string) string {
return ds.ds.Path(path)
}
func (ds *Datastore) UploadFile(src, dst string, host string) error {
p := soap.DefaultUpload
ctx := ds.driver.ctx
if 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 *Datastore) 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 *Datastore) 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
}
}

View File

@ -0,0 +1,96 @@
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)
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)
if err != nil {
t.Fatalf("Cannot upload file: %v", err)
}
if ds.FileExists(fileName) != true {
t.Fatalf("Cannot find file")
}
err = ds.Delete(fileName)
if err != nil {
t.Fatalf("Cannot delete file: %v", err)
}
}

View File

@ -0,0 +1,72 @@
package driver
import (
"context"
"fmt"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
"net/url"
"time"
)
type Driver struct {
ctx context.Context
client *govmomi.Client
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 := Driver{
ctx: ctx,
client: client,
datacenter: datacenter,
finder: finder,
}
return &d, nil
}

View File

@ -0,0 +1,39 @@
package driver
import (
"fmt"
"math/rand"
"os"
"testing"
"time"
)
// 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))
}

View File

@ -0,0 +1,67 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Folder struct {
driver *Driver
folder *object.Folder
}
func (d *Driver) NewFolder(ref *types.ManagedObjectReference) *Folder {
return &Folder{
folder: object.NewFolder(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindFolder(name string) (*Folder, error) {
f, err := d.finder.Folder(d.ctx, fmt.Sprintf("/%v/vm/%v", d.datacenter.Name(), name))
if err != nil {
return nil, err
}
return &Folder{
folder: f,
driver: d,
}, nil
}
func (f *Folder) Info(params ...string) (*mo.Folder, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.Folder
err := f.folder.Properties(f.driver.ctx, f.folder.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (f *Folder) Path() (string, error) {
info, err := f.Info("name", "parent")
if err != nil {
return "", err
}
if info.Parent.Type == "Datacenter" {
return "", nil
} else {
parent := f.driver.NewFolder(info.Parent)
path, err := parent.Path()
if err != nil {
return "", err
}
if path == "" {
return info.Name, nil
} else {
return fmt.Sprintf("%v/%v", path, info.Name), nil
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,45 @@
package driver
import (
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type Network struct {
driver *Driver
network *object.Network
}
func (d *Driver) NewNetwork(ref *types.ManagedObjectReference) *Network {
return &Network{
network: object.NewNetwork(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindNetwork(name string) (*Network, error) {
n, err := d.finder.Network(d.ctx, name)
if err != nil {
return nil, err
}
return &Network{
network: n.(*object.Network),
driver: d,
}, 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
err := n.network.Properties(n.driver.ctx, n.network.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@ -0,0 +1,74 @@
package driver
import (
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type ResourcePool struct {
pool *object.ResourcePool
driver *Driver
}
func (d *Driver) NewResourcePool(ref *types.ManagedObjectReference) *ResourcePool {
return &ResourcePool{
pool: object.NewResourcePool(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindResourcePool(cluster string, host string, name string) (*ResourcePool, error) {
var res string
if cluster != "" {
res = cluster
} else {
res = host
}
p, err := d.finder.ResourcePool(d.ctx, fmt.Sprintf("%v/Resources/%v", res, name))
if err != nil {
return nil, err
}
return &ResourcePool{
pool: p,
driver: d,
}, nil
}
func (p *ResourcePool) Info(params ...string) (*mo.ResourcePool, error) {
var params2 []string
if len(params) == 0 {
params2 = []string{"*"}
} else {
params2 = params
}
var info mo.ResourcePool
err := p.pool.Properties(p.driver.ctx, p.pool.Reference(), params2, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (p *ResourcePool) Path() (string, error) {
poolInfo, err := p.Info("name", "parent")
if err != nil {
return "", err
}
if poolInfo.Parent.Type == "ComputeResource" {
return "", nil
} else {
parent := p.driver.NewResourcePool(poolInfo.Parent)
parentPath, err := parent.Path()
if err != nil {
return "", err
}
if parentPath == "" {
return poolInfo.Name, nil
} else {
return fmt.Sprintf("%v/%v", parentPath, poolInfo.Name), nil
}
}
}

View File

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

View File

@ -0,0 +1,665 @@
package driver
import (
"context"
"errors"
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"log"
"strings"
"time"
)
type VirtualMachine struct {
vm *object.VirtualMachine
driver *Driver
}
type CloneConfig struct {
Name string
Folder string
Cluster string
Host string
ResourcePool string
Datastore string
LinkedClone bool
Network string
Annotation string
}
type HardwareConfig struct {
CPUs int32
CpuCores int32
CPUReservation int64
CPULimit int64
RAM int64
RAMReservation int64
RAMReserveAll bool
NestedHV bool
CpuHotAddEnabled bool
MemoryHotAddEnabled bool
VideoRAM int64
}
type CreateConfig struct {
DiskThinProvisioned bool
DiskControllerType string // example: "scsi", "pvscsi"
DiskSize int64
Annotation string
Name string
Folder string
Cluster string
Host string
ResourcePool string
Datastore string
GuestOS string // example: otherGuest
Network string // "" for default network
NetworkCard string // example: vmxnet3
USBController bool
Version uint // example: 10
Firmware string // efi or bios
}
func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine {
return &VirtualMachine{
vm: object.NewVirtualMachine(d.client.Client, *ref),
driver: d,
}
}
func (d *Driver) FindVM(name string) (*VirtualMachine, error) {
vm, err := d.finder.VirtualMachine(d.ctx, name)
if err != nil {
return nil, err
}
return &VirtualMachine{
vm: vm,
driver: d,
}, nil
}
func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
createSpec := types.VirtualMachineConfigSpec{
Name: config.Name,
Annotation: config.Annotation,
GuestId: config.GuestOS,
}
if config.Version != 0 {
createSpec.Version = fmt.Sprintf("%s%d", "vmx-", config.Version)
}
if config.Firmware != "" {
createSpec.Firmware = config.Firmware
}
folder, err := d.FindFolder(config.Folder)
if err != nil {
return nil, err
}
resourcePool, err := d.FindResourcePool(config.Cluster, config.Host, config.ResourcePool)
if err != nil {
return nil, err
}
var host *object.HostSystem
if config.Cluster != "" && config.Host != "" {
h, err := d.FindHost(config.Host)
if err != nil {
return nil, err
}
host = h.host
}
datastore, err := d.FindDatastore(config.Datastore, config.Host)
if err != nil {
return nil, err
}
devices := object.VirtualDeviceList{}
devices, err = addDisk(d, devices, config)
if err != nil {
return nil, err
}
devices, err = addNetwork(d, devices, config)
if err != nil {
return nil, err
}
if config.USBController {
t := true
usb := &types.VirtualUSBController{
EhciEnabled: &t,
}
devices = append(devices, usb)
}
createSpec.DeviceChange, err = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
if err != nil {
return nil, err
}
createSpec.Files = &types.VirtualMachineFileInfo{
VmPathName: fmt.Sprintf("[%s]", datastore.Name()),
}
task, err := folder.folder.CreateVM(d.ctx, createSpec, resourcePool.pool, host)
if err != nil {
return nil, err
}
taskInfo, err := task.WaitForResult(d.ctx, nil)
if err != nil {
return nil, err
}
vmRef := taskInfo.Result.(types.ManagedObjectReference)
return d.NewVM(&vmRef), nil
}
func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) {
var p []string
if len(params) == 0 {
p = []string{"*"}
} else {
p = params
}
var info mo.VirtualMachine
err := vm.vm.Properties(vm.driver.ctx, vm.vm.Reference(), p, &info)
if err != nil {
return nil, err
}
return &info, nil
}
func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) {
vmInfo, err := vm.Info("config.hardware.device")
if err != nil {
return nil, err
}
return vmInfo.Config.Hardware.Device, nil
}
func (vm *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*VirtualMachine, error) {
folder, err := vm.driver.FindFolder(config.Folder)
if err != nil {
return nil, err
}
var relocateSpec types.VirtualMachineRelocateSpec
pool, err := vm.driver.FindResourcePool(config.Cluster, config.Host, config.ResourcePool)
if err != nil {
return nil, err
}
poolRef := pool.pool.Reference()
relocateSpec.Pool = &poolRef
datastore, err := vm.driver.FindDatastore(config.Datastore, config.Host)
if err != nil {
return nil, err
}
datastoreRef := datastore.ds.Reference()
relocateSpec.Datastore = &datastoreRef
var cloneSpec types.VirtualMachineCloneSpec
cloneSpec.Location = relocateSpec
cloneSpec.PowerOn = false
if config.LinkedClone == true {
cloneSpec.Location.DiskMoveType = "createNewChildDiskBacking"
tpl, err := vm.Info("snapshot")
if err != nil {
return nil, err
}
if tpl.Snapshot == nil {
err = errors.New("`linked_clone=true`, but template has no snapshots")
return nil, err
}
cloneSpec.Snapshot = tpl.Snapshot.CurrentSnapshot
}
var configSpec types.VirtualMachineConfigSpec
cloneSpec.Config = &configSpec
if config.Annotation != "" {
configSpec.Annotation = config.Annotation
}
if config.Network != "" {
net, err := vm.driver.FindNetwork(config.Network)
if err != nil {
return nil, err
}
backing, err := net.network.EthernetCardBackingInfo(ctx)
if err != nil {
return nil, err
}
devices, err := vm.vm.Device(ctx)
if err != nil {
return nil, err
}
adapter, err := findNetworkAdapter(devices)
if err != nil {
return nil, err
}
adapter.GetVirtualEthernetCard().Backing = backing
config := &types.VirtualDeviceConfigSpec{
Device: adapter.(types.BaseVirtualDevice),
Operation: types.VirtualDeviceConfigSpecOperationEdit,
}
configSpec.DeviceChange = append(configSpec.DeviceChange, config)
}
task, err := vm.vm.Clone(vm.driver.ctx, folder.folder, config.Name, cloneSpec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(ctx, nil)
if err != nil {
if ctx.Err() == context.Canceled {
err = task.Cancel(context.TODO())
return nil, err
}
return nil, err
}
vmRef := info.Result.(types.ManagedObjectReference)
created := vm.driver.NewVM(&vmRef)
return created, nil
}
func (vm *VirtualMachine) Destroy() error {
task, err := vm.vm.Destroy(vm.driver.ctx)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
var confSpec types.VirtualMachineConfigSpec
confSpec.NumCPUs = config.CPUs
confSpec.NumCoresPerSocket = config.CpuCores
confSpec.MemoryMB = config.RAM
var cpuSpec types.ResourceAllocationInfo
cpuSpec.Reservation = &config.CPUReservation
if config.CPULimit != 0 {
cpuSpec.Limit = &config.CPULimit
}
confSpec.CpuAllocation = &cpuSpec
var ramSpec types.ResourceAllocationInfo
ramSpec.Reservation = &config.RAMReservation
confSpec.MemoryAllocation = &ramSpec
confSpec.MemoryReservationLockedToMax = &config.RAMReserveAll
confSpec.NestedHVEnabled = &config.NestedHV
confSpec.CpuHotAddEnabled = &config.CpuHotAddEnabled
confSpec.MemoryHotAddEnabled = &config.MemoryHotAddEnabled
if config.VideoRAM != 0 {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
l := devices.SelectByType((*types.VirtualMachineVideoCard)(nil))
if len(l) != 1 {
return err
}
card := l[0].(*types.VirtualMachineVideoCard)
card.VideoRamSizeInKB = config.VideoRAM
spec := &types.VirtualDeviceConfigSpec{
Device: card,
Operation: types.VirtualDeviceConfigSpecOperationEdit,
}
confSpec.DeviceChange = append(confSpec.DeviceChange, spec)
}
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) ResizeDisk(diskSize int64) error {
var confSpec types.VirtualMachineConfigSpec
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
disk, err := findDisk(devices)
if err != nil {
return err
}
disk.CapacityInKB = diskSize * 1024
confSpec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{
&types.VirtualDeviceConfigSpec{
Device: disk,
Operation: types.VirtualDeviceConfigSpecOperationEdit,
},
}
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
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")
}
func (vm *VirtualMachine) PowerOn() error {
task, err := vm.vm.PowerOn(vm.driver.ctx)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) WaitForIP(ctx context.Context) (string, error) {
return vm.vm.WaitForIP(ctx)
}
func (vm *VirtualMachine) PowerOff() error {
state, err := vm.vm.PowerState(vm.driver.ctx)
if err != nil {
return err
}
if state == types.VirtualMachinePowerStatePoweredOff {
return nil
}
task, err := vm.vm.PowerOff(vm.driver.ctx)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) StartShutdown() error {
err := vm.vm.ShutdownGuest(vm.driver.ctx)
return err
}
func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
shutdownTimer := time.After(timeout)
for {
powerState, err := vm.vm.PowerState(vm.driver.ctx)
if err != nil {
return err
}
if powerState == "poweredOff" {
break
}
select {
case <-shutdownTimer:
err := errors.New("Timeout while waiting for machine to shut down.")
return err
case <-ctx.Done():
return nil
default:
time.Sleep(1 * time.Second)
}
}
return nil
}
func (vm *VirtualMachine) CreateSnapshot(name string) error {
task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) ConvertToTemplate() error {
return vm.vm.MarkAsTemplate(vm.driver.ctx)
}
func (vm *VirtualMachine) GetDir() (string, error) {
vmInfo, err := vm.Info("name", "layoutEx.file")
if err != nil {
return "", err
}
vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name)
for _, file := range vmInfo.LayoutEx.File {
if strings.HasSuffix(file.Name, vmxName) {
return RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil
}
}
return "", fmt.Errorf("cannot find '%s'", vmxName)
}
func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
device, err := devices.CreateSCSIController(config.DiskControllerType)
if err != nil {
return nil, err
}
devices = append(devices, device)
controller, err := devices.FindDiskController(devices.Name(device))
if err != nil {
return nil, err
}
disk := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Key: devices.NewKey(),
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(config.DiskThinProvisioned),
},
},
CapacityInKB: config.DiskSize * 1024,
}
devices.AssignController(disk, controller)
devices = append(devices, disk)
return devices, nil
}
func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
var network object.NetworkReference
if config.Network == "" {
h, err := d.FindHost(config.Host)
if err != nil {
return nil, err
}
i, err := h.Info("network")
if err != nil {
return nil, err
}
if len(i.Network) > 1 {
return nil, fmt.Errorf("Host has multiple networks. Specify it explicitly")
}
network = object.NewNetwork(d.client.Client, i.Network[0])
} else {
var err error
network, err = d.finder.Network(d.ctx, config.Network)
if err != nil {
return nil, err
}
}
backing, err := network.EthernetCardBackingInfo(d.ctx)
if err != nil {
return nil, err
}
device, err := object.EthernetCardTypes().CreateEthernetCard(config.NetworkCard, backing)
if err != nil {
return nil, err
}
return append(devices, device), nil
}
func (vm *VirtualMachine) AddCdrom(controllerType string, isoPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
var controller *types.VirtualController
if controllerType == "sata" {
c, err := vm.FindSATAController()
if err != nil {
return err
}
controller = c.GetVirtualController()
} else {
c, err := devices.FindIDEController("")
if err != nil {
return err
}
controller = c.GetVirtualController()
}
cdrom, err := vm.CreateCdrom(controller)
if err != nil {
return err
}
if isoPath != "" {
devices.InsertIso(cdrom, isoPath)
}
log.Printf("Creating CD-ROM on controller '%v' with iso '%v'", controller, isoPath)
return vm.addDevice(cdrom)
}
func (vm *VirtualMachine) AddFloppy(imgPath string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
floppy, err := devices.CreateFloppy()
if err != nil {
return err
}
if imgPath != "" {
floppy = devices.InsertImg(floppy, imgPath)
}
return vm.addDevice(floppy)
}
func (vm *VirtualMachine) SetBootOrder(order []string) error {
devices, err := vm.vm.Device(vm.driver.ctx)
if err != nil {
return err
}
bootOptions := types.VirtualMachineBootOptions{
BootOrder: devices.BootOrder(order),
}
return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions)
}
func (vm *VirtualMachine) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...)
}
func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error {
newDevices := object.VirtualDeviceList{device}
confSpec := types.VirtualMachineConfigSpec{}
var err error
confSpec.DeviceChange, err = newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
if err != nil {
return err
}
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func (vm *VirtualMachine) AddConfigParams(params map[string]string) error {
var confSpec types.VirtualMachineConfigSpec
var ov []types.BaseOptionValue
for k, v := range params {
o := types.OptionValue{
Key: k,
Value: v,
}
ov = append(ov, &o)
}
confSpec.ExtraConfig = ov
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
if err != nil {
return err
}
_, err = task.WaitForResult(vm.driver.ctx, nil)
return err
}
func findNetworkAdapter(l object.VirtualDeviceList) (types.BaseVirtualEthernetCard, error) {
c := l.SelectByType((*types.VirtualEthernetCard)(nil))
if len(c) == 0 {
return nil, errors.New("no network adapter device found")
}
return c[0].(types.BaseVirtualEthernetCard), nil
}

View File

@ -0,0 +1,71 @@
package driver
import (
"errors"
"github.com/vmware/govmomi/vim25/types"
)
var (
ErrNoSataController = errors.New("no available SATA controller")
)
func (vm *VirtualMachine) AddSATAController() error {
sata := &types.VirtualAHCIController{}
return vm.addDevice(sata)
}
func (vm *VirtualMachine) 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 *VirtualMachine) 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 *VirtualMachine) EjectCdroms() error {
devices, err := vm.Devices()
if err != nil {
return err
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
for _, cd := range cdroms {
c := cd.(*types.VirtualCdrom)
c.Backing = &types.VirtualCdromRemotePassthroughBackingInfo{}
c.Connectable = &types.VirtualDeviceConnectInfo{}
err := vm.vm.EditDevice(vm.driver.ctx, c)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,312 @@
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.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.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.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()); {
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.driver.FindVM(vmName); err == nil {
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName)
}
}

View File

@ -0,0 +1,92 @@
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.driver
// Check that the clone can be found by its name
if _, err := d.FindVM(config.Name); err != nil {
t.Errorf("Cannot find created vm '%v': %v", config.Name, err)
}
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
if vmInfo.Name != config.Name {
t.Errorf("Invalid VM name: expected '%v', got '%v'", config.Name, vmInfo.Name)
}
f := d.NewFolder(vmInfo.Parent)
folderPath, err := f.Path()
if err != nil {
t.Fatalf("Cannot read folder name: %v", err)
}
if folderPath != "" {
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
}
h := d.NewHost(vmInfo.Runtime.Host)
hostInfo, err := h.Info("name")
if err != nil {
t.Fatal("Cannot read host properties: ", err)
}
if hostInfo.Name != TestHostName {
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
}
p := d.NewResourcePool(vmInfo.ResourcePool)
poolPath, err := p.Path()
if err != nil {
t.Fatalf("Cannot read resource pool name: %v", err)
}
if poolPath != "" {
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
}
dsr := vmInfo.Datastore[0].Reference()
ds := d.NewDatastore(&dsr)
dsInfo, err := ds.Info("name")
if err != nil {
t.Fatal("Cannot read datastore properties: ", err)
}
if dsInfo.Name != "datastore1" {
t.Errorf("Invalid datastore name: expected 'datastore1', got '%v'", dsInfo.Name)
}
}

View File

@ -0,0 +1,82 @@
package driver
import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/mobile/event/key"
"strings"
"unicode"
)
type KeyInput struct {
Message string
Scancode key.Code
Alt bool
Ctrl bool
Shift bool
}
var scancodeMap = make(map[rune]key.Code)
func init() {
scancodeIndex := make(map[string]key.Code)
scancodeIndex["abcdefghijklmnopqrstuvwxyz"] = key.CodeA
scancodeIndex["ABCDEFGHIJKLMNOPQRSTUVWXYZ"] = key.CodeA
scancodeIndex["1234567890"] = key.Code1
scancodeIndex["!@#$%^&*()"] = key.Code1
scancodeIndex[" "] = key.CodeSpacebar
scancodeIndex["-=[]\\"] = key.CodeHyphenMinus
scancodeIndex["_+{}|"] = key.CodeHyphenMinus
scancodeIndex[";'`,./"] = key.CodeSemicolon
scancodeIndex[":\"~<>?"] = key.CodeSemicolon
for chars, start := range scancodeIndex {
for i, r := range chars {
scancodeMap[r] = start + key.Code(i)
}
}
}
const shiftedChars = "!@#$%^&*()_+{}|:\"~<>?"
func (vm *VirtualMachine) TypeOnKeyboard(input KeyInput) (int32, error) {
var spec types.UsbScanCodeSpec
for _, r := range input.Message {
scancode := scancodeMap[r]
shift := input.Shift || unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
// https://github.com/lamw/vghetto-scripts/blob/f74bc8ba20064f46592bcce5a873b161a7fa3d72/powershell/VMKeystrokes.ps1#L130
UsbHidCode: int32(scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &shift,
},
})
}
if input.Scancode != 0 {
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
UsbHidCode: int32(input.Scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &input.Shift,
},
})
}
req := &types.PutUsbScanCodes{
This: vm.vm.Reference(),
Spec: spec,
}
resp, err := methods.PutUsbScanCodes(vm.driver.ctx, vm.driver.client.RoundTripper, req)
if err != nil {
return 0, err
}
return resp.Returnval, nil
}

View File

@ -0,0 +1,62 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": true,
"vm_name": "alpine-{{timestamp}}",
"host": "esxi-1.vsphere65.test",
"CPUs": 1,
"RAM": 512,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 1024,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"guest_os_type": "other3xLinux64Guest",
"iso_paths": [
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"
],
"floppy_files": [
"{{template_dir}}/answerfile",
"{{template_dir}}/setup.sh"
],
"boot_wait": "15s",
"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>"
],
"ssh_username": "root",
"ssh_password": "jetbrains"
}
],
"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,51 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "macos-packer",
"host": "esxi-mac.vsphere65.test",
"guest_os_type": "darwin16_64Guest",
"CPUs": 1,
"RAM": 4096,
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "e1000e",
"usb_controller": true,
"configuration_parameters": {
"ich7m.present": "TRUE",
"smc.present": "TRUE"
},
"cdrom_type": "sata",
"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"],
"iso_checksum_type": "sha256",
"iso_checksum_url": "file:///{{template_dir}}/setup/out/sha256sums",
"boot_wait": "4m",
"boot_command": [
"<enter><wait5>",
"<leftCtrlOn><f2><leftCtrlOff>u<enter>t<enter><wait5>",
"/Volumes/setup/setup.sh<enter>"
],
"ssh_username": "jetbrains",
"ssh_password": "jetbrains"
}
]
}

View File

@ -0,0 +1 @@
out/

View File

@ -0,0 +1,15 @@
#!/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

View File

@ -0,0 +1,27 @@
#!/bin/sh
set -eux
mkdir -p out/pkgroot
rm -rf /out/pkgroot/*
mkdir -p out/scripts
rm -rf /out/scripts/*
cp postinstall out/scripts/
pkgbuild \
--identifier io.packer.install \
--root out/pkgroot \
--scripts out/scripts \
out/postinstall.pkg
mkdir -p out/iso
rm -rf out/iso/*
cp setup.sh out/iso/
chmod +x out/iso/setup.sh
productbuild --package out/postinstall.pkg out/iso/postinstall.pkg
rm -f out/setup.iso
hdiutil makehybrid -iso -joliet -default-volume-name setup -o out/setup.iso out/iso
cd out
shasum -a 256 setup.iso >sha256sums

View File

@ -0,0 +1,25 @@
#!/bin/sh
set -eux
# debug output in /var/log/install.log
# Create user account
USERNAME=jetbrains
PASSWORD=jetbrains
dscl . -create "/Users/${USERNAME}"
dscl . -create "/Users/${USERNAME}" UserShell /bin/bash
dscl . -create "/Users/${USERNAME}" RealName "${USERNAME}"
dscl . -create "/Users/${USERNAME}" UniqueID 510
dscl . -create "/Users/${USERNAME}" PrimaryGroupID 20
dscl . -create "/Users/${USERNAME}" NFSHomeDirectory "/Users/${USERNAME}"
dscl . -passwd "/Users/${USERNAME}" "${PASSWORD}"
dscl . -append /Groups/admin GroupMembership "${USERNAME}"
createhomedir -c
# Start VMware Tools daemon explicitly
launchctl load /Library/LaunchDaemons/com.vmware.launchd.tools.plist
# Enable SSH
systemsetup -setremotelogin on
# Disable the welcome screen
touch "$3/private/var/db/.AppleSetupDone"

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -eux
# Format partition
diskutil eraseDisk JHFS+ Disk disk0
# Packages are installed in reversed order - why?
"/Volumes/Image Volume/Install macOS High Sierra.app/Contents/Resources/startosinstall" \
--volume /Volumes/Disk \
--converttoapfs no \
--agreetolicense \
--installpackage "/Volumes/setup/postinstall.pkg" \
--installpackage "/Volumes/VMware Tools/Install VMware Tools.app/Contents/Resources/VMware Tools.pkg"

View File

@ -0,0 +1,16 @@
d-i passwd/user-fullname string jetbrains
d-i passwd/username string jetbrains
d-i passwd/user-password password jetbrains
d-i passwd/user-password-again password jetbrains
d-i user-setup/allow-password-weak boolean true
d-i partman-auto/disk string /dev/sda
d-i partman-auto/method string regular
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i pkgsel/include string open-vm-tools openssh-server
d-i finish-install/reboot_in_progress note

View File

@ -0,0 +1,62 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "example-ubuntu",
"host": "esxi-1.vsphere65.test",
"guest_os_type": "ubuntu64Guest",
"ssh_username": "jetbrains",
"ssh_password": "jetbrains",
"CPUs": 1,
"RAM": 1024,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"iso_paths": [
"[datastore1] ISO/ubuntu-16.04.3-server-amd64.iso"
],
"floppy_files": [
"{{template_dir}}/preseed.cfg"
],
"boot_command": [
"<enter><wait><f6><wait><esc><wait>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs>",
"/install/vmlinuz",
" initrd=/install/initrd.gz",
" priority=critical",
" locale=en_US",
" file=/media/preseed.cfg",
"<enter>"
]
}
],
"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
}
]
}

View File

@ -0,0 +1,2 @@
*.cmd text eol=crlf
*.ps1 text eol=crlf

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UILanguage>en-US</UILanguage>
</component>
<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="A">
<!-- pvscsi-Windows8.flp -->
<Path>B:\</Path>
</PathAndCredentials>
</DriverPaths>
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserData>
<AcceptEula>true</AcceptEula>
<!-- Retail image requires a key
<ProductKey>
<Key>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</Key>
</ProductKey>
-->
</UserData>
<ImageInstall>
<OSImage>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/NAME</Key>
<Value>Windows 10 Pro</Value>
</MetaData>
</InstallFrom>
<InstallToAvailablePartition>true</InstallToAvailablePartition>
</OSImage>
</ImageInstall>
<DiskConfiguration>
<Disk wcm:action="add">
<DiskID>0</DiskID>
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Extend>true</Extend>
<Type>Primary</Type>
</CreatePartition>
</CreatePartitions>
</Disk>
</DiskConfiguration>
</component>
</settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- Disable user account control -->
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<!-- Install VMware Tools from windows.iso -->
<Path>a:\vmtools.cmd</Path>
<WillReboot>Always</WillReboot>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SystemLocale>en-US</SystemLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OOBE>
<!-- Privacy settings -->
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
<!--
<TimeZone>Russian Standard Time</TimeZone>
-->
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Name>jetbrains</Name>
<Password>
<Value>jetbrains</Value>
<PlainText>true</PlainText>
</Password>
<Group>Administrators</Group>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<AutoLogon>
<Enabled>true</Enabled>
<Username>jetbrains</Username>
<Password>
<Value>jetbrains</Value>
<PlainText>true</PlainText>
</Password>
<LogonCount>1</LogonCount>
</AutoLogon>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<!-- Enable WinRM service -->
<CommandLine>powershell -ExecutionPolicy Bypass -File a:\setup.ps1</CommandLine>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>

View File

@ -0,0 +1,15 @@
$ErrorActionPreference = "Stop"
# Switch network connection to private mode
# Required for WinRM firewall rules
$profile = Get-NetConnectionProfile
Set-NetConnectionProfile -Name $profile.Name -NetworkCategory Private
# Enable WinRM service
winrm quickconfig -quiet
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
# Reset auto logon count
# https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-autologon-logoncount#logoncount-known-issue
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoLogonCount -Value 0

View File

@ -0,0 +1,2 @@
@rem Silent mode, basic UI, no reboot
e:\setup64 /s /v "/qb REBOOT=R"

View File

@ -0,0 +1,48 @@
{
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "example-windows",
"host": "esxi-1.vsphere65.test",
"guest_os_type": "windows9_64Guest",
"communicator": "winrm",
"winrm_username": "jetbrains",
"winrm_password": "jetbrains",
"CPUs": 1,
"RAM": 4096,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"iso_paths": [
"[datastore1] ISO/en_windows_10_multi-edition_vl_version_1709_updated_dec_2017_x64_dvd_100406172.iso",
"[datastore1] ISO/VMware Tools/10.2.0/windows.iso"
],
"floppy_files": [
"{{template_dir}}/setup/"
],
"floppy_img_path": "[datastore1] ISO/VMware Tools/10.2.0/pvscsi-Windows8.flp"
}
],
"provisioners": [
{
"type": "windows-shell",
"inline": ["dir c:\\"]
}
]
}

View File

@ -0,0 +1,148 @@
package iso
import (
"context"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
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 packer.Ui, hook packer.Hook) (packer.Artifact, error) {
state := new(multistep.BasicStateBag)
state.Put("hook", hook)
state.Put("ui", ui)
var steps []multistep.Step
steps = append(steps,
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
)
if b.config.ISOUrls != nil {
steps = append(steps,
&packerCommon.StepDownload{
Checksum: b.config.ISOChecksum,
ChecksumType: b.config.ISOChecksumType,
Description: "ISO",
Extension: b.config.TargetExtension,
ResultKey: "iso_path",
TargetPath: b.config.TargetPath,
Url: b.config.ISOUrls,
},
&StepRemoteUpload{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
)
}
steps = append(steps,
&StepCreateVM{
Config: &b.config.CreateConfig,
Location: &b.config.LocationConfig,
Force: b.config.PackerConfig.PackerForce,
},
&common.StepConfigureHardware{
Config: &b.config.HardwareConfig,
},
&StepAddCDRom{
Config: &b.config.CDRomConfig,
},
&common.StepConfigParams{
Config: &b.config.ConfigParamsConfig,
},
)
if b.config.Comm.Type != "none" {
steps = append(steps,
&packerCommon.StepCreateFloppy{
Files: b.config.FloppyFiles,
Directories: b.config.FloppyDirectories,
},
&StepAddFloppy{
Config: &b.config.FloppyConfig,
Datastore: b.config.Datastore,
Host: b.config.Host,
},
&packerCommon.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&common.StepRun{
Config: &b.config.RunConfig,
SetOrder: true,
},
&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.SSHHost),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&packerCommon.StepProvision{},
&common.StepShutdown{
Config: &b.config.ShutdownConfig,
},
&StepRemoveFloppy{
Datastore: b.config.Datastore,
Host: b.config.Host,
},
)
}
steps = append(steps,
&StepRemoveCDRom{},
&common.StepCreateSnapshot{
CreateSnapshot: b.config.CreateSnapshot,
},
&common.StepConvertToTemplate{
ConvertToTemplate: b.config.ConvertToTemplate,
},
)
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
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.VirtualMachine),
}
return artifact, nil
}

View File

@ -0,0 +1,555 @@
package iso
import (
"fmt"
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi/vim25/types"
"io/ioutil"
"os"
"testing"
)
func TestISOBuilderAcc_default(t *testing.T) {
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,
"host": "esxi-1.vsphere65.test",
"ssh_username": "root",
"ssh_password": "jetbrains",
"vm_name": commonT.NewVMName(),
"disk_size": 2048,
"communicator": "none", // do not start the VM without any bootable devices
}
return config
}
func checkDefault(t *testing.T, name string, host string, datastore string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk", "config.firmware")
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)
}
fw := vmInfo.Config.Firmware
if fw != "bios" {
t.Errorf("Invalid firmware: expected 'bios', got '%v'", fw)
}
return nil
}
}
func TestISOBuilderAcc_notes(t *testing.T) {
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 []packer.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("notes should be 'test'")
}
return nil
}
}
func TestISOBuilderAcc_hardware(t *testing.T) {
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["NestedHV"] = true
config["firmware"] = "efi"
config["video_ram"] = 8192
return commonT.RenderConfig(config)
}
func checkHardware(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.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)
}
nestedHV := vmInfo.Config.NestedHVEnabled
if !*nestedHV {
t.Errorf("VM should have NestedHV enabled, got %v", nestedHV)
}
fw := vmInfo.Config.Firmware
if fw != "efi" {
t.Errorf("Invalid firmware: expected 'efi', got '%v'", fw)
}
l, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM devices: %v", err)
}
c := l.PickController((*types.VirtualIDEController)(nil))
if c == nil {
t.Errorf("VM should have IDE controller")
}
s := l.PickController((*types.VirtualAHCIController)(nil))
if s != nil {
t.Errorf("VM should have no SATA controllers")
}
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 TestISOBuilderAcc_limit(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: limitConfig(),
Check: checkLimit(t),
})
}
func limitConfig() string {
config := defaultConfig()
config["CPUs"] = 1 // hardware is customized, but CPU limit is not specified explicitly
return commonT.RenderConfig(config)
}
func checkLimit(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
vmInfo, err := vm.Info("config.cpuAllocation")
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
limit := *vmInfo.Config.CpuAllocation.Limit
if limit != -1 { // must be unlimited
t.Errorf("Invalid CPU limit: expected '%v', got '%v'", -1, limit)
}
return nil
}
}
func TestISOBuilderAcc_sata(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: sataConfig(),
Check: checkSata(t),
})
}
func sataConfig() string {
config := defaultConfig()
config["cdrom_type"] = "sata"
return commonT.RenderConfig(config)
}
func checkSata(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
l, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM devices: %v", err)
}
c := l.PickController((*types.VirtualAHCIController)(nil))
if c == nil {
t.Errorf("VM has no SATA controllers")
}
return nil
}
}
func TestISOBuilderAcc_cdrom(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: cdromConfig(),
})
}
func cdromConfig() string {
config := defaultConfig()
config["iso_paths"] = []string{
"[datastore1] test0.iso",
"[datastore1] test1.iso",
}
return commonT.RenderConfig(config)
}
func TestISOBuilderAcc_networkCard(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: networkCardConfig(),
Check: checkNetworkCard(t),
})
}
func networkCardConfig() string {
config := defaultConfig()
config["network_card"] = "vmxnet3"
return commonT.RenderConfig(config)
}
func checkNetworkCard(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
d := commonT.TestConn(t)
vm := commonT.GetVM(t, d, artifacts)
devices, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read VM properties: %v", err)
}
netCards := devices.SelectByType((*types.VirtualEthernetCard)(nil))
if len(netCards) == 0 {
t.Fatalf("Cannot find the network card")
}
if len(netCards) > 1 {
t.Fatalf("Found several network catds")
}
if _, ok := netCards[0].(*types.VirtualVmxnet3); !ok {
t.Errorf("The network card type is not the expected one (vmxnet3)")
}
return nil
}
}
func TestISOBuilderAcc_createFloppy(t *testing.T) {
tmpFile, err := ioutil.TempFile("", "packer-vsphere-iso-test")
if err != nil {
t.Fatalf("Error creating temp file: %v", err)
}
_, err = fmt.Fprint(tmpFile, "Hello, World!")
if err != nil {
t.Fatalf("Error creating temp file: %v", err)
}
err = tmpFile.Close()
if err != nil {
t.Fatalf("Error creating temp file: %v", err)
}
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: createFloppyConfig(tmpFile.Name()),
})
}
func createFloppyConfig(filePath string) string {
config := defaultConfig()
config["floppy_files"] = []string{filePath}
return commonT.RenderConfig(config)
}
func TestISOBuilderAcc_full(t *testing.T) {
config := fullConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkFull(t),
})
}
func fullConfig() 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(),
"host": "esxi-1.vsphere65.test",
"RAM": 512,
"disk_controller_type": "pvscsi",
"disk_size": 1024,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"guest_os_type": "other3xLinux64Guest",
"iso_paths": []string{
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso",
},
"floppy_files": []string{
"../examples/alpine/answerfile",
"../examples/alpine/setup.sh",
},
"boot_wait": "20s",
"boot_command": []string{
"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><wait10>",
"root<enter>",
"jetbrains<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"/media/floppy/SETUP.SH<enter>",
},
"ssh_username": "root",
"ssh_password": "jetbrains",
}
return config
}
func checkFull(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.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")
}
devices, err := vm.Devices()
if err != nil {
t.Fatalf("Cannot read devices: %v", err)
}
cdroms := devices.SelectByType((*types.VirtualCdrom)(nil))
for _, cd := range cdroms {
_, ok := cd.(*types.VirtualCdrom).Backing.(*types.VirtualCdromRemotePassthroughBackingInfo)
if !ok {
t.Errorf("wrong cdrom backing")
}
}
return nil
}
}
func TestISOBuilderAcc_bootOrder(t *testing.T) {
config := fullConfig()
config["boot_order"] = "disk,cdrom,floppy"
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
Check: checkBootOrder(t),
})
}
func checkBootOrder(t *testing.T) builderT.TestCheckFunc {
return func(artifacts []packer.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 TestISOBuilderAcc_cluster(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: clusterConfig(),
})
}
func clusterConfig() string {
config := defaultConfig()
config["cluster"] = "cluster1"
config["host"] = "esxi-2.vsphere65.test"
return commonT.RenderConfig(config)
}
func TestISOBuilderAcc_clusterDRS(t *testing.T) {
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: clusterDRSConfig(),
})
}
func clusterDRSConfig() string {
config := defaultConfig()
config["cluster"] = "cluster2"
config["host"] = ""
config["datastore"] = "datastore3" // bug #183
config["network"] = "VM Network" // bug #183
return commonT.RenderConfig(config)
}

View File

@ -0,0 +1,81 @@
//go:generate mapstructure-to-hcl2 -type Config
package iso
import (
"github.com/hashicorp/packer/builder/vsphere/common"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
packerCommon.HTTPConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CreateConfig `mapstructure:",squash"`
common.LocationConfig `mapstructure:",squash"`
common.HardwareConfig `mapstructure:",squash"`
common.ConfigParamsConfig `mapstructure:",squash"`
packerCommon.ISOConfig `mapstructure:",squash"`
CDRomConfig `mapstructure:",squash"`
FloppyConfig `mapstructure:",squash"`
common.RunConfig `mapstructure:",squash"`
BootConfig `mapstructure:",squash"`
common.WaitIpConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
CreateSnapshot bool `mapstructure:"create_snapshot"`
ConvertToTemplate bool `mapstructure:"convert_to_template"`
ctx interpolate.Context
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(c, &config.DecodeOpts{
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(packer.MultiError)
if c.ISOUrls != nil {
isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
}
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.CreateConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
if len(errs.Errors) > 0 {
return warnings, errs
}
return warnings, nil
}

View File

@ -0,0 +1,238 @@
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package iso
import (
"github.com/hashicorp/hcl/v2/hcldec"
"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"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max"`
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username"`
Password *string `mapstructure:"password" cty:"password"`
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
Version *uint `mapstructure:"vm_version" cty:"vm_version"`
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type"`
Firmware *string `mapstructure:"firmware" cty:"firmware"`
DiskControllerType *string `mapstructure:"disk_controller_type" cty:"disk_controller_type"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned"`
Network *string `mapstructure:"network" cty:"network"`
NetworkCard *string `mapstructure:"network_card" cty:"network_card"`
USBController *bool `mapstructure:"usb_controller" cty:"usb_controller"`
Notes *string `mapstructure:"notes" cty:"notes"`
VMName *string `mapstructure:"vm_name" cty:"vm_name"`
Folder *string `mapstructure:"folder" cty:"folder"`
Cluster *string `mapstructure:"cluster" cty:"cluster"`
Host *string `mapstructure:"host" cty:"host"`
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
Datastore *string `mapstructure:"datastore" cty:"datastore"`
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs"`
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum"`
ISOChecksumURL *string `mapstructure:"iso_checksum_url" cty:"iso_checksum_url"`
ISOChecksumType *string `mapstructure:"iso_checksum_type" cty:"iso_checksum_type"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension"`
CdromType *string `mapstructure:"cdrom_type" cty:"cdrom_type"`
ISOPaths []string `mapstructure:"iso_paths" cty:"iso_paths"`
FloppyIMGPath *string `mapstructure:"floppy_img_path" cty:"floppy_img_path"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs"`
BootOrder *string `mapstructure:"boot_order" cty:"boot_order"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait"`
HTTPIP *string `mapstructure:"http_ip" cty:"http_ip"`
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout"`
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
Type *string `mapstructure:"communicator" cty:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"`
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
}
// 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_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.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: 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_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},
"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},
"vm_version": &hcldec.AttrSpec{Name: "vm_version", Type: cty.Number, Required: false},
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.String, Required: false},
"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},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"network_card": &hcldec.AttrSpec{Name: "network_card", Type: cty.String, Required: false},
"usb_controller": &hcldec.AttrSpec{Name: "usb_controller", Type: cty.Bool, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
"folder": &hcldec.AttrSpec{Name: "folder", Type: cty.String, Required: false},
"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},
"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},
"NestedHV": &hcldec.AttrSpec{Name: "NestedHV", Type: cty.Bool, Required: false},
"configuration_parameters": &hcldec.BlockAttrsSpec{TypeName: "configuration_parameters", ElementType: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_checksum_url": &hcldec.AttrSpec{Name: "iso_checksum_url", Type: cty.String, Required: false},
"iso_checksum_type": &hcldec.AttrSpec{Name: "iso_checksum_type", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, 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},
"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},
"boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: 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},
"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},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_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_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_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_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_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},
"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},
}
return s
}

View File

@ -0,0 +1,69 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CDRomConfig
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type CDRomConfig struct {
// Which controller to use. Example: `sata`. Defaults to `ide`.
CdromType string `mapstructure:"cdrom_type"`
// List of datastore paths to ISO files that will be mounted to the VM.
// Example: `"[datastore1] ISO/ubuntu.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").(packer.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 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
}
}
}
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
}
}
return multistep.ActionContinue
}
func (s *StepAddCDRom) Cleanup(state multistep.StateBag) {}

View File

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

View File

@ -0,0 +1,104 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type FloppyConfig
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
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"`
}
type StepAddFloppy struct {
Config *FloppyConfig
Datastore string
Host string
}
func (s *StepAddFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.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); 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").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
if UploadedFloppyPath, ok := state.GetOk("uploaded_floppy_path"); ok {
ui.Say("Deleting Floppy image ...")
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", err)
return
}
err = ds.Delete(UploadedFloppyPath.(string))
if err != nil {
state.Put("error", err)
return
}
}
}

View File

@ -0,0 +1,34 @@
// Code generated by "mapstructure-to-hcl2 -type FloppyConfig"; DO NOT EDIT.
package iso
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"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs"`
}
// 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},
}
return s
}

View File

@ -0,0 +1,260 @@
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"golang.org/x/mobile/event/key"
"log"
"net"
"os"
"strings"
"time"
"unicode/utf8"
)
type BootConfig struct {
BootCommand []string `mapstructure:"boot_command"`
BootWait time.Duration `mapstructure:"boot_wait"` // example: "1m30s"; default: "10s"
HTTPIP string `mapstructure:"http_ip"`
}
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort int
Name string
}
func (c *BootConfig) Prepare() []error {
var errs []error
if c.BootWait == 0 {
c.BootWait = 10 * time.Second
}
return errs
}
type StepBootCommand struct {
Config *BootConfig
VMName string
Ctx interpolate.Context
}
var special = map[string]key.Code{
"<enter>": key.CodeReturnEnter,
"<esc>": key.CodeEscape,
"<bs>": key.CodeDeleteBackspace,
"<del>": key.CodeDeleteForward,
"<tab>": key.CodeTab,
"<f1>": key.CodeF1,
"<f2>": key.CodeF2,
"<f3>": key.CodeF3,
"<f4>": key.CodeF4,
"<f5>": key.CodeF5,
"<f6>": key.CodeF6,
"<f7>": key.CodeF7,
"<f8>": key.CodeF8,
"<f9>": key.CodeF9,
"<f10>": key.CodeF10,
"<f11>": key.CodeF11,
"<f12>": key.CodeF12,
"<insert>": key.CodeInsert,
"<home>": key.CodeHome,
"<end>": key.CodeEnd,
"<pageUp>": key.CodePageUp,
"<pageDown>": key.CodePageDown,
"<left>": key.CodeLeftArrow,
"<right>": key.CodeRightArrow,
"<up>": key.CodeUpArrow,
"<down>": key.CodeDownArrow,
}
var keyInterval = packerCommon.PackerKeyDefault
func init() {
if delay, err := time.ParseDuration(os.Getenv(packerCommon.PackerKeyEnv)); err == nil {
keyInterval = delay
}
}
func (s *StepBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if s.Config.BootCommand == nil {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait))
wait := time.After(s.Config.BootWait)
WAITLOOP:
for {
select {
case <-wait:
break WAITLOOP
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
}
}
port := state.Get("http_port").(int)
if port > 0 {
ip, err := getHostIP(s.Config.HTTPIP)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
err = packerCommon.SetHTTPIP(ip)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.Ctx.Data = &bootCommandTemplateData{
ip,
port,
s.VMName,
}
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
}
ui.Say("Typing boot command...")
var keyAlt bool
var keyCtrl bool
var keyShift bool
for _, command := range s.Config.BootCommand {
message, err := interpolate.Render(command, &s.Ctx)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
for len(message) > 0 {
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return multistep.ActionHalt
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Waiting 1 second")
time.Sleep(1 * time.Second)
message = message[len("<wait>"):]
continue
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Waiting 5 seconds")
time.Sleep(5 * time.Second)
message = message[len("<wait5>"):]
continue
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Waiting 10 seconds")
time.Sleep(10 * time.Second)
message = message[len("<wait10>"):]
continue
}
if strings.HasPrefix(message, "<leftAltOn>") {
keyAlt = true
message = message[len("<leftAltOn>"):]
continue
}
if strings.HasPrefix(message, "<leftAltOff>") {
keyAlt = false
message = message[len("<leftAltOff>"):]
continue
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
keyCtrl = true
message = message[len("<leftCtrlOn>"):]
continue
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
keyCtrl = false
message = message[len("<leftCtrlOff>"):]
continue
}
if strings.HasPrefix(message, "<leftShiftOn>") {
keyShift = true
message = message[len("<leftShiftOn>"):]
continue
}
if strings.HasPrefix(message, "<leftShiftOff>") {
keyShift = false
message = message[len("<leftShiftOff>"):]
continue
}
var scancode key.Code
for specialCode, specialValue := range special {
if strings.HasPrefix(message, specialCode) {
scancode = specialValue
log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue)
message = message[len(specialCode):]
}
}
var char rune
if scancode == 0 {
var size int
char, size = utf8.DecodeRuneInString(message)
message = message[size:]
}
_, err := vm.TypeOnKeyboard(driver.KeyInput{
Message: string(char),
Scancode: scancode,
Ctrl: keyCtrl,
Alt: keyAlt,
Shift: keyShift,
})
if err != nil {
state.Put("error", fmt.Errorf("error typing a boot command: %v", err))
return multistep.ActionHalt
}
time.Sleep(keyInterval)
}
}
return multistep.ActionContinue
}
func (s *StepBootCommand) Cleanup(state multistep.StateBag) {}
func getHostIP(s string) (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
}
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", fmt.Errorf("IP not found")
}

View File

@ -0,0 +1,133 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type CreateConfig
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/common"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type CreateConfig struct {
// Set VM hardware version. Defaults to the most current VM hardware
// version supported by vCenter. See
// [VMWare article 1003746](https://kb.vmware.com/s/article/1003746) for
// the full list of supported VM hardware versions.
Version uint `mapstructure:"vm_version"`
// Set VM OS type. Defaults to `otherGuest`. See [
// here](https://pubs.vmware.com/vsphere-6-5/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.GuestOsDescriptor.GuestOsIdentifier.html)
// for a full list of possible values.
GuestOSType string `mapstructure:"guest_os_type"`
// Set the Firmware at machine creation. Example `efi`. Defaults to `bios`.
Firmware string `mapstructure:"firmware"`
// Set VM disk controller type. Example `pvscsi`.
DiskControllerType string `mapstructure:"disk_controller_type"`
// The size of the disk in MB.
DiskSize int64 `mapstructure:"disk_size"`
// Enable VMDK thin provisioning for VM. Defaults to `false`.
DiskThinProvisioned bool `mapstructure:"disk_thin_provisioned"`
// Set network VM will be connected to.
Network string `mapstructure:"network"`
// Set VM network card type. Example `vmxnet3`.
NetworkCard string `mapstructure:"network_card"`
// Create USB controller for virtual machine. Defaults to `false`.
USBController bool `mapstructure:"usb_controller"`
// VM notes.
Notes string `mapstructure:"notes"`
}
func (c *CreateConfig) Prepare() []error {
var errs []error
if c.DiskSize == 0 {
errs = append(errs, fmt.Errorf("'disk_size' is required"))
}
if c.GuestOSType == "" {
c.GuestOSType = "otherGuest"
}
if c.Firmware != "" && c.Firmware != "bios" && c.Firmware != "efi" {
errs = append(errs, fmt.Errorf("'firmware' must be 'bios' or 'efi'"))
}
return errs
}
type StepCreateVM struct {
Config *CreateConfig
Location *common.LocationConfig
Force bool
}
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
vm, err := d.FindVM(s.Location.VMName)
if s.Force == false && err == nil {
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it: %v", s.Location.VMName, err))
return multistep.ActionHalt
} else if s.Force == true && err == nil {
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", s.Location.VMName))
err := vm.Destroy()
if err != nil {
state.Put("error", fmt.Errorf("error destroying %s: %v", s.Location.VMName, err))
}
}
ui.Say("Creating VM...")
vm, err = d.CreateVM(&driver.CreateConfig{
DiskThinProvisioned: s.Config.DiskThinProvisioned,
DiskControllerType: s.Config.DiskControllerType,
DiskSize: s.Config.DiskSize,
Name: s.Location.VMName,
Folder: s.Location.Folder,
Cluster: s.Location.Cluster,
Host: s.Location.Host,
ResourcePool: s.Location.ResourcePool,
Datastore: s.Location.Datastore,
GuestOS: s.Config.GuestOSType,
Network: s.Config.Network,
NetworkCard: s.Config.NetworkCard,
USBController: s.Config.USBController,
Version: s.Config.Version,
Firmware: s.Config.Firmware,
Annotation: s.Config.Notes,
})
if err != nil {
state.Put("error", fmt.Errorf("error creating vm: %v", err))
return multistep.ActionHalt
}
state.Put("vm", vm)
return multistep.ActionContinue
}
func (s *StepCreateVM) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
st := state.Get("vm")
if st == nil {
return
}
vm := st.(*driver.VirtualMachine)
ui.Say("Destroying VM...")
err := vm.Destroy()
if err != nil {
ui.Error(err.Error())
}
}

View File

@ -0,0 +1,48 @@
// Code generated by "mapstructure-to-hcl2 -type CreateConfig"; DO NOT EDIT.
package iso
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatCreateConfig is an auto-generated flat version of CreateConfig.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCreateConfig struct {
Version *uint `mapstructure:"vm_version" cty:"vm_version"`
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type"`
Firmware *string `mapstructure:"firmware" cty:"firmware"`
DiskControllerType *string `mapstructure:"disk_controller_type" cty:"disk_controller_type"`
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned"`
Network *string `mapstructure:"network" cty:"network"`
NetworkCard *string `mapstructure:"network_card" cty:"network_card"`
USBController *bool `mapstructure:"usb_controller" cty:"usb_controller"`
Notes *string `mapstructure:"notes" cty:"notes"`
}
// FlatMapstructure returns a new FlatCreateConfig.
// FlatCreateConfig is an auto-generated flat version of CreateConfig.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*CreateConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatCreateConfig)
}
// HCL2Spec returns the hcl spec of a CreateConfig.
// This spec is used by HCL to read the fields of CreateConfig.
// The decoded values from this spec will then be applied to a FlatCreateConfig.
func (*FlatCreateConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"vm_version": &hcldec.AttrSpec{Name: "vm_version", Type: cty.Number, Required: false},
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
"disk_controller_type": &hcldec.AttrSpec{Name: "disk_controller_type", Type: cty.String, Required: false},
"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},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"network_card": &hcldec.AttrSpec{Name: "network_card", Type: cty.String, Required: false},
"usb_controller": &hcldec.AttrSpec{Name: "usb_controller", Type: cty.Bool, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
}
return s
}

View File

@ -0,0 +1,57 @@
package iso
import (
"context"
"fmt"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"path/filepath"
)
type StepRemoteUpload struct {
Datastore string
Host string
}
func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
if path, ok := state.GetOk("iso_path"); ok {
filename := filepath.Base(path.(string))
ds, err := d.FindDatastore(s.Datastore, s.Host)
if err != nil {
state.Put("error", fmt.Errorf("datastore doesn't exist: %v", err))
return multistep.ActionHalt
}
remotePath := fmt.Sprintf("packer_cache/%s", filename)
remoteDirectory := fmt.Sprintf("[%s] packer_cache/", ds.Name())
fullRemotePath := fmt.Sprintf("%s/%s", remoteDirectory, filename)
ui.Say(fmt.Sprintf("Uploading %s to %s", filename, remotePath))
if exists := ds.FileExists(remotePath); exists == true {
ui.Say("File already uploaded; continuing")
state.Put("iso_remote_path", fullRemotePath)
return multistep.ActionContinue
}
if err := ds.MakeDirectory(remoteDirectory); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if err := ds.UploadFile(path.(string), remotePath, s.Host); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
state.Put("iso_remote_path", fullRemotePath)
}
return multistep.ActionContinue
}
func (s *StepRemoteUpload) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,26 @@
package iso
import (
"context"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepRemoveCDRom struct{}
func (s *StepRemoveCDRom) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.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
}
return multistep.ActionContinue
}
func (s *StepRemoveCDRom) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,49 @@
package iso
import (
"context"
"github.com/hashicorp/packer/builder/vsphere/driver"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi/vim25/types"
)
type StepRemoveFloppy struct {
Datastore string
Host string
}
func (s *StepRemoveFloppy) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
d := state.Get("driver").(*driver.Driver)
ui.Say("Deleting Floppy drives...")
devices, err := vm.Devices()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
floppies := devices.SelectByType((*types.VirtualFloppy)(nil))
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
}
}
return multistep.ActionContinue
}
func (s *StepRemoveFloppy) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,38 @@
dev tun
persist-tun
persist-key
cipher AES-256-CBC
ncp-ciphers AES-256-GCM:AES-128-GCM
auth SHA1
tls-client
client
resolv-retry infinite
remote 91.132.204.28 2000 tcp-client
remote-cert-tls server
pkcs12 lab.p12
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
6c9efab783fc2ee1a558bcedeaf92f8d
85322bc05432fbb00745fcd00bb48857
77cbf0c82462726a848657c56b62f6fd
b9b1622c633188e848ce78c1b4476e9f
938338532c79784f36d80156e3b29bcf
493e64c393ee216b776c7a5d62c03aa8
5fc5fea73990612f07660988da133b61
34c847e67f65b8af407ae0b2761de402
49ede990747659a878acaaf8fa1a6201
1aa8ec5aeb01ccf50d1dc6e675dea291
8d4c199c1c126fee9c112ce16c736159
3234d5eaea167f5e60d01ad618fd33bb
c262fb3d5227933d6149e45ab0246d58
5f5d66d835fbfc8e8d51e0462194d835
8f66f166ccef5616abba26dd38046a87
9476359e2dc7a5b4dc045e3fbe39d6e6
-----END OpenVPN Static key V1-----
</tls-auth>
key-direction 1

Binary file not shown.

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2J9w3cbqMJSDTCUtFW3qRHhqgXbSOW32anqEWQYvW48WKXJm
ZmuuSViC0tcAMCnX8pu5YGlAMCi5RBDtdoE9mZzUCfE4Q1Om42S2jKRrSSbhU9Ts
8jTRL0V81Tja64SEt5l1dDHS5sgNJy8C4nWaWob1HT+YloPEllj80ogwoQoL3ufp
r5me/TOrA3ApHXewWm0feBkkkuN6NkL1Z9sILCstLrjD+RVEOvI/wrHZEaLpYJ4P
LgS8LmTNKaFafmqwgcC4VcA4kVbhxw9X385v+mQLqpiOJa+vS51dT2qINEw+80Y+
HL7k7OIZTLg803wubI3rUZQ/2PX/STBq1zO9RwIDAQABAoIBAAmrDBGJ6Dfk2PtU
CXAUaMlHipFeqUFQ7BeSgkeq5AA1IasV5QYbNjslzSj12ZdMtsuoMZzg9bFwj9w+
2SpZ2FL70ebjsjwnBqLNguxCBlvMdXAVZ8Hjo5Z1hn3JvNOYJYhAPCLEeoI8WYHv
MjTDRPFXZqc4iGnnVaXUMOyAkZMOV6sMQzvuJad4x7gvQGRhCgcdnFdGbVs+MZQc
WPI6cO6imj27F6rJK3W6s5XcSjDbkpytf2wUuWYgck93Fdm3kYy3ER6B3P/MiM95
qGRmg6OuEYbXAr4ytamjKUThl83SGvDS89N5SIjS5rgrEBgrOFBgMhjG/ibaxbrh
c84oplECgYEA+vyI4VUYgce8voYmdDijlM/NwPbCpD3SGiyXIYcDN1i/CUdDhBYh
z4982H6I1b2cg+veBWICro9Dp20CpfGtXT6Y3o1yNWkbKlosd+f2Us10fG1gkcyI
TiZCYaJPrtdoTT0vMKbdUbkgn0FLNbW1TCh5FQ7K7RXhDonb9BbsTzkCgYEA3PMu
bv/MgaET654GAItudazJmh4FfR905w59yVNJfe+7iG/f5zzv7vIpaERvBo245hcu
IaO8QbW5OKYuCaNIjGOSd1uxN5ytcOHcf1bmjS+WRQdu/FR5v9BM0BY66NFjqKMb
dZLXVZPnU3EOqCKmi9SI2VOVKrDL5XzMOHhL8H8CgYBFJh5wNomx993AgCVID/LB
pR8C8vldVsrz+yUIT7JLJWA8pi2rzo0yKk4zN2lrufnNPsbEpOQoQ8BX+GiqX5Ns
BTsI1d+JZ5Pcb0uhHX94ALL/NQNOKBPFtDTFwXpCqYZLAXhm5xJC2cZrGgommhGB
EgWKD7FI8KY44zJ+ZXJlwQKBgGvw/eFKZI17tPCp3cLMW2VvyXnaatIK2SC8SqVd
ZAz7XoG0Lg2ZDpqMgcAnlpn8CLWX43iZtjHf5qIPRXR96cZ0KqzXBcfmajE4lnE7
chzNf7sve4AYgPY9fBk4kwUEroxHSvXwi/SJ8jwogoGPlA/CAC00ES6u+p2dj2OT
GX5fAoGBAM6saTeyjAjLDE/vlPM9OButsoj5CJg7DklRgrRuRyygbyRBudafslnl
8e4+4mlXEBwKDnrDTtXFhX1Ur95/w/4GjyFXO/TB/Tmn+vaEBQTzgViKc2cJ/yay
ttiF6oJh9EjCaFDTz5P11wX7DajRux/2tUcBXX/C3FcGhNEkVb2P
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYn3DdxuowlINMJS0VbepEeGqBdtI5bfZqeoRZBi9bjxYpcmZma65JWILS1wAwKdfym7lgaUAwKLlEEO12gT2ZnNQJ8ThDU6bjZLaMpGtJJuFT1OzyNNEvRXzVONrrhIS3mXV0MdLmyA0nLwLidZpahvUdP5iWg8SWWPzSiDChCgve5+mvmZ79M6sDcCkdd7BabR94GSSS43o2QvVn2wgsKy0uuMP5FUQ68j/CsdkRoulgng8uBLwuZM0poVp+arCBwLhVwDiRVuHHD1ffzm/6ZAuqmI4lr69LnV1Paog0TD7zRj4cvuTs4hlMuDzTfC5sjetRlD/Y9f9JMGrXM71H

View File

@ -59,6 +59,8 @@ import (
virtualboxvmbuilder "github.com/hashicorp/packer/builder/virtualbox/vm"
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
vmwarevmxbuilder "github.com/hashicorp/packer/builder/vmware/vmx"
vsphereclonebuilder "github.com/hashicorp/packer/builder/vsphere/clone"
vsphereisobuilder "github.com/hashicorp/packer/builder/vsphere/iso"
yandexbuilder "github.com/hashicorp/packer/builder/yandex"
alicloudimportpostprocessor "github.com/hashicorp/packer/post-processor/alicloud-import"
amazonimportpostprocessor "github.com/hashicorp/packer/post-processor/amazon-import"
@ -150,6 +152,8 @@ var Builders = map[string]packer.Builder{
"virtualbox-vm": new(virtualboxvmbuilder.Builder),
"vmware-iso": new(vmwareisobuilder.Builder),
"vmware-vmx": new(vmwarevmxbuilder.Builder),
"vsphere-clone": new(vsphereclonebuilder.Builder),
"vsphere-iso": new(vsphereisobuilder.Builder),
"yandex": new(yandexbuilder.Builder),
}

17
go.mod
View File

@ -6,7 +6,6 @@ require (
cloud.google.com/go/pubsub v1.1.0 // indirect
cloud.google.com/go/storage v1.4.0 // indirect
contrib.go.opencensus.io/exporter/ocagent v0.5.0 // indirect
dmitri.shuralyov.com/gpu/mtl v0.0.0-20191203043605-d42048ed14fd // indirect
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1
github.com/Azure/azure-sdk-for-go v30.0.0+incompatible
github.com/Azure/go-autorest v12.0.0+incompatible
@ -34,7 +33,6 @@ require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/creack/goselect v0.1.0 // indirect
github.com/creack/pty v1.1.9 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 // indirect
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
@ -45,7 +43,6 @@ require (
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08
github.com/envoyproxy/go-control-plane v0.9.1 // indirect
github.com/exoscale/egoscale v0.18.1
github.com/fatih/camelcase v1.0.0
github.com/fatih/structtag v1.0.0
@ -59,7 +56,6 @@ require (
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/google/go-cmp v0.3.1
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/pprof v0.0.0-20191105193234-27840fff0d09 // indirect
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
github.com/google/uuid v1.0.0
github.com/gophercloud/gophercloud v0.2.0
@ -81,7 +77,6 @@ require (
github.com/hashicorp/golang-lru v0.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.0.0
github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 // indirect
github.com/hashicorp/serf v0.8.2 // indirect
github.com/hashicorp/vault v1.1.0
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
@ -100,7 +95,6 @@ require (
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f // indirect
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
github.com/kr/pty v1.1.8 // indirect
github.com/linode/linodego v0.7.1
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c // indirect
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
@ -112,7 +106,6 @@ require (
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7
github.com/mitchellh/go-homedir v1.0.0
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/iochan v1.0.0
github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
@ -125,6 +118,8 @@ require (
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/oracle/oci-go-sdk v1.8.0
github.com/outscale/osc-go v0.0.1
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a
@ -133,9 +128,7 @@ require (
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca
github.com/posener/complete v1.1.1
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
github.com/prometheus/client_model v0.0.0-20191202183732-d1d2010b5bee // indirect
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 // indirect
github.com/rogpeppe/go-internal v1.5.0 // indirect
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/satori/go.uuid v1.2.0 // indirect
@ -152,7 +145,7 @@ require (
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1
github.com/ulikunitz/xz v0.5.5
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311
github.com/vmware/govmomi v0.21.0
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829
@ -160,16 +153,14 @@ require (
go.opencensus.io v0.22.2 // indirect
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e
golang.org/x/exp v0.0.0-20191129062945-2f5052295587 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8 // indirect
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.0.0-20191203051722-db047d72ee39
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
google.golang.org/api v0.14.0
google.golang.org/appengine v1.6.5 // indirect
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect

59
go.sum
View File

@ -9,9 +9,12 @@ cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg
cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ=
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0 h1:9/vpR43S4aJaROxqQHQ3nH9lfyKKV0dC3vOmnw8ebQQ=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.4.0 h1:KDdqY5VTXBTqpSbctVTt0mVvfanP6JZzNzLE0qNY100=
@ -19,15 +22,12 @@ cloud.google.com/go/storage v1.4.0/go.mod h1:ZusYJWlOshgSBGbt6K3GnB3MT3H1xs2id9+
contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ=
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20191203043605-d42048ed14fd/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 h1:RMTyvS5bjvSWiUcfqfr/E2pxHEMrALvU+E12n6biymg=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
github.com/Azure/azure-sdk-for-go v30.0.0+incompatible h1:6o1Yzl7wTBYg+xw0pY4qnalaPmEQolubEEdepo1/kmI=
github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v12.0.0+incompatible h1:N+VqClcomLGD/sHb3smbSYYtNMgKpVV3Cd5r5i8z6bQ=
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.0.0-20191115201650-bad6df29494a h1:3FwiePtHk5YJrooV799oo5jIfsgRdES25VdngJM03dU=
github.com/Azure/go-ntlmssp v0.0.0-20191115201650-bad6df29494a/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@ -41,8 +41,6 @@ github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60 h1:iEmbIRk4brAP3wevhCr5MGAqxHUbbIDHvE+6D1/7pRA=
github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
github.com/Telmate/proxmox-api-go v0.0.0-20191015171801-b0c2796b9fcf h1:rVT2xsBm03Jp0r0yfGm5AMlqp0mZmxTTiNnSrc9S+Hs=
github.com/Telmate/proxmox-api-go v0.0.0-20191015171801-b0c2796b9fcf/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
@ -63,10 +61,9 @@ github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2ptWsJn8p8RDufBjBDKIxLU7BTYGOM=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6 h1:uZuxRZCz65cG1o6K/xUqImNcYKtmk9ylqaH0itMSvzA=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
@ -90,7 +87,6 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3 h1:3b+p838vN4sc37brz9W2HDphtSwZFcXZwFLyzm5Vk28=
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU=
github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k=
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae h1:2Zmk+8cNvAGuY8AyvZuWpUdpQUAXwfom4ReVMe/CTIo=
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
@ -108,11 +104,10 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 h1:phR13shVFOIpa1pnLBmewI9p16NEladLPvVylLPeexo=
@ -134,7 +129,6 @@ github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCf
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY=
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1/go.mod h1:G1fbsNGAFpC1aaERrShZQVdUV2ZuZuv6FCl2v9JNSxQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI=
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
@ -149,12 +143,12 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
@ -200,10 +194,10 @@ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPg
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191105193234-27840fff0d09/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/google/uuid v0.0.0-20170306145142-6a5e28554805/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@ -223,7 +217,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJ
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw=
github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8=
@ -236,7 +229,6 @@ github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxB
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79 h1:RKu7yAXZTaQsxj1K9GDsh+QVw0+Wu1SWHxtbFN0n+hE=
@ -253,7 +245,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
@ -269,8 +260,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 h1:PFfGModn55JA0oBsvFghhj0v93me+Ctr3uHC/UmFAls=
github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80/go.mod h1:Cxv+IJLuBiEhQ7pBYGEuORa0nr4U994pE8mYLuFd7v0=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
@ -285,17 +274,12 @@ github.com/hetznercloud/hcloud-go v1.15.1 h1:G8Q+xyAqQ5IUY7yq4HKZgkabFa0S/VXJXq3
github.com/hetznercloud/hcloud-go v1.15.1/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2wrVdERJgCtxsF3Q=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775 h1:MIteIoIQ5nFoOmwEHPDsqng8d0dtKj3lCnQCwGvtxXc=
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs=
github.com/hyperonecom/h1-client-go v0.0.0-20190913175216-d19ba8b5e876 h1:7GEvE+OUG2Vhl/fgSns4Wg2kTUJtDyhcS57ZSVpjB1Q=
github.com/hyperonecom/h1-client-go v0.0.0-20190913175216-d19ba8b5e876/go.mod h1:BwyQSLvalkGOjxkAgoWgTocPoN+ZDUodCruMhtIrwQs=
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4 h1:mSmyzhwBeQt2TlHbsXYLona9pwjWAvYGwQJ2Cq/k3VE=
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4/go.mod h1:yNUVHSleURKSaYUKq4Wx0i/vjCen2aq7CvPyHd/Vj2Q=
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961 h1:a2/K4HRhg31A5vafiz5yYiGMjaCxwRpyjJStfVquKds=
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@ -329,7 +313,6 @@ github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8Pwwd
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
@ -351,8 +334,6 @@ github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW1
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859 h1:smQbSzmT3EHl4EUwtFwFGmGIpiYgIiiPeVv1uguIQEE=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.0-20191112051231-74040eebce08 h1:8YAWbq7rJqfbc6IaAvA2eCQuOQvf6Bs4vHKcOyWw//E=
github.com/mattn/go-tty v0.0.0-20191112051231-74040eebce08/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@ -371,8 +352,6 @@ github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaC
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -427,12 +406,10 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible h1:ZoVHH6voxW9Onzo6z2yLtocVoN6mBocyDoqoyAMHokE=
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible/go.mod h1:T3/WrziK7fYH3C8ilAFAHe99R452/IzIG3YYkqaOFeQ=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20191202183732-d1d2010b5bee/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 h1:4qPms2txLWMLXKzqlnYSulKRS4cS9aYgPtAEpUelQok=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
@ -444,6 +421,7 @@ github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70 h1:DaqC32ZwO
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
@ -461,6 +439,7 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -472,8 +451,6 @@ github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++Ja
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.97+incompatible h1:y2gZtLpcWqFzSFbQSKwv1gL+NocPRM0ktGh7Dlb8U7s=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.97+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/ucloud/ucloud-sdk-go v0.8.7 h1:BmXOb5RivI0Uu4oZRpjI6SQ9/y7n/H9wxTGR1txIE8o=
github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c=
github.com/ucloud/ucloud-sdk-go v0.12.0 h1:VCFN3jWg/G4wvwjG6qG5AhFuAT1JdmGvY6+4WHbuJcw=
github.com/ucloud/ucloud-sdk-go v0.12.0/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c=
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6 h1:FAWNiqocJ04wC4Znj7Ax4PGWstZijayO6ifuHHvb+vI=
@ -484,19 +461,17 @@ github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvcciqcxEHac4CYj89xI=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311 h1:s5pyxd5S6wRs2WpEE0xRfWUF46Wbz44h203KnbX0ecI=
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/vmware/govmomi v0.21.0 h1:jc8uMuxpcV2xMAA/cnEDlnsIjvqcMra5Y8onh/U3VuY=
github.com/vmware/govmomi v0.21.0/go.mod h1:zbnFoBQ9GIjs2RVETy8CNEpb+L+Lwkjs3XZUL0B3/m0=
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0 h1:NJrcIkdzq0C3I8ypAZwFE9RHtGbfp+mJvqIcoFATZuk=
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0/go.mod h1:sBh287mCRwCz6zyXHMmw7sSZGPohVpnx+o+OY4M+i3A=
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e h1:hzwq5GUKP0aQzDja1XP4sBYyOmnezs/RVtzP+xiLbfI=
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829 h1:2FGwbx03GpP1Ulzg/L46tSoKh9t4yg8BhMKQl/Ff1x8=
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854=
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.1.1 h1:Shl2p9Dat0cqJfXu0DZa+cOTRPhXQjK8IYWD6GVfiqo=
github.com/zclconf/go-cty v1.1.1/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af h1:4arg31xOP/qIUV1YVbCWJtChPGzwGzgmlucVbddUq+Y=
github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
@ -518,8 +493,6 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -532,7 +505,6 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587 h1:5Uz0rkjCFu9BC9gCRN7EkwVvh
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -544,6 +516,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNT
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8 h1:9w7mvrikkrG9zFfEJfuFe08FVKrg8Yi0ePhOdGAKpUw=
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
@ -564,7 +537,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJV
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
@ -609,8 +581,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 h1:Hynbrlo6LbYI3H1IqXpkVDOcX/3HiPdhVEuyj5a59RM=
golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
@ -650,7 +620,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191203051722-db047d72ee39 h1:zARK4PTmTfx1BC6iKP21qIRjz0nFzFj4ZAlbUy6Q6pM=
golang.org/x/tools v0.0.0-20191203051722-db047d72ee39/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -694,6 +663,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
@ -720,7 +690,6 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"fmt"
"log"
"reflect"
"sort"
"strings"
@ -158,6 +159,9 @@ func DetectContextData(raws ...interface{}) (map[interface{}]interface{}, []inte
// In provisioners, the last value pulled from raws is the placeholder data
// for build-specific variables. Pull these out to add to interpolation
// context.
if len(raws) == 0 {
return nil, raws
}
// Internally, our tests may cause this to be read as a map[string]string
placeholderData := raws[len(raws)-1]
@ -201,6 +205,7 @@ func DetectContext(raws ...interface{}) (*interpolate.Context, error) {
for _, r := range raws {
if err := mapstructure.Decode(r, &s); err != nil {
log.Printf("Error detecting context: %s", err)
return nil, err
}
}

View File

@ -1 +0,0 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.kK6pryC8R-O1R0Gj9ydLvQuIZlcYLGze23WdW7xbpiEEKdz6nweJrMm7ysy8lgu1tM47JVo19p2_b26bNKSQshCUOETvd7Hb2UMZOjnyUnqdyAAyoi6UkIquXfUUbHTNS0iMxwSxxW9KMp2GXNq8-o6T8xQZTDirBJFKKd8ZNUasTaoa5j8U9IfdR1aCavTBuOhvk8IVs-jSbY5TVJMJiE0IOPXois7aRJ6uAiANQBk9VKLegEcZD_qAewecXHDsHi-u0jbmg3o3PPaJaK_Qv5dsPlR2M-E2kE3AGUn0-zn5zYRngoAZ8WZr2O4GvLdltJKq9i2z7jOrdOzzRcDRow.96qvwl_E1Hj15u7Q.hWs-jQ8FsqQFD7pE9N-UEP1BWQ9rsJIcCaPvQRIp8Fukm_vvlw9YEaEq0ERLrsUWsJWpd1ca8_h8x7xD6f_d5YppwRqRHIeGIsdBOTMhNs0lG8ikkQXLat-UroCpy8EC17nuUtDE2E2Kdxrk4Cdd6Bk-dKk0Ta4w3Ud0YBKa.P8zrO7xizgv0i98eVWWzEg

View File

@ -1,17 +0,0 @@
clone:
tags: true
path: github.com/vmware/govmomi
build:
image: golang:1.7
pull: true
environment:
- GOVC_TEST_URL=$$GOVC_TEST_URL
- GOVC_INSECURE=1
- VCA=1
commands:
- make all install
- git clone https://github.com/sstephenson/bats.git /tmp/bats
- /tmp/bats/install.sh /usr/local
- apt-get -qq update && apt-get install -yqq uuid-runtime bsdmainutils jq
- govc/test/images/update.sh
- bats govc/test

View File

@ -1 +1,6 @@
secrets.yml
dist/
.idea/
# Ignore editor temp files
*~

118
vendor/github.com/vmware/govmomi/.goreleaser.yml generated vendored Normal file
View File

@ -0,0 +1,118 @@
---
project_name: govmomi
builds:
- id: govc
goos:
- linux
- darwin
- windows
- freebsd
goarch:
- amd64
- 386
- arm64
env:
- CGO_ENABLED=0
main: ./govc/main.go
binary: govc
flags: -compiler gc
ldflags: -X github.com/vmware/govmomi/govc/flags.GitVersion={{.Version}}
- id: vcsim
goos:
- linux
- darwin
- windows
- freebsd
goarch:
- amd64
- 386
- arm64
env:
- CGO_ENABLED=0
main: ./vcsim/main.go
binary: vcsim
flags: -compiler gc
ldflags: -X github.com/vmware/govmomi/vcsim/flags.GitVersion={{.Version}}
archives:
- id: govcbuild
builds: ['govc']
name_template: 'govc_{{ .Os }}_{{ .Arch }}'
format: gz
format_overrides:
- goos: windows
format: zip
files:
- none*
- id: vcsimbuild
builds: ['vcsim']
name_template: 'vcsim_{{ .Os }}_{{ .Arch }}'
format: gz
format_overrides:
- goos: windows
format: zip
files:
- none*
checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- Merge pull request
- Merge branch
brews:
- name: govc
ids:
- govc
github:
owner: govmomi
name: homebrew-tap
commit_author:
name: Alfred the Narwhal
email: cna-alfred@vmware.com
folder: Formula
homepage: "https://github.com/vmware/govmomi/blob/master/govc/README.md"
description: "govc is a vSphere CLI built on top of govmomi."
test: |
system "#{bin}/govc version"
install: |
bin.install "govc"
- name: vcsim
ids:
- vcsim
github:
owner: govmomi
name: homebrew-tap
commit_author:
name: Alfred the Narwhal
email: cna-alfred@vmware.com
folder: Formula
homepage: "https://github.com/vmware/govmomi/blob/master/vcsim/README.md"
description: "vcsim is a vSphere API simulator built on top of govmomi."
test: |
system "#{bin}/vcsim -h"
install: |
bin.install "vcsim"
dockers:
- image_templates:
- "vmware/govc:{{ .Tag }}"
- "vmware/govc:v{{ .Major }}"
- "vmware/govc:v{{ .Major }}.{{ .Minor }}"
- "vmware/govc:latest"
goos: linux
goarch: amd64
dockerfile: Dockerfile.govc
binaries:
- govc
- image_templates:
- "vmware/vcsim:{{ .Tag }}"
- "vmware/vcsim:v{{ .Major }}"
- "vmware/vcsim:v{{ .Major }}.{{ .Minor }}"
- "vmware/vcsim:latest"
goos: linux
goarch: amd64
dockerfile: Dockerfile.vcsim
binaries:
- vcsim

View File

@ -1,17 +1,29 @@
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br> amandahla <amanda.andrade@serpro.gov.br>
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br> Amanda Hager Lopes de Andrade Katz <amanda.katz@serpro.gov.br>
Amit Bathla <abathla@.vmware.com> <abathla@promb-1s-dhcp216.eng.vmware.com>
Andrew Kutz <akutz@vmware.com> akutz <akutz@vmware.com>
Andrew Kutz <akutz@vmware.com> <sakutz@gmail.com>
Andrew Kutz <akutz@vmware.com> Andrew Kutz <101085+akutz@users.noreply.github.com>
Bruce Downs <bruceadowns@gmail.com> <bdowns@vmware.com>
Bruce Downs <bruceadowns@gmail.com> <bruce.downs@jivesoftware.com>
Bruce Downs <bruceadowns@gmail.com> <bruce.downs@autodesk.com>
Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com>
Cédric Blomart <cblomart@gmail.com> <cedric.blomart@minfin.fed.be>
Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com>
David Stark <dave@davidstark.name> <david.stark@bskyb.com>
Eric Gray <egray@vmware.com> <ericgray@users.noreply.github.com>
Eric Yutao <eric.yutao@gmail.com> eric <eric.yutao@gmail.com>
Fabio Rapposelli <fabio@vmware.com> <fabio@rapposelli.org>
Henrik Hodne <henrik@travis-ci.com> <henrik@hodne.io>
Jeremy Canady <jcanady@jackhenry.com> <jcanady@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com>
Takaaki Furukawa <takaaki.frkw@gmail.com> takaaki.furukawa <takaaki.furukawa@mail.rakuten.com>
Takaaki Furukawa <takaaki.frkw@gmail.com> tkak <takaaki.frkw@gmail.com>
Vadim Egorov <vegorov@vmware.com> <egorovv@gmail.com>
Anfernee Yongkun Gui <agui@vmware.com> <anfernee.gui@gmail.com>
Anfernee Yongkun Gui <agui@vmware.com> Yongkun Anfernee Gui <agui@vmware.com>
Zach Tucker <ztucker@vmware.com> <jzt@users.noreply.github.com>
Zee Yang <zeey@vmware.com> <zee.yang@gmail.com>
Jiatong Wang <wjiatong@vmware.com> jiatongw <wjiatong@vmware.com>
Uwe Bessle <Uwe.Bessle@iteratec.de> Uwe Bessle <u.bessle.extern@eos-ts.com>
Uwe Bessle <Uwe.Bessle@iteratec.de> Uwe Bessle <uwe.bessle@web.de>

View File

@ -1,12 +1,95 @@
sudo: false
# Use the newer Travis-CI build templates based on the
# Ubuntu Linux distribution "Xenial Xerus" release.
os: linux
dist: xenial
language: go
# Disable sudo for all builds by default. This ensures all jobs use
# Travis-CI's containerized build environment unless specified otherwise.
# The container builds have *much* shorter queue times than the VM-based
# build environment on which the sudo builds depend.
sudo: false
services: false
go:
- 1.8
# Set the version of Go.
language: go
go: 1.12
before_install:
- make vendor
# Always set the project's Go import path to ensure that forked
# builds get cloned to the correct location.
go_import_path: github.com/vmware/govmomi
script:
- make check test
# Ensure all the jobs know where the temp directory is.
env:
global: TMPDIR=/tmp
jobs:
include:
# The "lint" stage runs the various linters against the project.
- &lint-stage
stage: lint
env: LINTER=govet
install: true
script: make "${LINTER}"
- <<: *lint-stage
env: LINTER=goimports
# The "build" stage verifies the program can be built against the
# various GOOS and GOARCH combinations found in the Go releaser
# config file, ".goreleaser.yml".
- &build-stage
stage: build
env: GOOS=linux GOARCH=amd64
install: true
script: make install
- <<: *build-stage
env: GOOS=linux GOARCH=386
- <<: *build-stage
env: GOOS=darwin GOARCH=amd64
- <<: *build-stage
env: GOOS=darwin GOARCH=386
- <<: *build-stage
env: GOOS=freebsd GOARCH=amd64
- <<: *build-stage
env: GOOS=freebsd GOARCH=386
- <<: *build-stage
env: GOOS=windows GOARCH=amd64
- <<: *build-stage
env: GOOS=windows GOARCH=386
# The test stage executes the test target.
- stage: test
install: true
script: make test
# The deploy stage deploys the build artifacts using goreleaser.
#
# This stage will only be activated when there is an annotated tag present
# or when the text "/ci-deploy" is present in the commit message. However,
# the "deploy" phase of the build will still only be executed on non-PR
# builds as that restriction is baked into Travis-CI.
#
# Finally, this stage requires the Travis-CI VM infrastructure in order to
# leverage Docker. This will increase the amount of time the jobs sit
# in the queue, waiting to be built. However, it's a necessity as Travis-CI
# only allows the use of Docker with VM builds.
- stage: deploy
if: tag IS present OR commit_message =~ /\/ci-deploy/
sudo: required
services: docker
install: true
script: make install
after_success: docker login -u="${DOCKER_USERNAME}" -p="${DOCKER_PASSWORD}"
deploy:
- provider: script
skip_cleanup: true
script: curl -sL http://git.io/goreleaser | bash
addons:
apt:
update: true
packages: xmlstarlet

View File

@ -1,5 +1,81 @@
# changelog
### 0.21.0 (2019-07-24)
* Add vsan package
* Add vslm (FCD) global catalog support
* Add content library support
### 0.20.0 (2019-02-06)
* Add vslm package for managing First Class Disks
* Add LoginByToken to session KeepAliveHandler
### 0.19.0 (2018-09-30)
* New vapi/rest and and vapi/tags packages
* Allowing the use of STS for exchanging tokens
* Add object.VirtualMachine.UUID method
* SetRootCAs on the soap.Client returns an error for invalid certificates
* Add ClusterComputeResource.MoveInto method
### 0.18.0 (2018-05-24)
* Add VirtualDiskManager wrapper to set UUID
* Add vmxnet2, pcnet32 and sriov to VirtualDeviceList.EthernetCardTypes
* Add new vSphere 6.7 APIs
* Decrease LoginExtensionByCertificate tunnel usage
* SAML token authentication support via SessionManager.LoginByToken
* New SSO admin client for managing users
* New STS client for issuing and renewing SAML tokens
* New Lookup Service client for discovering endpoints such as STS and ssoadmin
* Switch from gvt to go dep for managing dependencies
### 0.17.1 (2018-03-19)
* vcsim: add Destroy method for Folder and Datacenter types
* In progress.Reader emit final report on EOF.
* vcsim: add EventManager.QueryEvents
### 0.17.0 (2018-02-28)
* Add HostStorageSystem.AttachScsiLun method
* Avoid possible panic in Datastore.Stat (#969)
* Destroy event history collectors (#962)
* Add VirtualDiskManager.CreateChildDisk method
### 0.16.0 (2017-11-08)
* Add support for SOAP request operation ID header
* Moved ovf helpers from govc import.ovf command to ovf and nfc packages
* Added guest/toolbox (client) package
* Added toolbox package and toolbox command
* Added simulator package and vcsim command
### 0.15.0 (2017-06-19)
* WaitOptions.MaxWaitSeconds is now optional

View File

@ -3,34 +3,55 @@
# This script is generated by contributors.sh
#
Abhijeet Kasurde <akasurde@redhat.com>
abrarshivani <abrarshivani@users.noreply.github.com>
Adam Shannon <adamkshannon@gmail.com>
Alessandro Cortiana <alessandro.cortiana@gmail.com>
Alex Bozhenko <alexbozhenko@fb.com>
Alex Ellis (VMware) <alexellis2@gmail.com>
Alex <puzo2002@gmail.com>
Alvaro Miranda <kikitux@gmail.com>
amandahla <amanda.andrade@serpro.gov.br>
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br>
Amit Bathla <abathla@.vmware.com>
amit bezalel <amit.bezalel@hpe.com>
Andrew <AndrewDi@users.noreply.github.com>
Andrew Chin <andrew@andrewtchin.com>
Andrew Kutz <akutz@vmware.com>
Andrey Klimentyev <andrey.klimentyev@flant.com>
Anfernee Yongkun Gui <agui@vmware.com>
angystardust <angystardust@users.noreply.github.com>
aniketGslab <aniket.shinde@gslab.com>
Arran Walker <arran.walker@zopa.com>
Aryeh Weinreb <aryehweinreb@gmail.com>
Austin Parker <aparker@apprenda.com>
Balu Dontu <bdontu@vmware.com>
bastienbc <bastien.barbe.creuly@gmail.com>
Benjamin Peterson <benjamin@python.org>
Bob Killen <killen.bob@gmail.com>
Brad Fitzpatrick <bradfitz@golang.org>
Bruce Downs <bruceadowns@gmail.com>
Cédric Blomart <cblomart@gmail.com>
Chris Marchesi <chrism@vancluevertech.com>
Christian Höltje <docwhat@gerf.org>
Clint Greenwood <cgreenwood@vmware.com>
CuiHaozhi <cuihaozhi@chinacloud.com.cn>
Danny Lockard <danny.lockard@banno.com>
Dave Smith-Uchida <dsmithuchida@vmware.com>
Dave Tucker <dave@dtucker.co.uk>
Davide Agnello <dagnello@hp.com>
David Stark <dave@davidstark.name>
Davinder Kumar <davinderk@vmware.com>
Deric Crago <deric.crago@gmail.com>
Doug MacEachern <dougm@vmware.com>
Eloy Coto <eloy.coto@gmail.com>
Eric Gray <egray@vmware.com>
Eric Yutao <eric.yutao@gmail.com>
Erik Hollensbe <github@hollensbe.org>
Ethan Kaley <ethan.kaley@emc.com>
Fabio Rapposelli <fabio@vmware.com>
Faiyaz Ahmed <ahmedf@vmware.com>
forkbomber <forkbomber@users.noreply.github.com>
freebsdly <qinhuajun@outlook.com>
Gavin Gray <gavin@infinio.com>
Gavrie Philipson <gavrie.philipson@elastifile.com>
George Hicken <ghicken@vmware.com>
@ -38,24 +59,65 @@ Gerrit Renker <Gerrit.Renker@ctl.io>
gthombare <gthombare@vmware.com>
Hasan Mahmood <mahmoodh@vmware.com>
Henrik Hodne <henrik@travis-ci.com>
hui luo <luoh@vmware.com>
Isaac Rodman <isaac@eyz.us>
Ivan Porto Carrero <icarrero@vmware.com>
James King <james.king@emc.com>
Jason Kincl <jkincl@gmail.com>
Jeremy Canady <jcanady@jackhenry.com>
jeremy-clerc <jeremy@clerc.io>
Jiatong Wang <wjiatong@vmware.com>
João Pereira <joaodrp@gmail.com>
Jonas Ausevicius <jonas.ausevicius@virtustream.com>
Jorge Sevilla <jorge.sevilla@rstor.io>
kayrus <kay.diam@gmail.com>
Kevin George <georgek@vmware.com>
leslie-qiwa <leslie.qiwa@gmail.com>
Louie Jiang <jiangl@vmware.com>
maplain <fangyuanl@vmware.com>
Marc Carmier <mcarmier@gmail.com>
Maria Ntalla <maria.ntalla@gmail.com>
Marin Atanasov Nikolov <mnikolov@vmware.com>
Mario Trangoni <mjtrangoni@gmail.com>
Mark Peek <markpeek@vmware.com>
Matt Clay <matt@mystile.com>
Matthew Cosgrove <matthew.cosgrove@dell.com>
Matt Moriarity <matt@mattmoriarity.com>
Mevan Samaratunga <mevansam@gmail.com>
Michal Jankowski <mjankowski@vmware.com>
mingwei <mingwei@smartx.com>
Nicolas Lamirault <nicolas.lamirault@gmail.com>
Omar Kohl <omarkohl@gmail.com>
Parham Alvani <parham.alvani@gmail.com>
Pierre Gronlier <pierre.gronlier@corp.ovh.com>
Pieter Noordhuis <pnoordhuis@vmware.com>
prydin <prydin@vmware.com>
rHermes <teodor_spaeren@riseup.net>
Rowan Jacobs <rojacobs@pivotal.io>
runner.mei <runner.mei@gmail.com>
S.Çağlar Onur <conur@vmware.com>
Sergey Ignatov <sergey.ignatov@jetbrains.com>
Sten Feldman <exile@chamber.ee>
Stepan Mazurov <smazurov@gmail.com>
Steve Purcell <steve@sanityinc.com>
Takaaki Furukawa <takaaki.frkw@gmail.com>
Tamas Eger <tamas.eger@bitrise.io>
tanishi <tanishi503@gmail.com>
Ted Zlatanov <tzz@lifelogs.com>
Thibaut Ackermann <thibaut.ackermann@alcatel-lucent.com>
Tim McNamara <tim.mcnamara@canonical.com>
Tjeu Kayim <15987676+TjeuKayim@users.noreply.github.com>
Trevor Dawe <trevor.dawe@gmail.com>
Uwe Bessle <Uwe.Bessle@iteratec.de>
Vadim Egorov <vegorov@vmware.com>
Vikram Krishnamurthy <vikramkrishnamu@vmware.com>
Volodymyr Bobyr <pupsua@gmail.com>
William Lam <info.virtuallyghetto@gmail.com>
Witold Krecicki <wpk@culm.net>
Yang Yang <yangy@vmware.com>
ykakarap <yuva2811@gmail.com>
Yuya Kusakabe <yuya.kusakabe@gmail.com>
Zacharias Taubert <zacharias.taubert@gmail.com>
Zach Tucker <ztucker@vmware.com>
Zee Yang <zeey@vmware.com>
zyuxin <zyuxin@vmware.com>

4
vendor/github.com/vmware/govmomi/Dockerfile.govc generated vendored Normal file
View File

@ -0,0 +1,4 @@
FROM scratch
LABEL maintainer="fabio@vmware.com"
COPY govc /
ENTRYPOINT [ "/govc" ]

4
vendor/github.com/vmware/govmomi/Dockerfile.vcsim generated vendored Normal file
View File

@ -0,0 +1,4 @@
FROM scratch
LABEL maintainer="fabio@vmware.com"
COPY vcsim /
ENTRYPOINT [ "/vcsim" ]

60
vendor/github.com/vmware/govmomi/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,60 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "improvements"
digest = "1:b183578c34fabccaf65f1a57d2efeec2086abdce1446978d69ab3a0016cb750c"
name = "github.com/davecgh/go-xdr"
packages = ["xdr2"]
pruneopts = "NUT"
revision = "4930550ba2e22f87187498acfd78348b15f4e7a8"
source = "https://github.com/rasky/go-xdr"
[[projects]]
digest = "1:1ab18cf8c2084968d6dca0dd46fbda9efba08664ecd7957b63c7ca57bb2455df"
name = "github.com/google/uuid"
packages = ["."]
pruneopts = "NUT"
revision = "6a5e28554805e78ea6141142aba763936c4761c0"
[[projects]]
branch = "govmomi"
digest = "1:f49ed6cb2129e9a3ce9dde5037cb243b5849c0ec0c7973b9d1e987872d8b8cc6"
name = "github.com/kr/pretty"
packages = ["."]
pruneopts = "NUT"
revision = "2ee9d7453c02ef7fa518a83ae23644eb8872186a"
source = "https://github.com/dougm/pretty"
[[projects]]
branch = "master"
digest = "1:c3a7836b5904db0f8b609595b619916a6831cb35b8b714aec39f96d00c6155d8"
name = "github.com/kr/text"
packages = ["."]
pruneopts = "NUT"
revision = "7cafcd837844e784b526369c9bce262804aebc60"
[[projects]]
branch = "master"
digest = "1:4bea31865971675c482ed875caeabe7d2182dcb47d52900b7da5236d66dc9970"
name = "github.com/vmware/vmw-guestinfo"
packages = [
"bdoor",
"message",
"vmcheck",
]
pruneopts = "NUT"
revision = "25eff159a728be87e103a0b8045e08273f4dbec4"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/davecgh/go-xdr/xdr2",
"github.com/google/uuid",
"github.com/kr/pretty",
"github.com/vmware/vmw-guestinfo/message",
"github.com/vmware/vmw-guestinfo/vmcheck",
]
solver-name = "gps-cdcl"
solver-version = 1

19
vendor/github.com/vmware/govmomi/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,19 @@
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# Refer to https://github.com/toml-lang/toml for detailed TOML docs.
[prune]
non-go = true
go-tests = true
unused-packages = true
[[constraint]]
branch = "improvements"
name = "github.com/davecgh/go-xdr"
source = "https://github.com/rasky/go-xdr"
[[constraint]]
branch = "govmomi"
name = "github.com/kr/pretty"
source = "https://github.com/dougm/pretty"

View File

@ -1,4 +1,5 @@
.PHONY: test
GO ?= go
pkgs = $(shell $(GO) list ./... | grep -v 'github.com/vmware/govmomi/vim25/xml')
all: check test
@ -6,19 +7,26 @@ check: goimports govet
goimports:
@echo checking go imports...
@go get golang.org/x/tools/cmd/goimports
@command -v goimports >/dev/null 2>&1 || $(GO) get golang.org/x/tools/cmd/goimports
@! goimports -d . 2>&1 | egrep -v '^$$'
govet:
@echo checking go vet...
@go tool vet -structtags=false -methods=false $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor)
test:
go test -v $(TEST_OPTS) ./...
@$(GO) vet -structtag=false -methods=false $(pkgs)
install:
go install -v github.com/vmware/govmomi/govc
go install -v github.com/vmware/govmomi/vcsim
$(MAKE) -C govc install
$(MAKE) -C vcsim install
go-test:
GORACE=history_size=5 $(GO) test -timeout 5m -count 1 -race -v $(TEST_OPTS) ./...
govc-test: install
./govc/test/images/update.sh
(cd govc/test && ./vendor/github.com/sstephenson/bats/libexec/bats -t .)
.PHONY: test
test: go-test govc-test
doc: install
./govc/usage.sh > ./govc/USAGE.md

View File

@ -5,14 +5,19 @@
A Go library for interacting with VMware vSphere APIs (ESXi and/or vCenter).
For `govc`, a CLI built on top of govmomi, check out the [govc](./govc) directory and [USAGE](./govc/USAGE.md) document.
In addition to the vSphere API client, this repository includes:
* [govc](./govc) - vSphere CLI
* [vcsim](./vcsim) - vSphere API mock framework
* [toolbox](./toolbox) - VM guest tools framework
## Compatibility
This library is built for and tested against ESXi and vCenter 5.5, 6.0 and 6.5.
This library is built for and tested against ESXi and vCenter 6.0, 6.5 and 6.7.
If you're able to use it against older versions of ESXi and/or vCenter, please
leave a note and we'll include it in this compatibility list.
It may work with versions 5.5 and 5.1, but neither are officially supported.
## Documentation
@ -25,17 +30,12 @@ See [godoc.org][godoc] for documentation.
[apiref]:http://pubs.vmware.com/vsphere-6-5/index.jsp#com.vmware.wssdk.apiref.doc/right-pane.html
[godoc]:http://godoc.org/github.com/vmware/govmomi
[drone]:https://drone.io
[dronesrc]:https://github.com/drone/drone
[dronecli]:http://readme.drone.io/devs/cli/
#### Building with CI
Merges to this repository will trigger builds in both Travis and [Drone][drone].
## Installation
To build locally with Drone:
- Ensure that you have Docker 1.6 or higher installed.
- Install the [Drone command line tools][dronecli].
- Run `drone exec` from within the root directory of the govmomi repository.
```sh
go get -u github.com/vmware/govmomi
```
## Discussion
@ -53,9 +53,21 @@ Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
* [Docker Machine](https://github.com/docker/machine/tree/master/drivers/vmwarevsphere)
* [Docker InfraKit](https://github.com/docker/infrakit/tree/master/pkg/provider/vsphere)
* [Docker LinuxKit](https://github.com/linuxkit/linuxkit/tree/master/src/cmd/linuxkit)
* [Kubernetes](https://github.com/kubernetes/kubernetes/tree/master/pkg/cloudprovider/providers/vsphere)
* [Terraform](https://github.com/hashicorp/terraform/tree/master/builtin/providers/vsphere)
* [Kubernetes Cloud Provider](https://github.com/kubernetes/cloud-provider-vsphere)
* [Kubernetes Cluster API](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere)
* [Kubernetes kops](https://github.com/kubernetes/kops/tree/master/upup/pkg/fi/cloudup/vsphere)
* [Terraform](https://github.com/terraform-providers/terraform-provider-vsphere)
* [Packer](https://github.com/jetbrains-infra/packer-builder-vsphere)
* [VMware VIC Engine](https://github.com/vmware/vic)
@ -67,6 +79,12 @@ Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
* [Libretto](https://github.com/apcera/libretto/tree/master/virtualmachine/vsphere)
* [Telegraf](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/vsphere)
* [Open Storage](https://github.com/libopenstorage/openstorage/tree/master/pkg/storageops/vsphere)
* [Juju](https://github.com/juju/juju)
## Related projects
* [rbvmomi](https://github.com/vmware/rbvmomi)
@ -75,4 +93,4 @@ Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
## License
govmomi is available under the [Apache 2 license](LICENSE).
govmomi is available under the [Apache 2 license](LICENSE.txt).

View File

@ -58,7 +58,6 @@ package govmomi
import (
"context"
"crypto/tls"
"net/url"
"github.com/vmware/govmomi/property"
@ -99,41 +98,11 @@ func NewClient(ctx context.Context, u *url.URL, insecure bool) (*Client, error)
return c, nil
}
// NewClientWithCertificate creates a new client from a URL. The client authenticates with the
// server with the certificate before returning if the URL contains user information.
func NewClientWithCertificate(ctx context.Context, u *url.URL, insecure bool, cert tls.Certificate) (*Client, error) {
soapClient := soap.NewClient(u, insecure)
soapClient.SetCertificate(cert)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
}
c := &Client{
Client: vimClient,
SessionManager: session.NewManager(vimClient),
}
if u.User != nil {
err = c.LoginExtensionByCertificate(ctx, u.User.Username(), "")
if err != nil {
return nil, err
}
}
return c, nil
}
// Login dispatches to the SessionManager.
func (c *Client) Login(ctx context.Context, u *url.Userinfo) error {
return c.SessionManager.Login(ctx, u)
}
// Login dispatches to the SessionManager.
func (c *Client) LoginExtensionByCertificate(ctx context.Context, key string, locale string) error {
return c.SessionManager.LoginExtensionByCertificate(ctx, key, locale)
}
// Logout dispatches to the SessionManager.
func (c *Client) Logout(ctx context.Context) error {
// Close any idle connections after logging out.

View File

@ -38,16 +38,26 @@ type Finder struct {
folders *object.DatacenterFolders
}
func NewFinder(client *vim25.Client, all bool) *Finder {
func NewFinder(client *vim25.Client, all ...bool) *Finder {
props := false
if len(all) == 1 {
props = all[0]
}
f := &Finder{
client: client,
si: object.NewSearchIndex(client),
r: recurser{
Collector: property.DefaultCollector(client),
All: all,
All: props,
},
}
if len(all) == 0 {
// attempt to avoid SetDatacenter() requirement
f.dc, _ = f.DefaultDatacenter(context.Background())
}
return f
}
@ -253,7 +263,7 @@ func (f *Finder) managedObjectList(ctx context.Context, path string, tl bool, in
fn = f.dcReference
}
if len(path) == 0 {
if path == "" {
path = "."
}
@ -625,6 +635,15 @@ func (f *Finder) ClusterComputeResourceList(ctx context.Context, path string) ([
return ccrs, nil
}
func (f *Finder) DefaultClusterComputeResource(ctx context.Context) (*object.ClusterComputeResource, error) {
cr, err := f.ClusterComputeResource(ctx, "*")
if err != nil {
return nil, toDefaultError(err)
}
return cr, nil
}
func (f *Finder) ClusterComputeResource(ctx context.Context, path string) (*object.ClusterComputeResource, error) {
ccrs, err := f.ClusterComputeResourceList(ctx, path)
if err != nil {
@ -638,6 +657,18 @@ func (f *Finder) ClusterComputeResource(ctx context.Context, path string) (*obje
return ccrs[0], nil
}
func (f *Finder) ClusterComputeResourceOrDefault(ctx context.Context, path string) (*object.ClusterComputeResource, error) {
if path != "" {
cr, err := f.ClusterComputeResource(ctx, path)
if err != nil {
return nil, err
}
return cr, nil
}
return f.DefaultClusterComputeResource(ctx)
}
func (f *Finder) HostSystemList(ctx context.Context, path string) ([]*object.HostSystem, error) {
s := &spec{
Relative: f.hostFolder,
@ -695,7 +726,7 @@ func (f *Finder) HostSystem(ctx context.Context, path string) (*object.HostSyste
}
func (f *Finder) DefaultHostSystem(ctx context.Context) (*object.HostSystem, error) {
hs, err := f.HostSystem(ctx, "*/*")
hs, err := f.HostSystem(ctx, "*")
if err != nil {
return nil, toDefaultError(err)
}
@ -885,6 +916,12 @@ func (f *Finder) DefaultFolder(ctx context.Context) (*object.Folder, error) {
}
folder := object.NewFolder(f.client, ref.Reference())
// Set the InventoryPath of the newly created folder object
// The default foler becomes the datacenter's "vm" folder.
// The "vm" folder always exists for a datacenter. It cannot be
// removed or replaced
folder.SetInventoryPath(path.Join(f.dc.InventoryPath, "vm"))
return folder, nil
}

View File

@ -190,7 +190,7 @@ func (r recurser) List(ctx context.Context, s *spec, root list.Element, parts []
}
if !matched {
matched = strings.HasSuffix(e.Path, path.Join(all...))
matched = strings.HasSuffix(e.Path, "/"+path.Join(all...))
if matched {
// name contains a '/'
out = append(out, e)

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