diff --git a/builder/proxmox/config.go b/builder/proxmox/config.go index 22cb6786d..41288cb7a 100644 --- a/builder/proxmox/config.go +++ b/builder/proxmox/config.go @@ -53,6 +53,7 @@ type Config struct { Agent bool `mapstructure:"qemu_agent"` SCSIController string `mapstructure:"scsi_controller"` Onboot bool `mapstructure:"onboot"` + DisableKVM bool `mapstructure:"disable_kvm"` TemplateName string `mapstructure:"template_name"` TemplateDescription string `mapstructure:"template_description"` diff --git a/builder/proxmox/config.hcl2spec.go b/builder/proxmox/config.hcl2spec.go index 73a70b6a1..5136e933f 100644 --- a/builder/proxmox/config.hcl2spec.go +++ b/builder/proxmox/config.hcl2spec.go @@ -96,6 +96,7 @@ type FlatConfig struct { 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"` @@ -202,6 +203,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false}, "scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false}, "onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false}, + "disable_kvm": &hcldec.AttrSpec{Name: "disable_kvm", Type: cty.Bool, Required: false}, "template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false}, "template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false}, "unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false}, diff --git a/builder/proxmox/config_test.go b/builder/proxmox/config_test.go index e9889ba38..1c4f03552 100644 --- a/builder/proxmox/config_test.go +++ b/builder/proxmox/config_test.go @@ -98,6 +98,7 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { // Agent not set, default is true // SCSI controller not set, using default 'lsi' // Firewall toggle not set, using default: 0 + // Disable KVM not set, using default: 0 if b.config.Memory != 512 { t.Errorf("Expected Memory to be 512, got %d", b.config.Memory) @@ -126,6 +127,9 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) { if b.config.Agent != true { t.Errorf("Expected Agent to be true, got %t", b.config.Agent) } + if b.config.DisableKVM != false { + t.Errorf("Expected Disable KVM toggle to be false, got %t", b.config.DisableKVM) + } if b.config.SCSIController != "lsi" { t.Errorf("Expected SCSI controller to be 'lsi', got %s", b.config.SCSIController) } diff --git a/builder/proxmox/step_start_vm.go b/builder/proxmox/step_start_vm.go index 63d961125..132c383d4 100644 --- a/builder/proxmox/step_start_vm.go +++ b/builder/proxmox/step_start_vm.go @@ -27,12 +27,18 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist agent = 0 } + kvm := true + if c.DisableKVM { + kvm = false + } + isoFile := state.Get("iso_file").(string) ui.Say("Creating VM") config := proxmox.ConfigQemu{ Name: c.VMName, Agent: agent, + QemuKVM: kvm, Boot: "cdn", // Boot priority, c:CDROM -> d:Disk -> n:Network QemuCpu: c.CPUType, Description: "Packer ephemeral build VM", diff --git a/go.mod b/go.mod index 4c3a3e66f..6e67f6c77 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0 github.com/PuerkitoBio/goquery v1.5.0 // indirect github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect - github.com/Telmate/proxmox-api-go v0.0.0-20200225212220-a29566462efd + github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887 github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af // indirect github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f diff --git a/go.sum b/go.sum index bb506bb41..a11563e17 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/Telmate/proxmox-api-go v0.0.0-20200225212220-a29566462efd h1:Moss3RtB00h4omKW+leNGIGIfmHcnkZPTJ5d0A0fY14= -github.com/Telmate/proxmox-api-go v0.0.0-20200225212220-a29566462efd/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= +github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887 h1:Q65o4V0g/KR1sSUZIMf4m1rShb7f1tVHuEt30hfnh2A= +github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887/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/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go index 787e7497f..f7fa27da5 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go @@ -13,6 +13,7 @@ import ( "mime/multipart" "net" "net/http" + "os" "regexp" "strconv" "strings" @@ -722,7 +723,28 @@ func (c *Client) DeleteVMDisks( } func (c *Client) Upload(node string, storage string, contentType string, filename string, file io.Reader) error { - body, mimetype, err := createUploadBody(contentType, filename, file) + var doStreamingIO bool + var fileSize int64 + var contentLength int64 + + if f, ok := file.(*os.File); ok { + doStreamingIO = true + fileInfo, err := f.Stat() + if err != nil { + return err + } + fileSize = fileInfo.Size() + } + + var body io.Reader + var mimetype string + var err error + + if doStreamingIO { + body, mimetype, contentLength, err = createStreamedUploadBody(contentType, filename, fileSize, file) + } else { + body, mimetype, err = createUploadBody(contentType, filename, file) + } if err != nil { return err } @@ -735,6 +757,10 @@ func (c *Client) Upload(node string, storage string, contentType string, filenam req.Header.Add("Content-Type", mimetype) req.Header.Add("Accept", "application/json") + if doStreamingIO { + req.ContentLength = contentLength + } + resp, err := c.session.Do(req) if err != nil { return err @@ -780,6 +806,38 @@ func createUploadBody(contentType string, filename string, r io.Reader) (io.Read return &buf, w.FormDataContentType(), nil } +// createStreamedUploadBody - Use MultiReader to create the multipart body from the file reader, +// avoiding allocation of large files in memory before upload (useful e.g. for Windows ISOs). +func createStreamedUploadBody(contentType string, filename string, fileSize int64, r io.Reader) (io.Reader, string, int64, error) { + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + err := w.WriteField("content", contentType) + if err != nil { + return nil, "", 0, err + } + + _, err = w.CreateFormFile("filename", filename) + if err != nil { + return nil, "", 0, err + } + + headerSize := buf.Len() + + err = w.Close() + if err != nil { + return nil, "", 0, err + } + + mr := io.MultiReader(bytes.NewReader(buf.Bytes()[:headerSize]), + r, + bytes.NewReader(buf.Bytes()[headerSize:])) + + contentLength := int64(buf.Len()) + fileSize + + return mr, w.FormDataContentType(), contentLength, nil +} + // getStorageAndVolumeName - Extract disk storage and disk volume, since disk name is saved // in Proxmox with its storage. func getStorageAndVolumeName( diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_lxc.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_lxc.go index e0138690e..5060d64ee 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_lxc.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_lxc.go @@ -313,7 +313,9 @@ func (config configLxc) CreateLxc(vmr *VmRef, client *Client) (err error) { // comma separated list of "key=value" pairs featuresParam := QemuDeviceParam{} featuresParam = featuresParam.createDeviceParam(config.Features, nil) - paramMap["features"] = strings.Join(featuresParam, ",") + if len(featuresParam) > 0 { + paramMap["features"] = strings.Join(featuresParam, ",") + } // build list of mountpoints // this does the same as for the feature list diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go index 43f0a1920..e57ee6bdf 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go @@ -38,6 +38,7 @@ type ConfigQemu struct { QemuVcpus int `json:"vcpus"` QemuCpu string `json:"cpu"` QemuNuma bool `json:"numa"` + QemuKVM bool `json:"kvm"` Hotplug string `json:"hotplug"` QemuIso string `json:"iso"` FullClone *int `json:"fullclone"` @@ -94,6 +95,7 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { "cores": config.QemuCores, "cpu": config.QemuCpu, "numa": config.QemuNuma, + "kvm": config.QemuKVM, "hotplug": config.Hotplug, "memory": config.Memory, "boot": config.Boot, @@ -107,11 +109,11 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { if config.Balloon >= 1 { params["balloon"] = config.Balloon } - + if config.QemuVcpus >= 1 { params["vcpus"] = config.QemuVcpus } - + if vmr.pool != "" { params["pool"] = vmr.pool } @@ -214,6 +216,7 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { "cores": config.QemuCores, "cpu": config.QemuCpu, "numa": config.QemuNuma, + "kvm": config.QemuKVM, "hotplug": config.Hotplug, "memory": config.Memory, "boot": config.Boot, @@ -231,13 +234,13 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { } else { deleteParams = append(deleteParams, "balloon") } - + if config.QemuVcpus >= 1 { configParams["vcpus"] = config.QemuVcpus } else { deleteParams = append(deleteParams, "vcpus") } - + if config.BootDisk != "" { configParams["bootdisk"] = config.BootDisk } @@ -307,11 +310,11 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { if config.Ipconfig2 != "" { configParams["ipconfig2"] = config.Ipconfig2 } - + if len(deleteParams) > 0 { configParams["delete"] = strings.Join(deleteParams, ", ") } - + _, err = client.SetVmConfig(vmr, configParams) if err != nil { log.Print(err) @@ -326,7 +329,7 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { } func NewConfigQemuFromJson(io io.Reader) (config *ConfigQemu, err error) { - config = &ConfigQemu{QemuVlanTag: -1} + config = &ConfigQemu{QemuVlanTag: -1, QemuKVM: true} err = json.NewDecoder(io).Decode(config) if err != nil { log.Fatal(err) @@ -449,6 +452,10 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e if _, isSet := vmConfig["bootdisk"]; isSet { bootdisk = vmConfig["bootdisk"].(string) } + kvm := true + if _, isSet := vmConfig["kvm"]; isSet { + kvm = Itob(int(vmConfig["kvm"].(float64))) + } scsihw := "lsi" if _, isSet := vmConfig["scsihw"]; isSet { scsihw = vmConfig["scsihw"].(string) @@ -470,6 +477,7 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e QemuSockets: int(sockets), QemuCpu: cpu, QemuNuma: numa, + QemuKVM: kvm, Hotplug: hotplug, QemuVlanTag: -1, Boot: boot, @@ -481,12 +489,12 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e QemuNetworks: QemuDevices{}, QemuSerials: QemuDevices{}, } - + if balloon >= 1 { - config.Balloon = int(balloon); + config.Balloon = int(balloon) } if vcpus >= 1 { - config.QemuVcpus = int(vcpus); + config.QemuVcpus = int(vcpus) } if vmConfig["ide2"] != nil { diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go index 819bc6903..2b6f58c44 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go @@ -65,7 +65,9 @@ func ParamsToBody(params map[string]interface{}) (body []byte) { default: v = fmt.Sprintf("%v", intrV) } - vals.Set(k, v) + if v != "" { + vals.Set(k, v) + } } body = bytes.NewBufferString(vals.Encode()).Bytes() return diff --git a/vendor/modules.txt b/vendor/modules.txt index 29f67e2b9..38055e719 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -69,7 +69,7 @@ github.com/NaverCloudPlatform/ncloud-sdk-go-v2/services/server github.com/PuerkitoBio/goquery # github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d github.com/StackExchange/wmi -# github.com/Telmate/proxmox-api-go v0.0.0-20200225212220-a29566462efd +# github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887 github.com/Telmate/proxmox-api-go/proxmox # github.com/agext/levenshtein v1.2.1 github.com/agext/levenshtein diff --git a/website/pages/docs/builders/proxmox.mdx b/website/pages/docs/builders/proxmox.mdx index 1dd34d189..3e6669923 100644 --- a/website/pages/docs/builders/proxmox.mdx +++ b/website/pages/docs/builders/proxmox.mdx @@ -193,6 +193,8 @@ builder. then `qemu-guest-agent` must be installed on the guest. When disabled, then `ssh_host` should be used. Defaults to `true`. +- `disable_kvm` (boolean) - Disables KVM hardware virtualization. Defaults to `false`. + - `scsi_controller` (string) - The SCSI controller model to emulate. Can be `lsi`, `lsi53c810`, `virtio-scsi-pci`, `virtio-scsi-single`, `megasas`, or `pvscsi`. Defaults to `lsi`.