Revert "Merge pull request #7391 from carlpett/proxmox-builder"

This reverts commit b7d62b2ae0, reversing
changes made to c36eaf16f7.
This commit is contained in:
Adrien Delorme 2019-04-10 16:20:13 +02:00
parent 267e2253a2
commit 032527ecfe
20 changed files with 0 additions and 1790 deletions

View File

@ -1,44 +0,0 @@
package proxmox
import (
"fmt"
"log"
"strconv"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/packer"
)
type Artifact struct {
templateID int
proxmoxClient *proxmox.Client
}
// Artifact implements packer.Artifact
var _ packer.Artifact = &Artifact{}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (*Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
return strconv.Itoa(a.templateID)
}
func (a *Artifact) String() string {
return fmt.Sprintf("A template was created: %d", a.templateID)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying template: %d", a.templateID)
_, err := a.proxmoxClient.DeleteVm(proxmox.NewVmRef(a.templateID))
return err
}

View File

@ -1,117 +0,0 @@
package proxmox
import (
"fmt"
"time"
"unicode"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/common/bootcommand"
)
type proxmoxDriver struct {
client commandTyper
vmRef *proxmox.VmRef
specialMap map[string]string
runeMap map[rune]string
interval time.Duration
}
func NewProxmoxDriver(c commandTyper, vmRef *proxmox.VmRef, interval time.Duration) *proxmoxDriver {
// Mappings for packer shorthand to qemu qkeycodes
sMap := map[string]string{
"spacebar": "spc",
"bs": "backspace",
"del": "delete",
"return": "ret",
"enter": "ret",
"pageUp": "pgup",
"pageDown": "pgdn",
}
// Mappings for runes that need to be translated to special qkeycodes
// Taken from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps/en-us
rMap := map[rune]string{
// Clean mappings
' ': "spc",
'.': "dot",
',': "comma",
';': "semicolon",
'*': "asterisk",
'-': "minus",
'[': "bracket_left",
']': "bracket_right",
'=': "equal",
'\'': "apostrophe",
'`': "grave_accent",
'/': "slash",
'\\': "backslash",
'!': "shift-1", // "exclam"
'@': "shift-2", // "at"
'#': "shift-3", // "numbersign"
'$': "shift-4", // "dollar"
'%': "shift-5", // "percent"
'^': "shift-6", // "asciicircum"
'&': "shift-7", // "ampersand"
'(': "shift-9", // "parenleft"
')': "shift-0", // "parenright"
'{': "shift-bracket_left", // "braceleft"
'}': "shift-bracket_right", // "braceright"
'"': "shift-apostrophe", // "quotedbl"
'+': "shift-equal", // "plus"
'_': "shift-minus", // "underscore"
':': "shift-semicolon", // "colon"
'<': "shift-comma", // "less" is recognized, but seem to map to '/'?
'>': "shift-dot", // "greater"
'~': "shift-grave_accent", // "asciitilde"
'?': "shift-slash", // "question"
'|': "shift-backslash", // "bar"
}
return &proxmoxDriver{
client: c,
vmRef: vmRef,
specialMap: sMap,
runeMap: rMap,
interval: interval,
}
}
func (p *proxmoxDriver) SendKey(key rune, action bootcommand.KeyAction) error {
if special, ok := p.runeMap[key]; ok {
return p.send(special)
}
var keys string
if unicode.IsUpper(key) {
keys = fmt.Sprintf("shift-%c", unicode.ToLower(key))
} else {
keys = fmt.Sprintf("%c", key)
}
return p.send(keys)
}
func (p *proxmoxDriver) SendSpecial(special string, action bootcommand.KeyAction) error {
keys := special
if replacement, ok := p.specialMap[special]; ok {
keys = replacement
}
return p.send(keys)
}
func (p *proxmoxDriver) send(keys string) error {
res, err := p.client.MonitorCmd(p.vmRef, "sendkey "+keys)
if err != nil {
return err
}
if data, ok := res["data"].(string); ok && len(data) > 0 {
return fmt.Errorf("failed to send keys: %s", data)
}
time.Sleep(p.interval)
return nil
}
func (p *proxmoxDriver) Flush() error { return nil }

View File

@ -1,122 +0,0 @@
package proxmox
import (
"context"
"crypto/tls"
"fmt"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// The unique id for the builder
const BuilderId = "proxmox.builder"
type Builder struct {
config Config
runner multistep.Runner
proxmoxClient *proxmox.Client
}
// Builder implements packer.Builder
var _ packer.Builder = &Builder{}
var pluginVersion = "1.0.0"
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
config, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = *config
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
var err error
tlsConfig := &tls.Config{
InsecureSkipVerify: b.config.SkipCertValidation,
}
b.proxmoxClient, err = proxmox.NewClient(b.config.ProxmoxURL.String(), nil, tlsConfig)
if err != nil {
return nil, err
}
err = b.proxmoxClient.Login(b.config.Username, b.config.Password)
if err != nil {
return nil, err
}
// Set up the state
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("proxmoxClient", b.proxmoxClient)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&stepStartVM{},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&stepTypeBootCommand{
BootConfig: b.config.BootConfig,
Ctx: b.config.ctx,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: getVMIP,
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.Comm,
},
&stepConvertToTemplate{},
&stepFinalizeTemplateConfig{},
&stepSuccess{},
}
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
artifact := &Artifact{
templateID: state.Get("template_id").(int),
proxmoxClient: b.proxmoxClient,
}
return artifact, nil
}
func getVMIP(state multistep.StateBag) (string, error) {
c := state.Get("proxmoxClient").(*proxmox.Client)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
ifs, err := c.GetVmAgentNetworkInterfaces(vmRef)
if err != nil {
return "", err
}
// TODO: Do something smarter here? Allow specifying interface? Or address family?
// For now, just go for first non-loopback
for _, iface := range ifs {
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
}
return "", fmt.Errorf("Found no IP addresses on VM")
}

