2020-08-31 04:48:24 -04:00
//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig
2019-10-14 10:43:59 -04:00
2019-04-11 12:52:21 -04:00
package proxmox
import (
"errors"
"fmt"
"log"
"net/url"
"os"
2020-08-31 04:48:24 -04:00
"strconv"
2020-02-26 02:44:27 -05:00
"strings"
2019-04-11 12:52:21 -04:00
"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" `
2020-01-04 16:50:35 -05:00
common . ISOConfig ` mapstructure:",squash" `
2019-04-11 12:52:21 -04:00
bootcommand . BootConfig ` mapstructure:",squash" `
2019-10-31 10:49:34 -04:00
BootKeyInterval time . Duration ` mapstructure:"boot_key_interval" `
2019-04-11 12:52:21 -04:00
Comm communicator . Config ` mapstructure:",squash" `
ProxmoxURLRaw string ` mapstructure:"proxmox_url" `
2019-10-14 10:10:35 -04:00
proxmoxURL * url . URL
2019-04-11 12:52:21 -04:00
SkipCertValidation bool ` mapstructure:"insecure_skip_tls_verify" `
Username string ` mapstructure:"username" `
Password string ` mapstructure:"password" `
Node string ` mapstructure:"node" `
2019-07-10 16:04:10 -04:00
Pool string ` mapstructure:"pool" `
2019-04-11 12:52:21 -04:00
VMName string ` mapstructure:"vm_name" `
VMID int ` mapstructure:"vm_id" `
2019-10-06 05:14:04 -04:00
Memory int ` mapstructure:"memory" `
Cores int ` mapstructure:"cores" `
2019-10-08 04:45:15 -04:00
CPUType string ` mapstructure:"cpu_type" `
2019-10-06 05:14:04 -04:00
Sockets int ` mapstructure:"sockets" `
OS string ` mapstructure:"os" `
2020-03-13 21:22:47 -04:00
VGA vgaConfig ` mapstructure:"vga" `
2019-10-06 05:14:04 -04:00
NICs [ ] nicConfig ` mapstructure:"network_adapters" `
Disks [ ] diskConfig ` mapstructure:"disks" `
ISOFile string ` mapstructure:"iso_file" `
2020-01-04 16:50:35 -05:00
ISOStoragePool string ` mapstructure:"iso_storage_pool" `
2019-10-06 05:14:04 -04:00
Agent bool ` mapstructure:"qemu_agent" `
SCSIController string ` mapstructure:"scsi_controller" `
2020-03-22 20:20:02 -04:00
Onboot bool ` mapstructure:"onboot" `
2020-07-15 13:47:24 -04:00
DisableKVM bool ` mapstructure:"disable_kvm" `
2019-04-11 12:52:21 -04:00
TemplateName string ` mapstructure:"template_name" `
TemplateDescription string ` mapstructure:"template_description" `
UnmountISO bool ` mapstructure:"unmount_iso" `
2020-04-10 17:19:33 -04:00
CloudInit bool ` mapstructure:"cloud_init" `
CloudInitStoragePool string ` mapstructure:"cloud_init_storage_pool" `
2020-01-04 16:50:35 -05:00
shouldUploadISO bool
2020-08-31 04:48:24 -04:00
AdditionalISOFiles [ ] storageConfig ` mapstructure:"additional_iso_files" `
2019-04-11 12:52:21 -04:00
ctx interpolate . Context
}
type nicConfig struct {
2020-07-15 18:07:02 -04:00
Model string ` mapstructure:"model" `
PacketQueues int ` mapstructure:"packet_queues" `
MACAddress string ` mapstructure:"mac_address" `
Bridge string ` mapstructure:"bridge" `
VLANTag string ` mapstructure:"vlan_tag" `
Firewall bool ` mapstructure:"firewall" `
2019-04-11 12:52:21 -04:00
}
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" `
}
2020-03-13 21:22:47 -04:00
type vgaConfig struct {
Type string ` mapstructure:"type" `
Memory int ` mapstructure:"memory" `
}
2020-08-31 04:48:24 -04:00
type storageConfig struct {
common . ISOConfig ` mapstructure:",squash" `
Device string ` mapstructure:"device" `
ISOFile string ` mapstructure:"iso_file" `
ISOStoragePool string ` mapstructure:"iso_storage_pool" `
Unmount bool ` mapstructure:"unmount" `
shouldUploadISO bool
downloadPathKey string
}
2019-04-11 12:52:21 -04:00
2019-12-17 05:25:56 -05:00
func ( c * Config ) Prepare ( raws ... interface { } ) ( [ ] string , error ) {
2019-04-30 14:23:34 -04:00
// Agent defaults to true
c . Agent = true
2020-04-10 17:19:33 -04:00
// Do not add a cloud-init cdrom by default
c . CloudInit = false
2019-04-11 12:52:21 -04:00
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 {
2019-12-17 05:25:56 -05:00
return nil , err
2019-04-11 12:52:21 -04:00
}
var errs * packer . MultiError
2020-01-04 16:50:35 -05:00
warnings := make ( [ ] string , 0 )
2019-04-11 12:52:21 -04:00
// 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" )
}
2019-10-31 10:49:34 -04:00
if c . BootKeyInterval == 0 && os . Getenv ( common . PackerKeyEnv ) != "" {
var err error
c . BootKeyInterval , err = time . ParseDuration ( os . Getenv ( common . PackerKeyEnv ) )
if err != nil {
errs = packer . MultiErrorAppend ( errs , err )
}
2019-04-11 12:52:21 -04:00
}
2019-10-31 10:49:34 -04:00
if c . BootKeyInterval == 0 {
2019-09-09 16:33:48 -04:00
c . BootKeyInterval = 5 * time . Millisecond
2019-04-11 12:52:21 -04:00
}
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
}
2019-10-06 06:39:53 -04:00
if c . CPUType == "" {
log . Printf ( "CPU type not set, using default 'kvm64'" )
c . CPUType = "kvm64"
}
2019-04-11 12:52:21 -04:00
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"
}
2020-06-15 08:00:32 -04:00
// For any storage pool types which aren't in rxStorageTypes in proxmox-api/proxmox/config_qemu.go:890
// (currently zfspool|lvm|rbd|cephfs), the format parameter is mandatory. Make sure this is still up to date
2019-09-08 12:40:29 -04:00
// when updating the vendored code!
2020-06-15 08:00:32 -04:00
if ! contains ( [ ] string { "zfspool" , "lvm" , "rbd" , "cephfs" } , c . Disks [ idx ] . StoragePoolType ) && c . Disks [ idx ] . DiskFormat == "" {
2020-07-16 13:19:22 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "disk format must be specified for pool type %q" , c . Disks [ idx ] . StoragePoolType ) )
2019-09-08 12:40:29 -04:00
}
2019-04-11 12:52:21 -04:00
}
2020-08-31 04:48:24 -04:00
for idx := range c . AdditionalISOFiles {
// Check AdditionalISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c . AdditionalISOFiles [ idx ] . ISOFile != "" {
c . AdditionalISOFiles [ idx ] . shouldUploadISO = false
} else {
c . AdditionalISOFiles [ idx ] . downloadPathKey = "downloaded_additional_iso_path_" + strconv . Itoa ( idx )
isoWarnings , isoErrors := c . AdditionalISOFiles [ idx ] . ISOConfig . Prepare ( & c . ctx )
errs = packer . MultiErrorAppend ( errs , isoErrors ... )
warnings = append ( warnings , isoWarnings ... )
c . AdditionalISOFiles [ idx ] . shouldUploadISO = true
}
if c . AdditionalISOFiles [ idx ] . Device == "" {
log . Printf ( "AdditionalISOFile %d Device not set, using default 'ide3'" , idx )
c . AdditionalISOFiles [ idx ] . Device = "ide3"
}
if strings . HasPrefix ( c . AdditionalISOFiles [ idx ] . Device , "ide" ) {
busnumber , err := strconv . Atoi ( c . AdditionalISOFiles [ idx ] . Device [ 3 : ] )
if err != nil {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "%s is not a valid bus index" , c . AdditionalISOFiles [ idx ] . Device [ 3 : ] ) )
}
if busnumber == 2 {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "IDE bus 2 is used by boot ISO" ) )
}
if busnumber > 3 {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "IDE bus index can't be higher than 3" ) )
}
}
if strings . HasPrefix ( c . AdditionalISOFiles [ idx ] . Device , "sata" ) {
busnumber , err := strconv . Atoi ( c . AdditionalISOFiles [ idx ] . Device [ 4 : ] )
if err != nil {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "%s is not a valid bus index" , c . AdditionalISOFiles [ idx ] . Device [ 4 : ] ) )
}
if busnumber > 5 {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "SATA bus index can't be higher than 5" ) )
}
}
if strings . HasPrefix ( c . AdditionalISOFiles [ idx ] . Device , "scsi" ) {
busnumber , err := strconv . Atoi ( c . AdditionalISOFiles [ idx ] . Device [ 4 : ] )
if err != nil {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "%s is not a valid bus index" , c . AdditionalISOFiles [ idx ] . Device [ 4 : ] ) )
}
if busnumber > 30 {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "SCSI bus index can't be higher than 30" ) )
}
}
if ( c . AdditionalISOFiles [ idx ] . ISOFile == "" && len ( c . AdditionalISOFiles [ idx ] . ISOConfig . ISOUrls ) == 0 ) || ( c . AdditionalISOFiles [ idx ] . ISOFile != "" && len ( c . AdditionalISOFiles [ idx ] . ISOConfig . ISOUrls ) != 0 ) {
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "either iso_file or iso_url, but not both, must be specified for AdditionalISO file %s" , c . AdditionalISOFiles [ idx ] . Device ) )
}
if len ( c . ISOConfig . ISOUrls ) != 0 && c . ISOStoragePool == "" {
errs = packer . MultiErrorAppend ( errs , errors . New ( "when specifying iso_url, iso_storage_pool must also be specified" ) )
}
}
2019-10-06 05:14:04 -04:00
if c . SCSIController == "" {
log . Printf ( "SCSI controller not set, using default 'lsi'" )
c . SCSIController = "lsi"
}
2019-04-11 12:52:21 -04:00
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 ) ... )
2020-01-04 16:50:35 -05:00
// Check ISO config
// Either a pre-uploaded ISO should be referenced in iso_file, OR a URL
// (possibly to a local file) to an ISO file that will be downloaded and
// then uploaded to Proxmox.
if c . ISOFile != "" {
c . shouldUploadISO = false
} else {
isoWarnings , isoErrors := c . ISOConfig . Prepare ( & c . ctx )
errs = packer . MultiErrorAppend ( errs , isoErrors ... )
warnings = append ( warnings , isoWarnings ... )
c . shouldUploadISO = true
}
if ( c . ISOFile == "" && len ( c . ISOConfig . ISOUrls ) == 0 ) || ( c . ISOFile != "" && len ( c . ISOConfig . ISOUrls ) != 0 ) {
errs = packer . MultiErrorAppend ( errs , errors . New ( "either iso_file or iso_url, but not both, must be specified" ) )
}
if len ( c . ISOConfig . ISOUrls ) != 0 && c . ISOStoragePool == "" {
errs = packer . MultiErrorAppend ( errs , errors . New ( "when specifying iso_url, iso_storage_pool must also be specified" ) )
}
2019-04-11 12:52:21 -04:00
// 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" ) )
}
2019-10-14 10:10:35 -04:00
if c . proxmoxURL , err = url . Parse ( c . ProxmoxURLRaw ) ; err != nil {
2020-07-16 13:19:22 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "Could not parse proxmox_url: %s" , err ) )
2019-04-11 12:52:21 -04:00
}
if c . Node == "" {
errs = packer . MultiErrorAppend ( errs , errors . New ( "node must be specified" ) )
}
2020-02-26 02:44:27 -05:00
if strings . ContainsAny ( c . TemplateName , " " ) {
errs = packer . MultiErrorAppend ( errs , errors . New ( "template_name must not contain spaces" ) )
}
2019-04-11 12:52:21 -04:00
for idx := range c . NICs {
if c . NICs [ idx ] . Bridge == "" {
2020-07-16 13:19:22 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "network_adapters[%d].bridge must be specified" , idx ) )
2019-04-11 12:52:21 -04:00
}
2020-07-15 18:07:02 -04:00
if c . NICs [ idx ] . Model != "virtio" && c . NICs [ idx ] . PacketQueues > 0 {
2020-07-16 13:19:22 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "network_adapters[%d].packet_queues can only be set for 'virtio' driver" , idx ) )
2019-04-11 12:52:21 -04:00
}
}
for idx := range c . Disks {
if c . Disks [ idx ] . StoragePool == "" {
2020-07-16 13:19:22 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "disks[%d].storage_pool must be specified" , idx ) )
2019-04-11 12:52:21 -04:00
}
if c . Disks [ idx ] . StoragePoolType == "" {
2020-07-16 13:19:22 -04:00
errs = packer . MultiErrorAppend ( errs , fmt . Errorf ( "disks[%d].storage_pool_type must be specified" , idx ) )
2019-04-11 12:52:21 -04:00
}
}
if errs != nil && len ( errs . Errors ) > 0 {
2019-12-17 05:25:56 -05:00
return nil , errs
2019-04-11 12:52:21 -04:00
}
packer . LogSecretFilter . Set ( c . Password )
2019-12-17 05:25:56 -05:00
return nil , nil
2019-04-11 12:52:21 -04:00
}
2019-09-08 12:40:29 -04:00
func contains ( haystack [ ] string , needle string ) bool {
for _ , candidate := range haystack {
if candidate == needle {
return true
}
}
return false
}