From a12ff09f0a7a1ca3361220b59d8e78d88290a026 Mon Sep 17 00:00:00 2001 From: Michael Kuzmin Date: Wed, 28 Jun 2017 06:04:25 +0300 Subject: [PATCH 1/4] move setup back into builder --- builder.go | 37 ++++++++++++++++++++---- step_clone_vm.go | 28 ++++++++----------- step_setup.go | 73 ------------------------------------------------ 3 files changed, 43 insertions(+), 95 deletions(-) delete mode 100644 step_setup.go diff --git a/builder.go b/builder.go index 1239e4903..64e26c744 100644 --- a/builder.go +++ b/builder.go @@ -10,6 +10,10 @@ import ( "github.com/hashicorp/packer/helper/communicator" gossh "golang.org/x/crypto/ssh" "github.com/hashicorp/packer/communicator/ssh" + "github.com/vmware/govmomi" + "context" + "net/url" + "github.com/vmware/govmomi/find" ) type Builder struct { @@ -28,16 +32,39 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - // Set up the state. state := new(multistep.BasicStateBag) state.Put("hook", hook) state.Put("ui", ui) + ctx := context.TODO() + state.Put("ctx", ctx) + + vcenter_url, err := url.Parse(b.config.Url) + if err != nil { + return nil, err + } + vcenter_url.User = url.UserPassword(b.config.Username, b.config.Password) + client, err := govmomi.NewClient(ctx, vcenter_url,true) + if err != nil { + return nil, err + } + state.Put("client", client) + + finder := find.NewFinder(client.Client, false) + dc, err := finder.DatacenterOrDefault(ctx, b.config.DCName) + if err != nil { + return nil, err + } + finder.SetDatacenter(dc) + state.Put("finder", finder) + state.Put("dc", dc) + + vmSrc, err := finder.VirtualMachine(ctx, b.config.Template) + if err != nil { + return nil, err + } + state.Put("vmSrc", vmSrc) - // Build the steps. steps := []multistep.Step{ - &StepSetup{ - config: b.config, - }, &StepCloneVM{ config: b.config, }, diff --git a/step_clone_vm.go b/step_clone_vm.go index 35e4043b9..be2e2bb53 100644 --- a/step_clone_vm.go +++ b/step_clone_vm.go @@ -1,7 +1,6 @@ package main import ( - "github.com/vmware/govmomi" "context" "github.com/mitchellh/multistep" "github.com/vmware/govmomi/vim25/types" @@ -14,28 +13,25 @@ import ( ) type CloneParameters struct { - client *govmomi.Client - folder *object.Folder + ctx context.Context + vmSrc *object.VirtualMachine + vmName string + folder *object.Folder resourcePool *object.ResourcePool datastore *object.Datastore - vmSrc *object.VirtualMachine - ctx context.Context - vmName string linkedClone bool } -type StepCloneVM struct{ - config *Config +type StepCloneVM struct { + config *Config success bool } func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { - client := state.Get("client").(*govmomi.Client) ctx := state.Get("ctx").(context.Context) finder := state.Get("finder").(*find.Finder) dc := state.Get("dc").(*object.Datacenter) vmSrc := state.Get("vmSrc").(*object.VirtualMachine) - ui := state.Get("ui").(packer.Ui) ui.Say("start cloning...") @@ -64,13 +60,12 @@ func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { } vm, err := cloneVM(&CloneParameters{ - client: client, - folder: folder, + ctx: ctx, + vmSrc: vmSrc, + vmName: s.config.VMName, + folder: folder, resourcePool: pool, datastore: datastore, - vmSrc: vmSrc, - ctx: ctx, - vmName: s.config.VMName, linkedClone: s.config.LinkedClone, }) if err != nil { @@ -79,7 +74,6 @@ func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction { } state.Put("vm", vm) - state.Put("ctx", ctx) s.success = true return multistep.ActionContinue } @@ -158,6 +152,6 @@ func cloneVM(params *CloneParameters) (vm *object.VirtualMachine, err error) { return } - vm = object.NewVirtualMachine(params.client.Client, info.Result.(types.ManagedObjectReference)) + vm = object.NewVirtualMachine(params.vmSrc.Client(), info.Result.(types.ManagedObjectReference)) return vm, nil } diff --git a/step_setup.go b/step_setup.go deleted file mode 100644 index 2b5327c29..000000000 --- a/step_setup.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "github.com/mitchellh/multistep" - "github.com/hashicorp/packer/packer" - "github.com/vmware/govmomi/find" - "fmt" - "github.com/vmware/govmomi" - "context" - "net/url" -) - -type StepSetup struct{ - config *Config -} - -func (s *StepSetup) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - ui.Say("setup...") - - // Prepare entities: client (authentification), finder, folder, virtual machine - client, ctx, err := createClient(s.config.Url, s.config.Username, s.config.Password) - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - // Set up finder - finder := find.NewFinder(client.Client, false) - dc, err := finder.DatacenterOrDefault(ctx, s.config.DCName) - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - finder.SetDatacenter(dc) - - // Get source VM - vmSrc, err := finder.VirtualMachine(ctx, s.config.Template) - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - state.Put("client", client) - state.Put("ctx", ctx) - state.Put("finder", finder) - state.Put("dc", dc) - state.Put("vmSrc", vmSrc) - return multistep.ActionContinue -} - -func (s *StepSetup) Cleanup(state multistep.StateBag) {} - -func createClient(URL, username, password string) (*govmomi.Client, context.Context, error) { - // create context - ctx := context.TODO() // an empty, default context (for those, who is unsure) - - // create a client - // (connected to the specified URL, - // logged in with the username-password) - u, err := url.Parse(URL) // create a URL object from string - if err != nil { - return nil, nil, err - } - u.User = url.UserPassword(username, password) // set username and password for automatical authentification - fmt.Println(u.String()) - client, err := govmomi.NewClient(ctx, u,true) // creating a client (logs in with given uname&pswd) - if err != nil { - return nil, nil, err - } - - return client, ctx, nil -} From f42df93e8d338bfb2e5ed3c1996946a0c684014e Mon Sep 17 00:00:00 2001 From: Michael Kuzmin Date: Fri, 30 Jun 2017 01:08:49 +0300 Subject: [PATCH 2/4] destroy artifact --- artifact.go | 18 +++++++++++++++++- builder.go | 3 ++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/artifact.go b/artifact.go index 53aef4206..8094bf1dc 100644 --- a/artifact.go +++ b/artifact.go @@ -1,9 +1,15 @@ package main +import ( + "github.com/vmware/govmomi/object" + "context" +) + const BuilderId = "jetbrains.vsphere" type Artifact struct { - VMName string `json:"vm_name"` + VMName string + Conn *object.VirtualMachine } func (a *Artifact) BuilderId() string { @@ -27,5 +33,15 @@ func (a *Artifact) State(name string) interface{} { } func (a *Artifact) Destroy() error { + ctx := context.TODO() + task, err := a.Conn.Destroy(ctx) + if err != nil { + return err + } + _, err = task.WaitForResult(ctx, nil) + if err != nil { + return err + } + return nil } diff --git a/builder.go b/builder.go index 64e26c744..97f9b1167 100644 --- a/builder.go +++ b/builder.go @@ -14,6 +14,7 @@ import ( "context" "net/url" "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" ) type Builder struct { @@ -121,9 +122,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe return nil, errors.New("Build was halted.") } - // No errors, must've worked artifact := &Artifact{ VMName: b.config.VMName, + Conn: state.Get("vm").(*object.VirtualMachine), } return artifact, nil } From 95ca846a08edc1fa9484f479dbebb2e2f396b96d Mon Sep 17 00:00:00 2001 From: Michael Kuzmin Date: Mon, 12 Jun 2017 21:08:25 +0300 Subject: [PATCH 3/4] Unit tests --- builder_test.go | 14 +++++++++++ config.go | 10 +++----- config_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 builder_test.go create mode 100644 config_test.go diff --git a/builder_test.go b/builder_test.go new file mode 100644 index 000000000..20243b659 --- /dev/null +++ b/builder_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/hashicorp/packer/packer" + "testing" +) + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Fatalf("Builder should be a builder") + } +} diff --git a/config.go b/config.go index b98ff9b94..9d11afa80 100644 --- a/config.go +++ b/config.go @@ -57,6 +57,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { // Accumulate any errors errs := new(packer.MultiError) + var warnings []string // Prepare config(s) errs = packer.MultiErrorAppend(errs, c.Config.Prepare(&c.ctx)...) @@ -100,12 +101,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) } - - // Warnings - var warnings []string - if c.Datastore == "" { - warnings = append(warnings, "Datastore is not specified, will try to find the default one") - } + //if c.Datastore == "" { + // warnings = append(warnings, "Datastore is not specified, will try to find the default one") + //} if len(errs.Errors) > 0 { return nil, warnings, errs diff --git a/config_test.go b/config_test.go new file mode 100644 index 000000000..f6bed1613 --- /dev/null +++ b/config_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "testing" + "time" +) + +func TestMinimalConfig(t *testing.T) { + _, warns, errs := NewConfig(minimalConfig()) + + testConfigOk(t, warns, errs) +} + +func TestInvalidCpu(t *testing.T) { + raw := minimalConfig() + raw["cpus"] = "string" + _, warns, errs := NewConfig(raw) + testConfigErr(t, warns, errs) +} + +func TestInvalidRam(t *testing.T) { + raw := minimalConfig() + raw["RAM"] = "string" + _, warns, errs := NewConfig(raw) + testConfigErr(t, warns, errs) +} + +func TestTimeout(t *testing.T) { + raw := minimalConfig() + raw["shutdown_timeout"] = "3m" + conf, warns, err := NewConfig(raw) + testConfigOk(t, warns, err) + if conf.ShutdownTimeout != 3 * time.Minute { + t.Fatalf("shutdown_timeout sourld equal 3 minutes") + } +} + +func minimalConfig() map[string]interface{} { + return map[string]interface{}{ + "url": "https://vcenter.domain.local/sdk", + "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.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad: %s", err) + } +} + +func testConfigErr(t *testing.T, warns []string, err error) { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should error") + } +} From 07cbbd1d0b1ee1c1a82d04f006c8c2f015655b44 Mon Sep 17 00:00:00 2001 From: Michael Kuzmin Date: Wed, 14 Jun 2017 21:16:57 +0300 Subject: [PATCH 4/4] integration test --- builder_acc_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 builder_acc_test.go diff --git a/builder_acc_test.go b/builder_acc_test.go new file mode 100644 index 000000000..fc24f92bc --- /dev/null +++ b/builder_acc_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "testing" + builderT "github.com/hashicorp/packer/helper/builder/testing" +) + +func TestBuilderAcc_basic(t *testing.T) { + builderT.Test(t, builderT.TestCase{ + Builder: &Builder{}, + Template: testBuilderAccBasic, + }) +} + +const testBuilderAccBasic = ` +{ + "builders": [{ + "type": "test", + + "url": "https://vcenter.vsphere5.test/sdk", + "username": "root", + "password": "jetbrains", + + "template": "basic", + "vm_name": "test1", + "host": "esxi-1.vsphere5.test", + + "ssh_username": "jetbrains", + "ssh_password": "jetbrains" + }] +} +`