View File

@ -1,196 +0,0 @@
package proxmox
import (
"errors"
"fmt"
"log"
"net/url"
"os"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/common/uuid"
"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/mitchellh/mapstructure"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
RawBootKeyInterval string `mapstructure:"boot_key_interval"`
BootKeyInterval time.Duration ``
Comm communicator.Config `mapstructure:",squash"`
ProxmoxURLRaw string `mapstructure:"proxmox_url"`
ProxmoxURL *url.URL
SkipCertValidation bool `mapstructure:"insecure_skip_tls_verify"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Node string `mapstructure:"node"`
VMName string `mapstructure:"vm_name"`
VMID int `mapstructure:"vm_id"`
Memory int `mapstructure:"memory"`
Cores int `mapstructure:"cores"`
Sockets int `mapstructure:"sockets"`
OS string `mapstructure:"os"`
NICs []nicConfig `mapstructure:"network_adapters"`
Disks []diskConfig `mapstructure:"disks"`
ISOFile string `mapstructure:"iso_file"`
TemplateName string `mapstructure:"template_name"`
TemplateDescription string `mapstructure:"template_description"`
UnmountISO bool `mapstructure:"unmount_iso"`
ctx interpolate.Context
}
type nicConfig struct {
Model string `mapstructure:"model"`
MACAddress string `mapstructure:"mac_address"`
Bridge string `mapstructure:"bridge"`
VLANTag string `mapstructure:"vlan_tag"`
}
type diskConfig struct {
Type string `mapstructure:"type"`
StoragePool string `mapstructure:"storage_pool"`
StoragePoolType string `mapstructure:"storage_pool_type"`
Size string `mapstructure:"disk_size"`
CacheMode string `mapstructure:"cache_mode"`
DiskFormat string `mapstructure:"format"`
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
var errs *packer.MultiError
// Defaults
if c.ProxmoxURLRaw == "" {
c.ProxmoxURLRaw = os.Getenv("PROXMOX_URL")
}
if c.Username == "" {
c.Username = os.Getenv("PROXMOX_USERNAME")
}
if c.Password == "" {
c.Password = os.Getenv("PROXMOX_PASSWORD")
}
if c.RawBootKeyInterval == "" {
c.RawBootKeyInterval = os.Getenv(common.PackerKeyEnv)
}
if c.RawBootKeyInterval == "" {
c.BootKeyInterval = common.PackerKeyDefault
} else {
if interval, err := time.ParseDuration(c.RawBootKeyInterval); err == nil {
c.BootKeyInterval = interval
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Could not parse boot_key_interval: %v", err))
}
}
if c.VMName == "" {
// Default to packer-[time-ordered-uuid]
c.VMName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
if c.Memory < 16 {
log.Printf("Memory %d is too small, using default: 512", c.Memory)
c.Memory = 512
}
if c.Cores < 1 {
log.Printf("Number of cores %d is too small, using default: 1", c.Cores)
c.Cores = 1
}
if c.Sockets < 1 {
log.Printf("Number of sockets %d is too small, using default: 1", c.Sockets)
c.Sockets = 1
}
if c.OS == "" {
log.Printf("OS not set, using default 'other'")
c.OS = "other"
}
for idx := range c.NICs {
if c.NICs[idx].Model == "" {
log.Printf("NIC %d model not set, using default 'e1000'", idx)
c.NICs[idx].Model = "e1000"
}
}
for idx := range c.Disks {
if c.Disks[idx].Type == "" {
log.Printf("Disk %d type not set, using default 'scsi'", idx)
c.Disks[idx].Type = "scsi"
}
if c.Disks[idx].Size == "" {
log.Printf("Disk %d size not set, using default '20G'", idx)
c.Disks[idx].Size = "20G"
}
if c.Disks[idx].CacheMode == "" {
log.Printf("Disk %d cache mode not set, using default 'none'", idx)
c.Disks[idx].CacheMode = "none"
}
}
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
// Required configurations that will display errors if not set
if c.Username == "" {
errs = packer.MultiErrorAppend(errs, errors.New("username must be specified"))
}
if c.Password == "" {
errs = packer.MultiErrorAppend(errs, errors.New("password must be specified"))
}
if c.ProxmoxURLRaw == "" {
errs = packer.MultiErrorAppend(errs, errors.New("proxmox_url must be specified"))
}
if c.ProxmoxURL, err = url.Parse(c.ProxmoxURLRaw); err != nil {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("Could not parse proxmox_url: %s", err)))
}
if c.ISOFile == "" {
errs = packer.MultiErrorAppend(errs, errors.New("iso_file must be specified"))
}
if c.Node == "" {
errs = packer.MultiErrorAppend(errs, errors.New("node must be specified"))
}
for idx := range c.NICs {
if c.NICs[idx].Bridge == "" {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("network_adapters[%d].bridge must be specified", idx)))
}
}
for idx := range c.Disks {
if c.Disks[idx].StoragePool == "" {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("disks[%d].storage_pool must be specified", idx)))
}
if c.Disks[idx].StoragePoolType == "" {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("disks[%d].storage_pool_type must be specified", idx)))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
packer.LogSecretFilter.Set(c.Password)
return c, nil, nil
}

View File

@ -1,115 +0,0 @@
package proxmox
import (
"strings"
"testing"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template"
)
func TestRequiredParameters(t *testing.T) {
_, _, err := NewConfig(make(map[string]interface{}))
if err == nil {
t.Fatal("Expected empty configuration to fail")
}
errs, ok := err.(*packer.MultiError)
if !ok {
t.Fatal("Expected errors to be packer.MultiError")
}
required := []string{"username", "password", "proxmox_url", "iso_file", "node", "ssh_username"}
for _, param := range required {
found := false
for _, err := range errs.Errors {
if strings.Contains(err.Error(), param) {
found = true
break
}
}
if !found {
t.Errorf("Expected error about missing parameter %q", required)
}
}
}
func TestBasicExampleFromDocsIsValid(t *testing.T) {
const config = `{
"builders": [
{
"type": "proxmox",
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"insecure_skip_tls_verify": true,
"username": "apiuser@pve",
"password": "supersecret",
"node": "my-proxmox",
"network_adapters": [
{
"bridge": "vmbr0"
}
],
"disks": [
{
"type": "scsi",
"disk_size": "5G",
"storage_pool": "local-lvm",
"storage_pool_type": "lvm"
}
],
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
"http_directory":"config",
"boot_wait": "10s",
"boot_command": [
"<up><tab> ip=dhcp inst.cmdline inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/ks.cfg<enter>"
],
"ssh_username": "root",
"ssh_timeout": "15m",
"ssh_password": "packer",
"unmount_iso": true,
"template_name": "fedora-29",
"template_description": "Fedora 29-1.2, generated on {{ isotime \"2006-01-02T15:04:05Z\" }}"
}
]
}`
tpl, err := template.Parse(strings.NewReader(config))
if err != nil {
t.Fatal(err)
}
b := &Builder{}
warn, err := b.Prepare(tpl.Builders["proxmox"].Config)
if err != nil {
t.Fatal(err, warn)
}
// The example config does not set a number of optional fields. Validate that:
// Memory 0 is too small, using default: 512
// Number of cores 0 is too small, using default: 1
// Number of sockets 0 is too small, using default: 1
// OS not set, using default 'other'
// NIC 0 model not set, using default 'e1000'
// Disk 0 cache mode not set, using default 'none'
if b.config.Memory != 512 {
t.Errorf("Expected Memory to be 512, got %d", b.config.Memory)
}
if b.config.Cores != 1 {
t.Errorf("Expected Cores to be 1, got %d", b.config.Cores)
}
if b.config.Sockets != 1 {
t.Errorf("Expected Sockets to be 1, got %d", b.config.Sockets)
}
if b.config.OS != "other" {
t.Errorf("Expected OS to be 'other', got %s", b.config.OS)
}
if b.config.NICs[0].Model != "e1000" {
t.Errorf("Expected NIC model to be 'e1000', got %s", b.config.NICs[0].Model)
}
if b.config.Disks[0].CacheMode != "none" {
t.Errorf("Expected disk cache mode to be 'none', got %s", b.config.Disks[0].CacheMode)
}
}

View File

@ -1,53 +0,0 @@
package proxmox
import (
"context"
"fmt"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// stepConvertToTemplate takes the running VM configured in earlier steps, stops it, and
// converts it into a Proxmox template.
//
// It sets the template_id state which is used for Artifact lookup.
type stepConvertToTemplate struct{}
type templateConverter interface {
ShutdownVm(*proxmox.VmRef) (string, error)
CreateTemplate(*proxmox.VmRef) error
}
var _ templateConverter = &proxmox.Client{}
func (s *stepConvertToTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(templateConverter)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
ui.Say("Stopping VM")
_, err := client.ShutdownVm(vmRef)
if err != nil {
err := fmt.Errorf("Error converting VM to template, could not stop: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Converting VM to template")
err = client.CreateTemplate(vmRef)
if err != nil {
err := fmt.Errorf("Error converting VM to template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("template_id", vmRef.VmId())
return multistep.ActionContinue
}
func (s *stepConvertToTemplate) Cleanup(state multistep.StateBag) {}

View File

@ -1,103 +0,0 @@
package proxmox
import (
"context"
"fmt"
"testing"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type converterMock struct {
shutdownVm func(*proxmox.VmRef) (string, error)
createTemplate func(*proxmox.VmRef) error
}
func (m converterMock) ShutdownVm(r *proxmox.VmRef) (string, error) {
return m.shutdownVm(r)
}
func (m converterMock) CreateTemplate(r *proxmox.VmRef) error {
return m.createTemplate(r)
}
var _ templateConverter = converterMock{}
func TestConvertToTemplate(t *testing.T) {
cs := []struct {
name string
shutdownErr error
expectCallCreateTemplate bool
createTemplateErr error
expectedAction multistep.StepAction
expectTemplateIdSet bool
}{
{
name: "no errors returns continue and sets template id",
expectCallCreateTemplate: true,
expectedAction: multistep.ActionContinue,
expectTemplateIdSet: true,
},
{
name: "when shutdown fails, don't try to create template and halt",
shutdownErr: fmt.Errorf("failed to stop vm"),
expectCallCreateTemplate: false,
expectedAction: multistep.ActionHalt,
expectTemplateIdSet: false,
},
{
name: "when create template fails, halt",
expectCallCreateTemplate: true,
createTemplateErr: fmt.Errorf("failed to stop vm"),
expectedAction: multistep.ActionHalt,
expectTemplateIdSet: false,
},
}
const vmid = 123
for _, c := range cs {
t.Run(c.name, func(t *testing.T) {
converter := converterMock{
shutdownVm: func(r *proxmox.VmRef) (string, error) {
if r.VmId() != vmid {
t.Errorf("ShutdownVm called with unexpected id, expected %d, got %d", vmid, r.VmId())
}
return "", c.shutdownErr
},
createTemplate: func(r *proxmox.VmRef) error {
if r.VmId() != vmid {
t.Errorf("CreateTemplate called with unexpected id, expected %d, got %d", vmid, r.VmId())
}
if !c.expectCallCreateTemplate {
t.Error("Did not expect CreateTemplate to be called")
}
return c.createTemplateErr
},
}
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("vmRef", proxmox.NewVmRef(vmid))
state.Put("proxmoxClient", converter)
step := stepConvertToTemplate{}
action := step.Run(context.TODO(), state)
if action != c.expectedAction {
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
}
id, wasSet := state.GetOk("template_id")
if c.expectTemplateIdSet != wasSet {
t.Errorf("Expected template_id state present=%v was present=%v", c.expectTemplateIdSet, wasSet)
}
if c.expectTemplateIdSet && id != vmid {
t.Errorf("Expected template_id state to be set to %d, got %v", vmid, id)
}
})
}
}

View File

@ -1,72 +0,0 @@
package proxmox
import (
"context"
"fmt"
"strings"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// stepFinalizeTemplateConfig does any required modifications to the configuration _after_
// the VM has been converted into a template, such as updating name and description, or
// unmounting the installation ISO.
type stepFinalizeTemplateConfig struct{}
type templateFinalizer interface {
GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error)
SetVmConfig(*proxmox.VmRef, map[string]interface{}) (interface{}, error)
}
var _ templateFinalizer = &proxmox.Client{}
func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(templateFinalizer)
c := state.Get("config").(*Config)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
changes := make(map[string]interface{})
if c.TemplateName != "" {
changes["name"] = c.TemplateName
}
// During build, the description is "Packer ephemeral build VM", so if no description is
// set, we need to clear it
changes["description"] = c.TemplateDescription
if c.UnmountISO {
vmParams, err := client.GetVmConfig(vmRef)
if err != nil {
err := fmt.Errorf("Error fetching template config: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if vmParams["ide2"] == nil || !strings.HasSuffix(vmParams["ide2"].(string), "media=cdrom") {
err := fmt.Errorf("Cannot eject ISO from cdrom drive, ide2 is not present, or not a cdrom media")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
changes["ide2"] = "none,media=cdrom"
}
if len(changes) > 0 {
_, err := client.SetVmConfig(vmRef, changes)
if err != nil {
err := fmt.Errorf("Error updating template: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepFinalizeTemplateConfig) Cleanup(state multistep.StateBag) {}

View File

@ -1,151 +0,0 @@
package proxmox
import (
"context"
"fmt"
"testing"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type finalizerMock struct {
getConfig func() (map[string]interface{}, error)
setConfig func(map[string]interface{}) (string, error)
}
func (m finalizerMock) GetVmConfig(*proxmox.VmRef) (map[string]interface{}, error) {
return m.getConfig()
}
func (m finalizerMock) SetVmConfig(vmref *proxmox.VmRef, c map[string]interface{}) (interface{}, error) {
return m.setConfig(c)
}
var _ templateFinalizer = finalizerMock{}
func TestTemplateFinalize(t *testing.T) {
cs := []struct {
name string
builderConfig *Config
initialVMConfig map[string]interface{}
getConfigErr error
expectCallSetConfig bool
expectedVMConfig map[string]interface{}
setConfigErr error
expectedAction multistep.StepAction
}{
{
name: "empty config changes only description",
builderConfig: &Config{},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
expectedVMConfig: map[string]interface{}{
"name": nil,
"description": "",
"ide2": nil,
},
expectedAction: multistep.ActionContinue,
},
{
name: "all options",
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
expectedVMConfig: map[string]interface{}{
"name": "my-template",
"description": "some-description",
"ide2": "none,media=cdrom",
},
expectedAction: multistep.ActionContinue,
},
{
name: "no cd-drive with unmount=true should returns halt",
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide1": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: false,
expectedAction: multistep.ActionHalt,
},
{
name: "GetVmConfig error should return halt",
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
getConfigErr: fmt.Errorf("some error"),
expectCallSetConfig: false,
expectedAction: multistep.ActionHalt,
},
{
name: "SetVmConfig error should return halt",
builderConfig: &Config{
TemplateName: "my-template",
TemplateDescription: "some-description",
UnmountISO: true,
},
initialVMConfig: map[string]interface{}{
"name": "dummy",
"description": "Packer ephemeral build VM",
"ide2": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso,media=cdrom",
},
expectCallSetConfig: true,
setConfigErr: fmt.Errorf("some error"),
expectedAction: multistep.ActionHalt,
},
}
for _, c := range cs {
t.Run(c.name, func(t *testing.T) {
finalizer := finalizerMock{
getConfig: func() (map[string]interface{}, error) {
return c.initialVMConfig, c.getConfigErr
},
setConfig: func(cfg map[string]interface{}) (string, error) {
if !c.expectCallSetConfig {
t.Error("Did not expect SetVmConfig to be called")
}
for key, val := range c.expectedVMConfig {
if cfg[key] != val {
t.Errorf("Expected %q to be %q, got %q", key, val, cfg[key])
}
}
return "", c.setConfigErr
},
}
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("config", c.builderConfig)
state.Put("vmRef", proxmox.NewVmRef(1))
state.Put("proxmoxClient", finalizer)
step := stepFinalizeTemplateConfig{}
action := step.Run(context.TODO(), state)
if action != c.expectedAction {
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
}
})
}
}

View File

@ -1,150 +0,0 @@
package proxmox
import (
"context"
"fmt"
"log"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// stepStartVM takes the given configuration and starts a VM on the given Proxmox node.
//
// It sets the vmRef state which is used throughout the later steps to reference the VM
// in API calls.
type stepStartVM struct{}
func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("proxmoxClient").(*proxmox.Client)
c := state.Get("config").(*Config)
ui.Say("Creating VM")
config := proxmox.ConfigQemu{
Name: c.VMName,
Agent: "1",
Description: "Packer ephemeral build VM",
Memory: c.Memory,
QemuCores: c.Cores,
QemuSockets: c.Sockets,
QemuOs: c.OS,
QemuIso: c.ISOFile,
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
QemuDisks: generateProxmoxDisks(c.Disks),
}
if c.VMID == 0 {
ui.Say("No VM ID given, getting next free from Proxmox")
for n := 0; n < 5; n++ {
id, err := proxmox.MaxVmId(client)
if err != nil {
log.Printf("Error getting max used VM ID: %v (attempt %d/5)", err, n+1)
continue
}
c.VMID = id + 1
break
}
if c.VMID == 0 {
err := fmt.Errorf("Failed to get free VM ID")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
vmRef := proxmox.NewVmRef(c.VMID)
vmRef.SetNode(c.Node)
err := config.CreateVm(vmRef, client)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Store the vm id for later
state.Put("vmRef", vmRef)
ui.Say("Starting VM")
_, err = client.StartVm(vmRef)
if err != nil {
err := fmt.Errorf("Error starting VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func generateProxmoxNetworkAdapters(nics []nicConfig) proxmox.QemuDevices {
devs := make(proxmox.QemuDevices)
for idx := range nics {
devs[idx] = make(proxmox.QemuDevice)
setDeviceParamIfDefined(devs[idx], "model", nics[idx].Model)
setDeviceParamIfDefined(devs[idx], "macaddr", nics[idx].MACAddress)
setDeviceParamIfDefined(devs[idx], "bridge", nics[idx].Bridge)
setDeviceParamIfDefined(devs[idx], "tag", nics[idx].VLANTag)
}
return devs
}
func generateProxmoxDisks(disks []diskConfig) proxmox.QemuDevices {
devs := make(proxmox.QemuDevices)
for idx := range disks {
devs[idx] = make(proxmox.QemuDevice)
setDeviceParamIfDefined(devs[idx], "type", disks[idx].Type)
setDeviceParamIfDefined(devs[idx], "size", disks[idx].Size)
setDeviceParamIfDefined(devs[idx], "storage", disks[idx].StoragePool)
setDeviceParamIfDefined(devs[idx], "storage_type", disks[idx].StoragePoolType)
setDeviceParamIfDefined(devs[idx], "cache", disks[idx].CacheMode)
setDeviceParamIfDefined(devs[idx], "format", disks[idx].DiskFormat)
}
return devs
}
func setDeviceParamIfDefined(dev proxmox.QemuDevice, key, value string) {
if value != "" {
dev[key] = value
}
}
type startedVMCleaner interface {
StopVm(*proxmox.VmRef) (string, error)
DeleteVm(*proxmox.VmRef) (string, error)
}
var _ startedVMCleaner = &proxmox.Client{}
func (s *stepStartVM) Cleanup(state multistep.StateBag) {
vmRefUntyped, ok := state.GetOk("vmRef")
// If not ok, we probably errored out before creating the VM
if !ok {
return
}
vmRef := vmRefUntyped.(*proxmox.VmRef)
// The vmRef will actually refer to the created template if everything
// finished successfully, so in that case we shouldn't cleanup
if _, ok := state.GetOk("success"); ok {
return
}
client := state.Get("proxmoxClient").(startedVMCleaner)
ui := state.Get("ui").(packer.Ui)
// Destroy the server we just created
ui.Say("Stopping VM")
_, err := client.StopVm(vmRef)
if err != nil {
ui.Error(fmt.Sprintf("Error stop VM. Please stop and delete it manually: %s", err))
return
}
ui.Say("Deleting VM")
_, err = client.DeleteVm(vmRef)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting VM. Please delete it manually: %s", err))
return
}
}

View File

@ -1,108 +0,0 @@
package proxmox
import (
"fmt"
"testing"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type startedVMCleanerMock struct {
stopVm func() (string, error)
deleteVm func() (string, error)
}
func (m startedVMCleanerMock) StopVm(*proxmox.VmRef) (string, error) {
return m.stopVm()
}
func (m startedVMCleanerMock) DeleteVm(*proxmox.VmRef) (string, error) {
return m.deleteVm()
}
var _ startedVMCleaner = &startedVMCleanerMock{}
func TestCleanupStartVM(t *testing.T) {
cs := []struct {
name string
setVmRef bool
setSuccess bool
stopVMErr error
expectCallStopVM bool
deleteVMErr error
expectCallDeleteVM bool
}{
{
name: "when vmRef state is not set, nothing should happen",
setVmRef: false,
expectCallStopVM: false,
},
{
name: "when success state is set, nothing should happen",
setVmRef: true,
setSuccess: true,
expectCallStopVM: false,
},
{
name: "when not successful, vm should be stopped and deleted",
setVmRef: true,
setSuccess: false,
expectCallStopVM: true,
expectCallDeleteVM: true,
},
{
name: "if stopping fails, DeleteVm should not be called",
setVmRef: true,
setSuccess: false,
expectCallStopVM: true,
stopVMErr: fmt.Errorf("some error"),
expectCallDeleteVM: false,
},
}
for _, c := range cs {
t.Run(c.name, func(t *testing.T) {
var stopWasCalled, deleteWasCalled bool
cleaner := startedVMCleanerMock{
stopVm: func() (string, error) {
if !c.expectCallStopVM {
t.Error("Did not expect StopVm to be called")
}
stopWasCalled = true
return "", c.stopVMErr
},
deleteVm: func() (string, error) {
if !c.expectCallDeleteVM {
t.Error("Did not expect DeleteVm to be called")
}
deleteWasCalled = true
return "", c.deleteVMErr
},
}
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("proxmoxClient", cleaner)
if c.setVmRef {
state.Put("vmRef", proxmox.NewVmRef(1))
}
if c.setSuccess {
state.Put("success", "true")
}
step := stepStartVM{}
step.Cleanup(state)
if c.expectCallStopVM && !stopWasCalled {
t.Error("Expected StopVm to be called, but it wasn't")
}
if c.expectCallDeleteVM && !deleteWasCalled {
t.Error("Expected DeleteVm to be called, but it wasn't")
}
})
}
}

View File

@ -1,22 +0,0 @@
package proxmox
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
)
// stepSuccess runs after the full build has succeeded.
//
// It sets the success state, which ensures cleanup does not remove the finished template
type stepSuccess struct{}
func (s *stepSuccess) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
// We need to ensure stepStartVM.Cleanup doesn't delete the template (no
// difference between VMs and templates when deleting)
state.Put("success", true)
return multistep.ActionContinue
}
func (s *stepSuccess) Cleanup(state multistep.StateBag) {}

View File

@ -1,119 +0,0 @@
package proxmox
import (
"context"
"errors"
"fmt"
"log"
"net"
"time"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
// stepTypeBootCommand takes the started VM, and sends the keystrokes required to start
// the installation process such that Packer can later reach the VM over SSH/WinRM
type stepTypeBootCommand struct {
bootcommand.BootConfig
Ctx interpolate.Context
}
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort uint
}
type commandTyper interface {
MonitorCmd(*proxmox.VmRef, string) (map[string]interface{}, error)
}
var _ commandTyper = &proxmox.Client{}
func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
client := state.Get("proxmoxClient").(commandTyper)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
if len(s.BootCommand) == 0 {
log.Println("No boot command given, skipping")
return multistep.ActionContinue
}
if int64(s.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot", s.BootWait.String()))
select {
case <-time.After(s.BootWait):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
httpIP, err := hostIP()
if err != nil {
err := fmt.Errorf("Failed to determine host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
common.SetHTTPIP(httpIP)
s.Ctx.Data = &bootCommandTemplateData{
HTTPIP: httpIP,
HTTPPort: state.Get("http_port").(uint),
}
ui.Say("Typing the boot command")
d := NewProxmoxDriver(client, vmRef, c.BootKeyInterval)
command, err := interpolate.Render(s.FlatBootCommand(), &s.Ctx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
seq, err := bootcommand.GenerateExpressionSequence(command)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {
commonhelper.RemoveSharedStateFile("ip", "")
}
func hostIP() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", errors.New("No host IP found")
}

View File

@ -1,128 +0,0 @@
package proxmox
import (
"context"
"fmt"
"strings"
"testing"
"github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type commandTyperMock struct {
monitorCmd func(*proxmox.VmRef, string) (map[string]interface{}, error)
}
func (m commandTyperMock) MonitorCmd(ref *proxmox.VmRef, cmd string) (map[string]interface{}, error) {
return m.monitorCmd(ref, cmd)
}
var _ commandTyper = commandTyperMock{}
func TestTypeBootCommand(t *testing.T) {
cs := []struct {
name string
builderConfig *Config
expectCallMonitorCmd bool
monitorCmdErr error
monitorCmdRet map[string]interface{}
expectedKeysSent string
expectedAction multistep.StepAction
}{
{
name: "simple boot command is typed",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello"}}},
expectCallMonitorCmd: true,
expectedKeysSent: "hello",
expectedAction: multistep.ActionContinue,
},
{
name: "interpolated boot command",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello<enter>world"}}},
expectCallMonitorCmd: true,
expectedKeysSent: "helloretworld",
expectedAction: multistep.ActionContinue,
},
{
name: "merge multiple interpolated boot command",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"Hello World 2.0", "foo!bar@baz"}}},
expectCallMonitorCmd: true,
expectedKeysSent: "shift-hellospcshift-worldspc2dot0fooshift-1barshift-2baz",
expectedAction: multistep.ActionContinue,
},
{
name: "without boot command monitorcmd should not be called",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{}}},
expectCallMonitorCmd: false,
expectedAction: multistep.ActionContinue,
},
{
name: "invalid boot command template function",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"{{ foo }}"}}},
expectCallMonitorCmd: false,
expectedAction: multistep.ActionHalt,
},
{
// When proxmox (or Qemu, really) doesn't recognize the keycode we send, we get no error back, but
// a map {"data": "invalid parameter: X"}, where X is the keycode.
name: "invalid keys sent to proxmox",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"x"}}},
expectCallMonitorCmd: true,
monitorCmdRet: map[string]interface{}{"data": "invalid parameter: x"},
expectedKeysSent: "x",
expectedAction: multistep.ActionHalt,
},
{
name: "error in typing should return halt",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello"}}},
expectCallMonitorCmd: true,
monitorCmdErr: fmt.Errorf("some error"),
expectedKeysSent: "h",
expectedAction: multistep.ActionHalt,
},
}
for _, c := range cs {
t.Run(c.name, func(t *testing.T) {
accumulator := strings.Builder{}
typer := commandTyperMock{
monitorCmd: func(ref *proxmox.VmRef, cmd string) (map[string]interface{}, error) {
if !c.expectCallMonitorCmd {
t.Error("Did not expect MonitorCmd to be called")
}
if !strings.HasPrefix(cmd, "sendkey ") {
t.Errorf("Expected all commands to be sendkey, got %s", cmd)
}
accumulator.WriteString(strings.TrimPrefix(cmd, "sendkey "))
return c.monitorCmdRet, c.monitorCmdErr
},
}
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("config", c.builderConfig)
state.Put("http_port", uint(0))
state.Put("vmRef", proxmox.NewVmRef(1))
state.Put("proxmoxClient", typer)
step := stepTypeBootCommand{
c.builderConfig.BootConfig,
c.builderConfig.ctx,
}
action := step.Run(context.TODO(), state)
step.Cleanup(state)
if action != c.expectedAction {
t.Errorf("Expected action to be %v, got %v", c.expectedAction, action)
}
if c.expectedKeysSent != accumulator.String() {
t.Errorf("Expected keystrokes to be %q, got %q", c.expectedKeysSent, accumulator.String())
}
})
}
}

View File

@ -40,7 +40,6 @@ import (
parallelsisobuilder "github.com/hashicorp/packer/builder/parallels/iso"
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
proxmoxbuilder "github.com/hashicorp/packer/builder/proxmox"
qemubuilder "github.com/hashicorp/packer/builder/qemu"
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
@ -119,7 +118,6 @@ var Builders = map[string]packer.Builder{
"parallels-iso": new(parallelsisobuilder.Builder),
"parallels-pvm": new(parallelspvmbuilder.Builder),
"profitbricks": new(profitbricksbuilder.Builder),
"proxmox": new(proxmoxbuilder.Builder),
"qemu": new(qemubuilder.Builder),
"scaleway": new(scalewaybuilder.Builder),
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),

2
go.sum
View File

@ -20,8 +20,6 @@ github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 h1:K9I21XUHN
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591 h1:/P9HCl71+Eh6vDbKNyRu+rpIIR70UCZWNOGexVV3e6k=
github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591/go.mod h1:EHGzQGbwozJBj/4qj3WGrTJ0FqjgOTOxLQ0VNWvPn08=
github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d h1:igrCnHheXb+lZ1bW9Ths8JZZIjh9D4Vi/49JqiHE+cI=
github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f h1:jI4DIE5Vf4oRaHfthB0oRhU+yuYuoOTurDzwAlskP00=

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2017 <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,62 +0,0 @@
package proxmox
import (
"strconv"
"strings"
)
func inArray(arr []string, str string) bool {
for _, elem := range arr {
if elem == str {
return true
}
}
return false
}
func Itob(i int) bool {
if i == 1 {
return true
}
return false
}
// ParseSubConf - Parse standard sub-conf strings `key=value`.
func ParseSubConf(
element string,
separator string,
) (key string, value interface{}) {
if strings.Contains(element, separator) {
conf := strings.Split(element, separator)
key, value := conf[0], conf[1]
var interValue interface{}
// Make sure to add value in right type,
// because all subconfig are returned as strings from Proxmox API.
if iValue, err := strconv.ParseInt(value, 10, 64); err == nil {
interValue = int(iValue)
} else if bValue, err := strconv.ParseBool(value); err == nil {
interValue = bValue
} else {
interValue = value
}
return key, interValue
}
return
}
// ParseConf - Parse standard device conf string `key1=val1,key2=val2`.
func ParseConf(
kvString string,
confSeparator string,
subConfSeparator string,
) QemuDevice {
var confMap = QemuDevice{}
confList := strings.Split(kvString, confSeparator)
for _, item := range confList {
key, value := ParseSubConf(item, subConfSeparator)
confMap[key] = value
}
return confMap
}

2
vendor/modules.txt vendored
View File

@ -45,8 +45,6 @@ github.com/NaverCloudPlatform/ncloud-sdk-go/sdk
github.com/NaverCloudPlatform/ncloud-sdk-go/common
github.com/NaverCloudPlatform/ncloud-sdk-go/request
github.com/NaverCloudPlatform/ncloud-sdk-go/oauth
# github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d
github.com/Telmate/proxmox-api-go/proxmox
# github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f
github.com/aliyun/aliyun-oss-go-sdk/oss
# github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd

View File

@ -1,201 +0,0 @@
---
description: |
The proxmox Packer builder is able to create new images for use with
Proxmox VE. The builder takes an ISO source, runs any provisioning
necessary on the image after launching it, then creates a virtual machine
template.
layout: docs
page_title: 'Proxmox - Builders'
sidebar_current: 'docs-builders-proxmox'
---
# Proxmox Builder
Type: `proxmox`
The `proxmox` Packer builder is able to create new images for use with
[Proxmox](https://www.proxmox.com/en/proxmox-ve). The builder takes an ISO
image, runs any provisioning necessary on the image after launching it, then
creates a virtual machine template. This template can then be used as to
create new virtual machines within Proxmox.
The builder does *not* manage templates. Once it creates a template, it is up
to you to use it or delete it.
## Configuration Reference
There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
In addition to the options listed here, a
[communicator](/docs/templates/communicator.html) can be configured for this
builder.
### Required:
- `proxmox_url` (string) - URL to the Proxmox API, including the full path,
so `https://<server>:<port>/api2/json` for example.
Can also be set via the `PROXMOX_URL` environment variable.
- `username` (string) - Username when authenticating to Proxmox, including
the realm. For example `user@pve` to use the local Proxmox realm.
Can also be set via the `PROXMOX_USERNAME` environment variable.
- `password` (string) - Password for the user.
Can also be set via the `PROXMOX_PASSWORD` environment variable.
- `node` (string) - Which node in the Proxmox cluster to start the virtual
machine on during creation.
- `iso_file` (string) - Path to the ISO file to boot from, expressed as a
proxmox datastore path, for example
`local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso`
### Optional:
- `insecure_skip_tls_verify` (bool) - Skip validating the certificate.
- `vm_name` (string) - Name of the virtual machine during creation. If not
given, a random uuid will be used.
- `vm_id` (int) - The ID used to reference the virtual machine. This will
also be the ID of the final template. If not given, the next free ID on
the node will be used.
- `memory` (int) - How much memory, in megabytes, to give the virtual
machine. Defaults to `512`.
- `cores` (int) - How many CPU cores to give the virtual machine. Defaults
to `1`.
- `sockets` (int) - How many CPU sockets to give the virtual machine.
Defaults to `1`
- `os` (string) - The operating system. Can be `linux`, `windows`, `solaris`
or `other`. Defaults to `other`.
- `network_adapters` (array of objects) - Network adapters attached to the
virtual machine. Example:
```json
[
{
"model": "virtio",
"bridge": "vmbr0",
"vlan_tag": "10"
}
]
```
- `bridge` (string) - Required. Which Proxmox bridge to attach the
adapter to.
- `model` (string) - Model of the virtual network adapter. Can be
`rtl8139`, `ne2k_pci`, `e1000`, `pcnet`, `virtio`, `ne2k_isa`,
`i82551`, `i82557b`, `i82559er`, `vmxnet3`, `e1000-82540em`,
`e1000-82544gc` or `e1000-82545em`. Defaults to `e1000`.
- `mac_address` (string) - Give the adapter a specific MAC address. If
not set, defaults to a random MAC.
- `vlan_tag` (string) - If the adapter should tag packets. Defaults to
no tagging.
- `disks` (array of objects) - Disks attached to the virtual machine.
Example:
```json
[
{
"type": "scsi",
"disk_size": "5G",
"storage_pool": "local-lvm",
"storage_pool_type": "lvm"
}
]
```
- `storage_pool` (string) - Required. Name of the Proxmox storage pool
to store the virtual machine disk on. A `local-lvm` pool is allocated
by the installer, for example.
- `storage_pool_type` (string) - Required. The type of the pool, can
be `lvm`, `lvm-thin`, `zfs` or `directory`.
- `type` (string) - The type of disk. Can be `scsi`, `sata`, `virtio` or
`ide`. Defaults to `scsi`.
- `disk_size` (string) - The size of the disk, including a unit suffix, such
as `10G` to indicate 10 gigabytes.
- `cache_mode` (string) - How to cache operations to the disk. Can be
`none`, `writethrough`, `writeback`, `unsafe` or `directsync`.
Defaults to `none`.
- `format` (string) - The format of the file backing the disk. Can be
`raw`, `cow`, `qcow`, `qed`, `qcow2`, `vmdk` or `cloop`. Defaults to
`raw`.
- `template_name` (string) - Name of the template. Defaults to the generated
name used during creation.
- `template_description` (string) - Description of the template, visible in
the Proxmox interface.
- `unmount_iso` (bool) - If true, remove the mounted ISO from the template
after finishing. Defaults to `false`.
## Example: Fedora with kickstart
Here is a basic example creating a Fedora 29 server image with a Kickstart
file served with Packer's HTTP server. Note that the iso file needs to be
manually downloaded.
``` json
{
"variables": {
"username": "apiuser@pve",
"password": "supersecret"
},
"builders": [
{
"type": "proxmox",
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"insecure_skip_tls_verify": true,
"username": "{{user `username`}}",
"password": "{{user `password`}}",
"node": "my-proxmox",
"network_adapters": [
{
"bridge": "vmbr0"
}
],
"disks": [
{
"type": "scsi",
"disk_size": "5G",
"storage_pool": "local-lvm",
"storage_pool_type": "lvm"
}
],
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
"http_directory":"config",
"boot_wait": "10s",
"boot_command": [
"<up><tab> ip=dhcp inst.cmdline inst.ks=http://{{.HTTPIP}}:{{.HTTPPort}}/ks.cfg<enter>"
],
"ssh_username": "root",
"ssh_timeout": "15m",
"ssh_password": "packer",
"unmount_iso": true,
"template_name": "fedora-29",
"template_description": "Fedora 29-1.2, generated on {{ isotime \"2006-01-02T15:04:05Z\" }}"
}
]
}
```