mirror of https://github.com/hashicorp/packer
Builder (#2)
* the very initial builder: just clones the VM * removed post-processor * builder now waits for IP address * more separated steps * clean up * added run and shutdown steps * added step connecting via ssh * changed the BuilderId to a proper one * added shutdown command, only echo works * added cleanup * removed wainting for VM to stop, removed error on the non-zero exit status * removed BuildSuccessFlag * refactored config structures; fixed interpolation of json template * the rest of configuration refactoring * removed duplicated parameters from Config * removed duplicated parameters drom Config * changed BuilderId and Artifact * create a dedicated step for VM hardware configuration * merged StepSetupCloningEnv into StepCloneVM * improved cleanup * added proper handling of 'disconnected' command exit status; added guest os shutdown before halting the machine [in case when no shutdown command is given] * refactored non-conventional variable and field names * removed redundant fields from Artifact * removed the success field at all * removed ArtifactFiles * removed unnecessary warnings * minor change in parameters structurepull/8480/head
parent
8b2a195efd
commit
b2c6e44c70
@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"github.com/hashicorp/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
config *Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c, warnings, errs := NewConfig(raws...)
|
||||
if errs != nil {
|
||||
return warnings, errs
|
||||
}
|
||||
b.config = c
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
&StepConfigureHW{
|
||||
config: b.config,
|
||||
},
|
||||
&StepCloneVM{
|
||||
config: b.config,
|
||||
},
|
||||
&StepRun{},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Config,
|
||||
Host: func(state multistep.StateBag) (string, error) {
|
||||
return state.Get("ip").(string), nil
|
||||
},
|
||||
SSHConfig: func(multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
return &gossh.ClientConfig{
|
||||
User: b.config.Config.SSHUsername,
|
||||
Auth: []gossh.AuthMethod{
|
||||
gossh.Password(b.config.Config.SSHPassword),
|
||||
gossh.KeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(b.config.Config.SSHPassword)),
|
||||
},
|
||||
// TODO: add a proper verification
|
||||
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&StepShutdown{
|
||||
Command: b.config.ShutdownCommand,
|
||||
},
|
||||
}
|
||||
|
||||
// Run!
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If we were interrupted or cancelled, then just exit.
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return nil, errors.New("Build was cancelled.")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
||||
return nil, errors.New("Build was halted.")
|
||||
}
|
||||
|
||||
// No errors, must've worked
|
||||
artifact := &Artifact{
|
||||
VMName: b.config.VMName,
|
||||
}
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
communicator.Config `mapstructure:",squash"`
|
||||
|
||||
Url string `mapstructure:"url"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
|
||||
Template string `mapstructure:"template"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
FolderName string `mapstructure:"folder_name"`
|
||||
DCName string `mapstructure:"dc_name"`
|
||||
|
||||
Cpus string `mapstructure:"cpus"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
Ram string `mapstructure:"RAM"`
|
||||
//TODO: add more options
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
c := new(Config)
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
// Prepare config(s)
|
||||
errs = packer.MultiErrorAppend(errs, c.Config.Prepare(&c.ctx)...)
|
||||
|
||||
// Check the required params
|
||||
if c.Url == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("URL required"))
|
||||
}
|
||||
if c.Username == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Username required"))
|
||||
}
|
||||
if c.Password == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Password required"))
|
||||
}
|
||||
if c.Template == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Template VM name required"))
|
||||
}
|
||||
if c.VMName == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Target VM name required"))
|
||||
}
|
||||
|
||||
// Verify numeric parameters if present
|
||||
if c.Cpus != "" {
|
||||
if _, err = strconv.Atoi(c.Cpus); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid number of cpu sockets"))
|
||||
}
|
||||
}
|
||||
if c.Ram != "" {
|
||||
if _, err = strconv.Atoi(c.Ram); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Invalid number for Ram"))
|
||||
}
|
||||
}
|
||||
|
||||
// Warnings
|
||||
var warnings []string
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return nil, warnings, errs
|
||||
}
|
||||
|
||||
return c, warnings, nil
|
||||
}
|
||||
@ -1,271 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type VMOptionalParams struct {
|
||||
Cpu_sockets int
|
||||
Ram int
|
||||
// TODO: add more options
|
||||
}
|
||||
|
||||
type VMRequiredParams struct {
|
||||
Url string
|
||||
Username string
|
||||
Password string
|
||||
Dc_name string
|
||||
Folder_name string
|
||||
Vm_source_name string
|
||||
Vm_target_name string
|
||||
}
|
||||
|
||||
const DefaultFolder = ""
|
||||
const Unspecified = -1
|
||||
|
||||
var vm_opt_params VMOptionalParams
|
||||
var vm_req_params VMRequiredParams
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
Url string `mapstructure:"url"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Dc_name string `mapstructure:"dc_name"`
|
||||
Vm_source_name string `mapstructure:"vm_source_name"`
|
||||
Vm_target_name string `mapstructure:"vm_target_name"`
|
||||
Cpu_sockets string `mapstructure:"cpus"`
|
||||
Ram string `mapstructure:"RAM"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
// Check the required params
|
||||
templates := map[string]*string{
|
||||
"url": &p.config.Url,
|
||||
"username": &p.config.Username,
|
||||
"password": &p.config.Password,
|
||||
"vm_source_name": &p.config.Vm_source_name,
|
||||
}
|
||||
for key, ptr := range templates {
|
||||
if *ptr == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("%s must be set, %s is present", key, *ptr))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
// Set optional params
|
||||
vm_req_params.Folder_name = DefaultFolder
|
||||
vm_opt_params.Cpu_sockets = Unspecified
|
||||
if p.config.Cpu_sockets != "" {
|
||||
vm_opt_params.Cpu_sockets, err = strconv.Atoi(p.config.Cpu_sockets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
vm_opt_params.Ram = Unspecified
|
||||
if p.config.Ram != "" {
|
||||
vm_opt_params.Ram, err = strconv.Atoi(p.config.Ram)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set required params
|
||||
vm_req_params.Url = p.config.Url
|
||||
vm_req_params.Username = p.config.Username
|
||||
vm_req_params.Password = p.config.Password
|
||||
vm_req_params.Dc_name = p.config.Dc_name
|
||||
vm_req_params.Vm_source_name = p.config.Vm_source_name
|
||||
vm_req_params.Vm_target_name = vm_req_params.Vm_source_name + "_clone"
|
||||
if p.config.Vm_target_name != "" {
|
||||
vm_req_params.Vm_target_name = p.config.Vm_target_name
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloneVM(req_params VMRequiredParams, opt_params VMOptionalParams) error {
|
||||
// Prepare entities: client (authentification), finder, folder, virtual machine
|
||||
client, ctx, err := createClient(req_params.Url, req_params.Username, req_params.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finder, ctx, err := createFinder(ctx, client, req_params.Dc_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folder, err := finder.FolderOrDefault(ctx, req_params.Folder_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vm_src, ctx, err := findVM_by_name(ctx, finder, req_params.Vm_source_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Creating spec's for cloning
|
||||
var relocateSpec types.VirtualMachineRelocateSpec
|
||||
|
||||
var confSpec types.VirtualMachineConfigSpec
|
||||
// configure HW
|
||||
if opt_params.Cpu_sockets != Unspecified {
|
||||
confSpec.NumCPUs = int32(opt_params.Cpu_sockets)
|
||||
}
|
||||
if opt_params.Ram != Unspecified {
|
||||
confSpec.MemoryMB = int64(opt_params.Ram)
|
||||
}
|
||||
|
||||
cloneSpec := types.VirtualMachineCloneSpec{
|
||||
Location: relocateSpec,
|
||||
Config: &confSpec,
|
||||
PowerOn: false,
|
||||
}
|
||||
|
||||
// Cloning itself
|
||||
task, err := vm_src.Clone(ctx, folder, req_params.Vm_target_name, cloneSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, source packer.Artifact) (packer.Artifact, bool, error) {
|
||||
err := CloneVM(vm_req_params, vm_opt_params)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Return:
|
||||
// source -- the given artifact -- since we didn't change anything;
|
||||
// false -- don't force packer to keep the source artifact
|
||||
// nil -- no error occured here
|
||||
return source, false, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func createFinder(ctx context.Context, client *govmomi.Client, dc_name string) (*find.Finder, context.Context, error) {
|
||||
// Create a finder to search for a vm with the specified name
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
// Need to specify the datacenter
|
||||
if dc_name == "" {
|
||||
dc, err := finder.DefaultDatacenter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var dc_mo mo.Datacenter
|
||||
err = dc.Properties(ctx, dc.Reference(), []string{"name"}, &dc_mo)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dc_name = dc_mo.Name
|
||||
finder.SetDatacenter(dc)
|
||||
} else {
|
||||
dc, err := finder.Datacenter(ctx, fmt.Sprintf("/%v", dc_name))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
finder.SetDatacenter(dc)
|
||||
}
|
||||
return finder, ctx, nil
|
||||
}
|
||||
|
||||
func findVM_by_name(ctx context.Context, finder *find.Finder, vm_name string) (*object.VirtualMachine, context.Context, error) {
|
||||
vm_o, err := finder.VirtualMachine(ctx, vm_name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return vm_o, ctx, nil
|
||||
}
|
||||
|
||||
func ReconfigureVM(URL, username, password, dc_name, vm_name string, cpus int) error {
|
||||
client, ctx, err := createClient(URL, username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finder, ctx, err := createFinder(ctx, client, dc_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vm_o, ctx, err := findVM_by_name(ctx, finder, vm_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// creating new configuration for vm
|
||||
vmConfigSpec := types.VirtualMachineConfigSpec{}
|
||||
vmConfigSpec.NumCPUs = int32(cpus)
|
||||
|
||||
// finally reconfiguring
|
||||
task, err := vm_o.Reconfigure(ctx, vmConfigSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/vmware/govmomi"
|
||||
"context"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type CloneParameters struct {
|
||||
client *govmomi.Client
|
||||
folder *object.Folder
|
||||
vmSrc *object.VirtualMachine
|
||||
ctx context.Context
|
||||
vmName string
|
||||
confSpec *types.VirtualMachineConfigSpec
|
||||
}
|
||||
|
||||
type StepCloneVM struct{
|
||||
config *Config
|
||||
success bool
|
||||
}
|
||||
|
||||
func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("start cloning...")
|
||||
|
||||
confSpec := state.Get("confSpec").(types.VirtualMachineConfigSpec)
|
||||
|
||||
// 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
|
||||
}
|
||||
finder, ctx, err := createFinder(ctx, client, s.config.DCName)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
folder, err := finder.FolderOrDefault(ctx, s.config.FolderName)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
vmSrc, err := finder.VirtualMachine(ctx, s.config.Template)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
cloneParameters := CloneParameters{
|
||||
client: client,
|
||||
folder: folder,
|
||||
vmSrc: vmSrc,
|
||||
ctx: ctx,
|
||||
vmName: s.config.VMName,
|
||||
confSpec: &confSpec,
|
||||
}
|
||||
|
||||
vm, err := cloneVM(&cloneParameters)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("vm", vm)
|
||||
state.Put("ctx", ctx)
|
||||
s.success = true
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
|
||||
if !s.success {
|
||||
return
|
||||
}
|
||||
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
vm := state.Get("vm").(*object.VirtualMachine)
|
||||
ctx := state.Get("ctx").(context.Context)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("destroying VM...")
|
||||
|
||||
task, err := vm.Destroy(ctx)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneVM(params *CloneParameters) (vm *object.VirtualMachine, err error) {
|
||||
vm = nil
|
||||
err = nil
|
||||
|
||||
// Creating specs for cloning
|
||||
var relocateSpec types.VirtualMachineRelocateSpec
|
||||
cloneSpec := types.VirtualMachineCloneSpec{
|
||||
Location: relocateSpec,
|
||||
Config: params.confSpec,
|
||||
PowerOn: false,
|
||||
}
|
||||
|
||||
// Cloning itself
|
||||
task, err := params.vmSrc.Clone(params.ctx, params.folder, params.vmName, cloneSpec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
info, err := task.WaitForResult(params.ctx, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
vm = object.NewVirtualMachine(params.client.Client, info.Result.(types.ManagedObjectReference))
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func createFinder(ctx context.Context, client *govmomi.Client, dcName string) (*find.Finder, context.Context, error) {
|
||||
// Create a finder to search for a vm with the specified name
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
// Need to specify the datacenter
|
||||
if dcName == "" {
|
||||
dc, err := finder.DefaultDatacenter(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
finder.SetDatacenter(dc)
|
||||
} else {
|
||||
dc, err := finder.Datacenter(ctx, dcName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
finder.SetDatacenter(dc)
|
||||
}
|
||||
return finder, ctx, nil
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"strconv"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type StepConfigureHW struct{
|
||||
config *Config
|
||||
}
|
||||
|
||||
func (s *StepConfigureHW) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("configuring virtual hardware...")
|
||||
|
||||
var confSpec types.VirtualMachineConfigSpec
|
||||
// configure HW
|
||||
if s.config.Cpus != "" {
|
||||
cpus, err := strconv.Atoi(s.config.Cpus)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
confSpec.NumCPUs = int32(cpus)
|
||||
}
|
||||
if s.config.Ram != "" {
|
||||
ram, err := strconv.Atoi(s.config.Ram)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
confSpec.MemoryMB = int64(ram)
|
||||
}
|
||||
|
||||
state.Put("confSpec", confSpec)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepConfigureHW) Cleanup(multistep.StateBag) {}
|
||||
@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type StepRun struct{
|
||||
// TODO: add boot time to provide a proper timeout during cleanup
|
||||
}
|
||||
|
||||
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vm := state.Get("vm").(*object.VirtualMachine)
|
||||
ctx := state.Get("ctx").(context.Context)
|
||||
|
||||
ui.Say("VM power on...")
|
||||
task, err := vm.PowerOn(ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("VM waiting for IP...")
|
||||
ip, err := vm.WaitForIP(ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("ip", ip)
|
||||
ui.Say(fmt.Sprintf("VM ip %v", ip))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
if cancelled || halted {
|
||||
vm := state.Get("vm").(*object.VirtualMachine)
|
||||
ctx := state.Get("ctx").(context.Context)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if state, err := vm.PowerState(ctx); state != types.VirtualMachinePowerStatePoweredOff && err == nil {
|
||||
ui.Say("shutting down VM...")
|
||||
|
||||
task, err := vm.PowerOff(ctx)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type StepShutdown struct{
|
||||
Command string
|
||||
}
|
||||
|
||||
func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||
// is set during the communicator.StepConnect
|
||||
comm := state.Get("communicator").(packer.Communicator)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vm := state.Get("vm").(*object.VirtualMachine)
|
||||
ctx := state.Get("ctx").(context.Context)
|
||||
|
||||
ui.Say("VM shutdown...")
|
||||
|
||||
if s.Command != "" {
|
||||
ui.Say("Gracefully halting virtual machine...")
|
||||
log.Printf("Executing shutdown command: %s", s.Command)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: s.Command,
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
}
|
||||
if err := comm.Start(cmd); err != nil {
|
||||
err := fmt.Errorf("Failed to send shutdown command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// TODO: add timeout
|
||||
for !cmd.Exited {
|
||||
ui.Say("Waiting for remote cmd to finish...")
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
}
|
||||
if cmd.ExitStatus != 0 && cmd.ExitStatus != packer.CmdDisconnect {
|
||||
err := fmt.Errorf("Cmd exit status %v, not 0", cmd.ExitStatus)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
} else if cmd.ExitStatus == packer.CmdDisconnect {
|
||||
ui.Say("VM disconnected")
|
||||
}
|
||||
} else {
|
||||
ui.Say("Forcibly halting virtual machine...")
|
||||
|
||||
err := vm.ShutdownGuest(ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
task, err := vm.PowerOff(ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("VM stopped")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
Loading…
Reference in new issue