Add ISO builder which creates an empty VM

This commit is contained in:
Andrei Tonkikh 2018-01-24 17:56:14 +03:00
parent f909669670
commit df40ffbe8d
16 changed files with 436 additions and 82 deletions

View File

@ -11,3 +11,7 @@ rm -f bin/*
GOOS=darwin go build -o bin/packer-builder-vsphere-clone.macos ./clone
GOOS=linux go build -o bin/packer-builder-vsphere-clone.linux ./clone
GOOS=windows go build -o bin/packer-builder-vsphere-clone.exe ./clone
GOOS=darwin go build -o bin/packer-builder-vsphere-iso.macos ./iso
GOOS=linux go build -o bin/packer-builder-vsphere-iso.linux ./iso
GOOS=windows go build -o bin/packer-builder-vsphere-iso.exe ./iso

View File

@ -1,8 +1,6 @@
package clone
import (
"errors"
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/packer"
@ -75,18 +73,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner = packerCommon.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.")
if err := common.CheckRunStatus(state); err != nil {
return nil, err
}
artifact := &common.Artifact{

View File

@ -1,21 +1,20 @@
package clone
import (
"encoding/json"
"fmt"
builderT "github.com/hashicorp/packer/helper/builder/testing"
commonT "github.com/jetbrains-infra/packer-builder-vsphere/common/testing"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"math/rand"
"testing"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
)
func TestBuilderAcc_default(t *testing.T) {
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: renderConfig(config),
Template: commonT.RenderConfig(config),
Check: checkDefault(t, config["vm_name"].(string), config["host"].(string), "datastore1"),
})
}
@ -33,7 +32,7 @@ func defaultConfig() map[string]interface{} {
"ssh_username": "root",
"ssh_password": "jetbrains",
}
config["vm_name"] = fmt.Sprintf("test-%v", rand.Intn(1000))
config["vm_name"] = commonT.NewVMName()
return config
}
@ -100,7 +99,7 @@ func TestBuilderAcc_artifact(t *testing.T) {
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: renderConfig(config),
Template: commonT.RenderConfig(config),
Check: checkArtifact(t),
})
}
@ -133,7 +132,7 @@ func folderConfig() string {
config := defaultConfig()
config["folder"] = "folder1/folder2"
config["linked_clone"] = true // speed up
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkFolder(t *testing.T, folder string) builderT.TestCheckFunc {
@ -171,7 +170,7 @@ func resourcePoolConfig() string {
config := defaultConfig()
config["resource_pool"] = "pool1/pool2"
config["linked_clone"] = true // speed up
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkResourcePool(t *testing.T, pool string) builderT.TestCheckFunc {
@ -208,7 +207,7 @@ func TestBuilderAcc_datastore(t *testing.T) {
func datastoreConfig() string {
config := defaultConfig()
config["template"] = "alpine-host4" // on esxi-4.vsphere65.test
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkDatastore(t *testing.T, name string) builderT.TestCheckFunc {
@ -251,7 +250,7 @@ func TestBuilderAcc_multipleDatastores(t *testing.T) {
func multipleDatastoresConfig() string {
config := defaultConfig()
config["host"] = "esxi-4.vsphere65.test" // host with 2 datastores
return renderConfig(config)
return commonT.RenderConfig(config)
}
func TestBuilderAcc_linkedClone(t *testing.T) {
@ -265,7 +264,7 @@ func TestBuilderAcc_linkedClone(t *testing.T) {
func linkedCloneConfig() string {
config := defaultConfig()
config["linked_clone"] = true
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkLinkedClone(t *testing.T) builderT.TestCheckFunc {
@ -303,7 +302,7 @@ func hardwareConfig() string {
config["RAM_reservation"] = 1024
config["linked_clone"] = true // speed up
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkHardware(t *testing.T) builderT.TestCheckFunc {
@ -358,7 +357,7 @@ func RAMReservationConfig() string {
config["RAM_reserve_all"] = true
config["linked_clone"] = true // speed up
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkRAMReservation(t *testing.T) builderT.TestCheckFunc {
@ -391,7 +390,7 @@ func sshKeyConfig() string {
config["ssh_password"] = ""
config["ssh_private_key_file"] = "../test-key.pem"
config["linked_clone"] = true // speed up
return renderConfig(config)
return commonT.RenderConfig(config)
}
func TestBuilderAcc_snapshot(t *testing.T) {
@ -405,7 +404,7 @@ func TestBuilderAcc_snapshot(t *testing.T) {
func snapshotConfig() string {
config := defaultConfig()
config["create_snapshot"] = true
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkSnapshot(t *testing.T) builderT.TestCheckFunc {
@ -439,7 +438,7 @@ func templateConfig() string {
config := defaultConfig()
config["convert_to_template"] = true
config["linked_clone"] = true // speed up
return renderConfig(config)
return commonT.RenderConfig(config)
}
func checkTemplate(t *testing.T) builderT.TestCheckFunc {
@ -460,22 +459,6 @@ func checkTemplate(t *testing.T) builderT.TestCheckFunc {
}
}
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 {
d, err := driver.NewDriver(&driver.ConnectConfig{
VCenterServer: "vcenter.vsphere65.test",

View File

@ -3,7 +3,6 @@ package clone
import (
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"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
@ -13,7 +12,7 @@ type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
CloneConfig `mapstructure:",squash"`
HardwareConfig `mapstructure:",squash"`
common.HardwareConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
CreateSnapshot bool `mapstructure:"create_snapshot"`
@ -24,14 +23,8 @@ type Config struct {
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
}
if err := common.DecodeConfig(c, &c.ctx, raws...); err != nil {
return nil, nil, err
}
errs := new(packer.MultiError)

View File

@ -1,42 +1,21 @@
package clone
import (
"github.com/mitchellh/multistep"
"github.com/hashicorp/packer/packer"
"fmt"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"github.com/mitchellh/multistep"
)
type HardwareConfig struct {
CPUs int32 `mapstructure:"CPUs"`
CPUReservation int64 `mapstructure:"CPU_reservation"`
CPULimit int64 `mapstructure:"CPU_limit"`
RAM int64 `mapstructure:"RAM"`
RAMReservation int64 `mapstructure:"RAM_reservation"`
RAMReserveAll bool `mapstructure:"RAM_reserve_all"`
DiskSize int64 `mapstructure:"disk_size"`
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
config *common.HardwareConfig
}
func (s *StepConfigureHardware) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(*driver.VirtualMachine)
if *s.config != (HardwareConfig{}) {
if *s.config != (common.HardwareConfig{}) {
ui.Say("Customizing hardware parameters...")
err := vm.Configure(&driver.HardwareConfig{

View File

@ -0,0 +1,24 @@
package common
import (
"github.com/mitchellh/multistep"
"errors"
)
func CheckRunStatus(state *multistep.BasicStateBag) error {
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return errors.New("Build was halted.")
}
return nil
}

14
common/decode_config.go Normal file
View File

@ -0,0 +1,14 @@
package common
import (
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/template/interpolate"
)
func DecodeConfig(cfg interface{}, ctx *interpolate.Context, raws ...interface{}) error {
err := config.Decode(cfg, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: ctx,
}, raws...)
return err
}

39
common/hardware_config.go Normal file
View File

@ -0,0 +1,39 @@
package common
import (
"fmt"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
)
type HardwareConfig struct {
CPUs int32 `mapstructure:"CPUs"`
CPUReservation int64 `mapstructure:"CPU_reservation"`
CPULimit int64 `mapstructure:"CPU_limit"`
RAM int64 `mapstructure:"RAM"`
RAMReservation int64 `mapstructure:"RAM_reservation"`
RAMReserveAll bool `mapstructure:"RAM_reserve_all"`
DiskSize int64 `mapstructure:"disk_size"`
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
}
func (c *HardwareConfig) ToDriverHardwareConfig() driver.HardwareConfig {
return driver.HardwareConfig{
CPUs: c.CPUs,
CPUReservation: c.CPUReservation,
CPULimit: c.CPULimit,
RAM: c.RAM,
RAMReservation: c.RAMReservation,
RAMReserveAll: c.RAMReserveAll,
DiskSize: c.DiskSize,
}
}

29
common/testing/utility.go Normal file
View File

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

70
iso/builder.go Normal file
View File

@ -0,0 +1,70 @@
package iso
import (
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"github.com/mitchellh/multistep"
)
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) {
state := new(multistep.BasicStateBag)
state.Put("comm", &b.config.Comm)
state.Put("hook", hook)
state.Put("ui", ui)
steps := []multistep.Step{}
steps = append(steps,
&common.StepConnect{
Config: &b.config.ConnectConfig,
},
&StepCreateVM{
config: &b.config.CreateConfig,
},
)
if b.config.CDRomConfig.ISOPath != "" {
steps = append(steps,
&StepAddCDRom{
config: &b.config.CDRomConfig,
},
)
}
// Run!
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
if err := common.CheckRunStatus(state); err != nil {
return nil, err
}
artifact := &common.Artifact{
Name: b.config.VMName,
VM: state.Get("vm").(*driver.VirtualMachine),
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
b.runner.Cancel()
}
}

33
iso/builder_acc_test.go Normal file
View File

@ -0,0 +1,33 @@
package iso
import (
builderT "github.com/hashicorp/packer/helper/builder/testing"
commonT "github.com/jetbrains-infra/packer-builder-vsphere/common/testing"
"testing"
)
func TestBuilderAcc_default(t *testing.T) {
config := defaultConfig()
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: commonT.RenderConfig(config),
})
}
func defaultConfig() map[string]interface{} {
config := map[string]interface{}{
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": true,
"host": "esxi-1.vsphere65.test",
"ssh_username": "root",
"ssh_password": "jetbrains",
"vm_name": commonT.NewVMName(),
}
return config
}

43
iso/config.go Normal file
View File

@ -0,0 +1,43 @@
package iso
import (
packerCommon "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
)
type Config struct {
packerCommon.PackerConfig `mapstructure:",squash"`
common.ConnectConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
common.ShutdownConfig `mapstructure:",squash"`
CreateSnapshot bool `mapstructure:"create_snapshot"`
ConvertToTemplate bool `mapstructure:"convert_to_template"`
CreateConfig `mapstructure:",squash"`
CDRomConfig `mapstructure:",squash"`
ctx interpolate.Context
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
if err := common.DecodeConfig(c, &c.ctx, raws...); err != nil {
return nil, nil, err
}
errs := new(packer.MultiError)
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
errs = packer.MultiErrorAppend(errs, c.CreateConfig.Prepare()...)
if len(errs.Errors) > 0 {
return nil, nil, errs
}
return c, nil, nil
}

40
iso/step_add_cdrom.go Normal file
View File

@ -0,0 +1,40 @@
package iso
import (
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"github.com/mitchellh/multistep"
)
type CDRomConfig struct {
ISOPath string `mapstructure:"iso_path"`
}
func (c *CDRomConfig) Prepare() []error {
var errs []error
return errs
}
type StepAddCDRom struct {
config *CDRomConfig
}
func (s *StepAddCDRom) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Adding CDRom ...")
vm := state.Get("vm").(*driver.VirtualMachine)
err := vm.AddCdrom(s.config.ISOPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepAddCDRom) Cleanup(state multistep.StateBag) {
// nothing
}

110
iso/step_create.go Normal file
View File

@ -0,0 +1,110 @@
package iso
import (
"fmt"
"github.com/hashicorp/packer/packer"
"github.com/jetbrains-infra/packer-builder-vsphere/common"
"github.com/jetbrains-infra/packer-builder-vsphere/driver"
"github.com/mitchellh/multistep"
)
type CreateConfig struct {
common.HardwareConfig `mapstructure:",squash"`
DiskThinProvisioned bool `mapstructure:"disk_thin_provisioned"`
DiskControllerType string `mapstructure:"disk_controller_type"`
VMName string `mapstructure:"vm_name"`
Folder string `mapstructure:"folder"`
Host string `mapstructure:"host"`
ResourcePool string `mapstructure:"resource_pool"`
Datastore string `mapstructure:"datastore"`
GuestOSType string `mapstructure:"guest_os_type"`
}
func (c *CreateConfig) Prepare() []error {
var errs []error
// needed to avoid changing the original config in case of errors
tmp := *c
// do recursive calls
errs = append(errs, tmp.HardwareConfig.Prepare()...)
// check for errors
if tmp.VMName == "" {
errs = append(errs, fmt.Errorf("Target VM name is required"))
}
if tmp.Host == "" {
errs = append(errs, fmt.Errorf("vSphere host is required"))
}
if len(errs) > 0 {
return errs
}
// set default values
if tmp.GuestOSType == "" {
tmp.GuestOSType = "otherGuest"
}
// change the original config
*c = tmp
return []error{}
}
type StepCreateVM struct {
config *CreateConfig
}
func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
d := state.Get("driver").(*driver.Driver)
ui.Say("Creating VM...")
vm, err := d.CreateVM(&driver.CreateConfig{
HardwareConfig: s.config.HardwareConfig.ToDriverHardwareConfig(),
DiskThinProvisioned: s.config.DiskThinProvisioned,
DiskControllerType: s.config.DiskControllerType,
Name: s.config.VMName,
Folder: s.config.Folder,
Host: s.config.Host,
ResourcePool: s.config.ResourcePool,
Datastore: s.config.Datastore,
GuestOS: s.config.GuestOSType,
})
if err != nil {
state.Put("error", 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())
}
}

4
iso/test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
export PACKER_ACC=1
go test -v "$@"

View File

@ -2,3 +2,4 @@
(cd driver && ./test.sh)
(cd clone && ./test.sh)
(cd iso && ./test.sh)