diff --git a/builder/proxmox/builder.go b/builder/proxmox/builder.go index c6077fd616d..b2387e1c005 100644 --- a/builder/proxmox/builder.go +++ b/builder/proxmox/builder.go @@ -71,8 +71,21 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack ResultKey: downloadPathKey, TargetPath: b.config.TargetPath, Url: b.config.ISOUrls, - }, + }} + + for idx := range b.config.AdditionalISOFiles { + steps = append(steps, &common.StepDownload{ + Checksum: b.config.AdditionalISOFiles[idx].ISOChecksum, + Description: "additional ISO", + Extension: b.config.AdditionalISOFiles[idx].TargetExtension, + ResultKey: b.config.AdditionalISOFiles[idx].downloadPathKey, + TargetPath: b.config.AdditionalISOFiles[idx].downloadPathKey, + Url: b.config.AdditionalISOFiles[idx].ISOUrls, + }) + } + steps = append(steps, &stepUploadISO{}, + &stepUploadAdditionalISOs{}, &stepStartVM{}, &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, @@ -96,7 +109,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack &stepConvertToTemplate{}, &stepFinalizeTemplateConfig{}, &stepSuccess{}, - } + ) + // Run the steps b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) b.runner.Run(ctx, state) diff --git a/builder/proxmox/config.go b/builder/proxmox/config.go index 3610379c3ce..f888a3c4eb0 100644 --- a/builder/proxmox/config.go +++ b/builder/proxmox/config.go @@ -1,4 +1,4 @@ -//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig +//go:generate mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig package proxmox @@ -8,6 +8,7 @@ import ( "log" "net/url" "os" + "strconv" "strings" "time" @@ -64,6 +65,8 @@ type Config struct { shouldUploadISO bool + AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"` + ctx interpolate.Context } @@ -87,6 +90,15 @@ type vgaConfig struct { Type string `mapstructure:"type"` Memory int `mapstructure:"memory"` } +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 +} func (c *Config) Prepare(raws ...interface{}) ([]string, error) { // Agent defaults to true @@ -183,6 +195,61 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("disk format must be specified for pool type %q", c.Disks[idx].StoragePoolType)) } } + 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")) + } + } if c.SCSIController == "" { log.Printf("SCSI controller not set, using default 'lsi'") c.SCSIController = "lsi" diff --git a/builder/proxmox/config.hcl2spec.go b/builder/proxmox/config.hcl2spec.go index f8d868f85ec..ac804f32e64 100644 --- a/builder/proxmox/config.hcl2spec.go +++ b/builder/proxmox/config.hcl2spec.go @@ -1,4 +1,4 @@ -// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig"; DO NOT EDIT. +// Code generated by "mapstructure-to-hcl2 -type Config,nicConfig,diskConfig,vgaConfig,storageConfig"; DO NOT EDIT. package proxmox import ( @@ -9,100 +9,101 @@ import ( // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` - PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` - PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` - PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` - PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` - PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` - HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"` - HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` - HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` - HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` - ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` - RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` - ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` - TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` - TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` - BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` - BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` - BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` - BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` - Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` - PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` - SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` - SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` - SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` - SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` - SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` - SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` - SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"` - SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"` - SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"` - SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"` - SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"` - SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"` - SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"` - SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"` - SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"` - SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"` - SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"` - SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"` - SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"` - SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"` - SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"` - SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"` - SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"` - SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"` - SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"` - SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"` - SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"` - SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"` - SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"` - SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"` - SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"` - SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"` - SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"` - SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"` - SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"` - SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"` - WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"` - WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"` - WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"` - WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"` - WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"` - WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"` - WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"` - WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"` - WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"` - ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"` - SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"` - Username *string `mapstructure:"username" cty:"username" hcl:"username"` - Password *string `mapstructure:"password" cty:"password" hcl:"password"` - Node *string `mapstructure:"node" cty:"node" hcl:"node"` - Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` - VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` - VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` - Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` - Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"` - CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"` - Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"` - OS *string `mapstructure:"os" cty:"os" hcl:"os"` - VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"` - NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"` - Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"` - ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` - ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` - Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"` - SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"` - Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"` - DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"` - TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"` - TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"` - UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"` - CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"` - CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"` + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"` + HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"` + HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"` + HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"` + ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` + RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` + TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` + BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` + BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` + BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` + BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"` + Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` + PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` + SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` + SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"` + SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"` + SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"` + SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"` + SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"` + SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"` + SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"` + SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"` + SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"` + SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"` + SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"` + SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"` + SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"` + SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"` + SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"` + SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"` + SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"` + SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"` + SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"` + SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"` + SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"` + SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"` + SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"` + SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"` + SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"` + SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"` + SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"` + SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"` + SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"` + SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"` + SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"` + SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"` + SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"` + SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"` + SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"` + WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"` + WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"` + WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"` + WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"` + WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"` + WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"` + WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"` + WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"` + WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"` + ProxmoxURLRaw *string `mapstructure:"proxmox_url" cty:"proxmox_url" hcl:"proxmox_url"` + SkipCertValidation *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"` + Username *string `mapstructure:"username" cty:"username" hcl:"username"` + Password *string `mapstructure:"password" cty:"password" hcl:"password"` + Node *string `mapstructure:"node" cty:"node" hcl:"node"` + Pool *string `mapstructure:"pool" cty:"pool" hcl:"pool"` + VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` + VMID *int `mapstructure:"vm_id" cty:"vm_id" hcl:"vm_id"` + Memory *int `mapstructure:"memory" cty:"memory" hcl:"memory"` + Cores *int `mapstructure:"cores" cty:"cores" hcl:"cores"` + CPUType *string `mapstructure:"cpu_type" cty:"cpu_type" hcl:"cpu_type"` + Sockets *int `mapstructure:"sockets" cty:"sockets" hcl:"sockets"` + OS *string `mapstructure:"os" cty:"os" hcl:"os"` + VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"` + NICs []FlatnicConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"` + Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"` + ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` + ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` + Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent" hcl:"qemu_agent"` + SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller" hcl:"scsi_controller"` + Onboot *bool `mapstructure:"onboot" cty:"onboot" hcl:"onboot"` + DisableKVM *bool `mapstructure:"disable_kvm" cty:"disable_kvm" hcl:"disable_kvm"` + TemplateName *string `mapstructure:"template_name" cty:"template_name" hcl:"template_name"` + TemplateDescription *string `mapstructure:"template_description" cty:"template_description" hcl:"template_description"` + UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso" hcl:"unmount_iso"` + CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"` + CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"` + AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` } // FlatMapstructure returns a new FlatConfig. @@ -211,6 +212,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false}, "cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false}, "cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false}, + "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())}, } return s } @@ -281,6 +283,45 @@ func (*FlatnicConfig) HCL2Spec() map[string]hcldec.Spec { return s } +// FlatstorageConfig is an auto-generated flat version of storageConfig. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatstorageConfig struct { + ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` + RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` + TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"` + TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"` + Device *string `mapstructure:"device" cty:"device" hcl:"device"` + ISOFile *string `mapstructure:"iso_file" cty:"iso_file" hcl:"iso_file"` + ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool" hcl:"iso_storage_pool"` + Unmount *bool `mapstructure:"unmount" cty:"unmount" hcl:"unmount"` +} + +// FlatMapstructure returns a new FlatstorageConfig. +// FlatstorageConfig is an auto-generated flat version of storageConfig. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*storageConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatstorageConfig) +} + +// HCL2Spec returns the hcl spec of a storageConfig. +// This spec is used by HCL to read the fields of storageConfig. +// The decoded values from this spec will then be applied to a FlatstorageConfig. +func (*FlatstorageConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, + "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, + "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, + "iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false}, + "iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false}, + "device": &hcldec.AttrSpec{Name: "device", Type: cty.String, Required: false}, + "iso_file": &hcldec.AttrSpec{Name: "iso_file", Type: cty.String, Required: false}, + "iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false}, + "unmount": &hcldec.AttrSpec{Name: "unmount", Type: cty.Bool, Required: false}, + } + return s +} + // FlatvgaConfig is an auto-generated flat version of vgaConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatvgaConfig struct { diff --git a/builder/proxmox/step_finalize_template_config.go b/builder/proxmox/step_finalize_template_config.go index 1e2deba6947..570c77b0bfc 100644 --- a/builder/proxmox/step_finalize_template_config.go +++ b/builder/proxmox/step_finalize_template_config.go @@ -93,6 +93,29 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St } } + if len(c.AdditionalISOFiles) > 0 { + 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 + } + for idx := range c.AdditionalISOFiles { + cdrom := c.AdditionalISOFiles[idx].Device + if c.AdditionalISOFiles[idx].Unmount { + if vmParams[cdrom] == nil || !strings.Contains(vmParams[cdrom].(string), "media=cdrom") { + err := fmt.Errorf("Cannot eject ISO from cdrom drive, %s is not present or not a cdrom media", cdrom) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + changes[cdrom] = "none,media=cdrom" + } else { + changes[cdrom] = c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom" + } + } + } if len(changes) > 0 { _, err := client.SetVmConfig(vmRef, changes) if err != nil { diff --git a/builder/proxmox/step_start_vm.go b/builder/proxmox/step_start_vm.go index f44fbf31508..c128fe44c35 100644 --- a/builder/proxmox/step_start_vm.go +++ b/builder/proxmox/step_start_vm.go @@ -91,6 +91,19 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist // instance id inside of the provisioners, used in step_provision. state.Put("instance_id", vmRef) + for idx := range c.AdditionalISOFiles { + params := map[string]interface{}{ + c.AdditionalISOFiles[idx].Device: c.AdditionalISOFiles[idx].ISOFile + ",media=cdrom", + } + _, err = client.SetVmConfig(vmRef, params) + if err != nil { + err := fmt.Errorf("Error configuring VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + ui.Say("Starting VM") _, err = client.StartVm(vmRef) if err != nil { diff --git a/builder/proxmox/step_upload_additional_isos.go b/builder/proxmox/step_upload_additional_isos.go new file mode 100644 index 00000000000..b867032c09f --- /dev/null +++ b/builder/proxmox/step_upload_additional_isos.go @@ -0,0 +1,68 @@ +package proxmox + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +// stepUploadAdditionalISOs uploads all additional ISO files that are mountet +// to the VM +type stepUploadAdditionalISOs struct{} + +type uploader interface { + Upload(node string, storage string, contentType string, filename string, file io.Reader) error +} + +var _ uploader = &proxmox.Client{} + +func (s *stepUploadAdditionalISOs) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + client := state.Get("proxmoxClient").(uploader) + c := state.Get("config").(*Config) + + for idx := range c.AdditionalISOFiles { + if !c.AdditionalISOFiles[idx].shouldUploadISO { + state.Put("additional_iso_files", c.AdditionalISOFiles) + continue + } + + p := state.Get(c.AdditionalISOFiles[idx].downloadPathKey).(string) + if p == "" { + err := fmt.Errorf("Path to downloaded ISO was empty") + state.Put("erroe", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + isoPath, _ := filepath.EvalSymlinks(p) + r, err := os.Open(isoPath) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + filename := filepath.Base(c.AdditionalISOFiles[idx].ISOConfig.ISOUrls[0]) + err = client.Upload(c.Node, c.AdditionalISOFiles[idx].ISOStoragePool, "iso", filename, r) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + isoStoragePath := fmt.Sprintf("%s:iso/%s", c.AdditionalISOFiles[idx].ISOStoragePool, filename) + c.AdditionalISOFiles[idx].ISOFile = isoStoragePath + state.Put("additional_iso_files", c.AdditionalISOFiles) + } + return multistep.ActionContinue +} + +func (s *stepUploadAdditionalISOs) Cleanup(state multistep.StateBag) { +} diff --git a/builder/proxmox/step_upload_iso.go b/builder/proxmox/step_upload_iso.go index deba1672f8e..de947075ced 100644 --- a/builder/proxmox/step_upload_iso.go +++ b/builder/proxmox/step_upload_iso.go @@ -3,7 +3,6 @@ package proxmox import ( "context" "fmt" - "io" "os" "path/filepath" @@ -15,10 +14,6 @@ import ( // stepUploadISO uploads an ISO file to Proxmox so we can boot from it type stepUploadISO struct{} -type uploader interface { - Upload(node string, storage string, contentType string, filename string, file io.Reader) error -} - var _ uploader = &proxmox.Client{} func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -58,7 +53,6 @@ func (s *stepUploadISO) Run(ctx context.Context, state multistep.StateBag) multi isoStoragePath := fmt.Sprintf("%s:iso/%s", c.ISOStoragePool, filename) state.Put("iso_file", isoStoragePath) - return multistep.ActionContinue } diff --git a/website/pages/docs/builders/proxmox.mdx b/website/pages/docs/builders/proxmox.mdx index 58df331a380..c9a2679a376 100644 --- a/website/pages/docs/builders/proxmox.mdx +++ b/website/pages/docs/builders/proxmox.mdx @@ -1,4 +1,4 @@ ---- + 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 @@ -140,7 +140,7 @@ builder. - `firewall` (bool) - If the interface should be protected by the firewall. Defaults to `false`. - + - `packet_queues` (int) - Number of packet queues to be used on the device. Values greater than 1 indicate that the multiqueue feature is activated. For best performance, set this to the number of cores available to the @@ -212,6 +212,39 @@ builder. - `cloud_init_storage_pool` - (string) - Name of the Proxmox storage pool to store the Cloud-Init CDROM on. If not given, the storage pool of the boot device will be used. +- `additional_iso_files` (array of objects) - Additional ISO files attached to the virtual machine. + Example: + + ```json + [ + { + "device": "scsi5", + "iso_file": "local:iso/virtio-win-0.1.185.iso", + "unmount": true, + "iso_checksum": "af2b3cc9fa7905dea5e58d31508d75bba717c2b0d5553962658a47aebc9cc386" + } + ] + ``` + - `device` (string) - Bus type and bus index that the ISO will be mounted on. Can be `ideX`, + `sataX` or `scsiX`. + For `ide` the bus index ranges from 0 to 3, for `sata` form 0 to 5 and for + `scsi` from 0 to 30. + Defaults to `ide3` since `ide2` is generelly the boot drive. + + - `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`. + Either `iso_file` OR `iso_url` must be specifed. + + - `iso_url` (string) - URL to an ISO file to upload to Proxmox, and then + boot from. Either `iso_file` OR `iso_url` must be specifed. + + - `iso_storage_pool` (string) - Proxmox storage pool onto which to upload + the ISO file. + + - `iso_checksum` (string) - Checksum of the ISO file. + - `unmount` (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