Revert "Merge pull request #7391 from carlpett/proxmox-builder"
This reverts commitb7d62b2ae0
, reversing changes made toc36eaf16f7
.
This commit is contained in:
parent
267e2253a2
commit
032527ecfe
|
@ -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
|
||||
}
|
|
@ -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 }
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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")
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
2
go.sum
|
@ -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=
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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\" }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue