diff --git a/builder/proxmox/builder.go b/builder/proxmox/builder.go index 131dfbb48..2b12308e1 100644 --- a/builder/proxmox/builder.go +++ b/builder/proxmox/builder.go @@ -46,7 +46,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack return nil, err } - err = b.proxmoxClient.Login(b.config.Username, b.config.Password) + err = b.proxmoxClient.Login(b.config.Username, b.config.Password, "") if err != nil { return nil, err } diff --git a/builder/proxmox/step_start_vm.go b/builder/proxmox/step_start_vm.go index 551174ea1..82358efb2 100644 --- a/builder/proxmox/step_start_vm.go +++ b/builder/proxmox/step_start_vm.go @@ -30,6 +30,8 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist config := proxmox.ConfigQemu{ Name: c.VMName, Agent: agent, + Boot: "cdn", // Boot priority, c:CDROM -> d:Disk -> n:Network + QemuCpu: "host", Description: "Packer ephemeral build VM", Memory: c.Memory, QemuCores: c.Cores, @@ -142,7 +144,7 @@ func (s *stepStartVM) Cleanup(state multistep.StateBag) { 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)) + ui.Error(fmt.Sprintf("Error stopping VM. Please stop and delete it manually: %s", err)) return } diff --git a/go.mod b/go.mod index a132d598c..32556cece 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 // indirect github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591 - github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4 + github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60 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 4fc7f942b..0f660370d 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591 h 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-20190614181158-26cd147831a4 h1:o//09WenT9BNcQypCYfOBfRe5gtLUvUfTPq0xQqPMEI= github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= +github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60 h1:iEmbIRk4brAP3wevhCr5MGAqxHUbbIDHvE+6D1/7pRA= +github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/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/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e h1:/8wOj52pewmIX/8d5eVO3t7Rr3astkBI/ruyg4WNqRo= 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 75ebf1a8a..d8acfa33f 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go @@ -31,6 +31,7 @@ type Client struct { ApiUrl string Username string Password string + Otp string } // VmRef - virtual machine ref parts @@ -38,6 +39,7 @@ type Client struct { type VmRef struct { vmId int node string + pool string vmType string } @@ -46,11 +48,19 @@ func (vmr *VmRef) SetNode(node string) { return } +func (vmr *VmRef) SetPool(pool string) { + vmr.pool = pool +} + func (vmr *VmRef) SetVmType(vmType string) { vmr.vmType = vmType return } +func (vmr *VmRef) GetVmType() (string) { + return vmr.vmType +} + func (vmr *VmRef) VmId() int { return vmr.vmId } @@ -59,6 +69,10 @@ func (vmr *VmRef) Node() string { return vmr.node } +func (vmr *VmRef) Pool() string { + return vmr.pool +} + func NewVmRef(vmId int) (vmr *VmRef) { vmr = &VmRef{vmId: vmId, node: "", vmType: ""} return @@ -73,10 +87,11 @@ func NewClient(apiUrl string, hclient *http.Client, tls *tls.Config) (client *Cl return client, err } -func (c *Client) Login(username string, password string) (err error) { +func (c *Client) Login(username string, password string, otp string) (err error) { c.Username = username c.Password = password - return c.session.Login(username, password) + c.Otp = otp + return c.session.Login(username, password, otp) } func (c *Client) GetJsonRetryable(url string, data *map[string]interface{}, tries int) error { @@ -275,10 +290,26 @@ func (c *Client) MonitorCmd(vmr *VmRef, command string) (monitorRes map[string]i reqbody := ParamsToBody(map[string]interface{}{"command": command}) url := fmt.Sprintf("/nodes/%s/%s/%d/monitor", vmr.node, vmr.vmType, vmr.vmId) resp, err := c.session.Post(url, nil, nil, &reqbody) + if err != nil { + return nil, err + } monitorRes, err = ResponseJSON(resp) return } +func (c *Client) Sendkey(vmr *VmRef, qmKey string) error { + err := c.CheckVmRef(vmr) + if err != nil { + return err + } + reqbody := ParamsToBody(map[string]interface{}{"key": qmKey}) + url := fmt.Sprintf("/nodes/%s/%s/%d/sendkey", vmr.node, vmr.vmType, vmr.vmId) + // No return, even for errors: https://bugzilla.proxmox.com/show_bug.cgi?id=2275 + _, err = c.session.Put(url, nil, nil, &reqbody) + + return err +} + // WaitForCompletion - poll the API for task completion func (c *Client) WaitForCompletion(taskResponse map[string]interface{}) (waitExitStatus string, err error) { if taskResponse["errors"] != nil { @@ -416,6 +447,29 @@ func (c *Client) CreateQemuVm(node string, vmParams map[string]interface{}) (exi return } +func (c *Client) CreateLxcContainer(node string, vmParams map[string]interface{}) (exitStatus string, err error) { + reqbody := ParamsToBody(vmParams) + url := fmt.Sprintf("/nodes/%s/lxc", node) + var resp *http.Response + resp, err = c.session.Post(url, nil, nil, &reqbody) + defer resp.Body.Close() + if err != nil { + // This might not work if we never got a body. We'll ignore errors in trying to read, + // but extract the body if possible to give any error information back in the exitStatus + b, _ := ioutil.ReadAll(resp.Body) + exitStatus = string(b) + return exitStatus, err + } + + taskResponse, err := ResponseJSON(resp) + if err != nil { + return "", err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + + return +} + func (c *Client) CloneQemuVm(vmr *VmRef, vmParams map[string]interface{}) (exitStatus string, err error) { reqbody := ParamsToBody(vmParams) url := fmt.Sprintf("/nodes/%s/qemu/%d/clone", vmr.node, vmr.vmId) @@ -457,6 +511,21 @@ func (c *Client) SetVmConfig(vmr *VmRef, vmParams map[string]interface{}) (exitS return } +// SetLxcConfig - send config options +func (c *Client) SetLxcConfig(vmr *VmRef, vmParams map[string]interface{}) (exitStatus interface{}, err error) { + reqbody := ParamsToBody(vmParams) + url := fmt.Sprintf("/nodes/%s/%s/%d/config", vmr.node, vmr.vmType, vmr.vmId) + resp, err := c.session.Put(url, nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return nil, err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + } + return +} + func (c *Client) ResizeQemuDisk(vmr *VmRef, disk string, moreSizeGB int) (exitStatus interface{}, err error) { // PUT //disk:virtio0 @@ -478,6 +547,23 @@ func (c *Client) ResizeQemuDisk(vmr *VmRef, disk string, moreSizeGB int) (exitSt return } +func (c *Client) MoveQemuDisk(vmr *VmRef, disk string, storage string) (exitStatus interface{}, err error) { + if disk == "" { + disk = "virtio0" + } + reqbody := ParamsToBody(map[string]interface{}{"disk": disk, "storage": storage, "delete": true}) + url := fmt.Sprintf("/nodes/%s/%s/%d/move_disk", vmr.node, vmr.vmType, vmr.vmId) + resp, err := c.session.Post(url, nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return nil, err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + } + return +} + // GetNextID - Get next free VMID func (c *Client) GetNextID(currentID int) (nextID int, err error) { var data map[string]interface{} 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 new file mode 100644 index 000000000..58393f0e3 --- /dev/null +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_lxc.go @@ -0,0 +1,433 @@ +package proxmox + +import ( + "encoding/json" + "fmt" + "io" + "log" + "strings" + "strconv" +) + +// LXC options for the Proxmox API +type configLxc struct { + Ostemplate string `json:"ostemplate"` + Arch string `json:"arch"` + BWLimit int `json:"bwlimit,omitempty"` + CMode string `json:"cmode"` + Console bool `json:"console"` + Cores int `json:"cores,omitempty"` + CPULimit int `json:"cpulimit"` + CPUUnits int `json:"cpuunits"` + Description string `json:"description,omitempty"` + Features QemuDevice `json:"features,omitempty"` + Force bool `json:"force,omitempty"` + Hookscript string `json:"hookscript,omitempty"` + Hostname string `json:"hostname,omitempty"` + IgnoreUnpackErrors bool `json:"ignore-unpack-errors,omitempty"` + Lock string `json:"lock,omitempty"` + Memory int `json:"memory"` + Mountpoints QemuDevices `json:"mountpoints,omitempty"` + Nameserver string `json:"nameserver,omitempty"` + Networks QemuDevices `json:"networks,omitempty"` + OnBoot bool `json:"onboot"` + OsType string `json:"ostype,omitempty"` + Password string `json:"password,omitempty"` + Pool string `json:"pool,omitempty"` + Protection bool `json:"protection"` + Restore bool `json:"restore,omitempty"` + RootFs string `json:"rootfs,omitempty"` + SearchDomain string `json:"searchdomain,omitempty"` + SSHPublicKeys string `json:"ssh-public-keys,omitempty"` + Start bool `json:"start"` + Startup string `json:"startup,omitempty"` + Storage string `json:"storage"` + Swap int `json:"swap"` + Template bool `json:"template,omitempty"` + Tty int `json:"tty"` + Unique bool `json:"unique,omitempty"` + Unprivileged bool `json:"unprivileged"` + Unused []string `json:"unused,omitempty"` +} + +func NewConfigLxc() (configLxc) { + return configLxc{ + Arch: "amd64", + CMode: "tty", + Console: true, + CPULimit: 0, + CPUUnits: 1024, + Memory: 512, + OnBoot: false, + Protection: false, + Start: false, + Storage: "local", + Swap: 512, + Template: false, + Tty: 2, + Unprivileged: false, + } +} + +func NewConfigLxcFromJson(io io.Reader) (config configLxc, err error) { + config = NewConfigLxc() + err = json.NewDecoder(io).Decode(config) + if err != nil { + log.Fatal(err) + return config, err + } + log.Println(config) + return +} + +func NewConfigLxcFromApi(vmr *VmRef, client *Client) (config *configLxc, err error) { + // prepare json map to receive the information from the api + var lxcConfig map[string]interface{} + lxcConfig, err = client.GetVmConfig(vmr) + if err != nil { + log.Fatal(err) + return nil, err + } + + // prepare a new lxc config to store and return\ + // the information from api + newConfig := NewConfigLxc() + config = &newConfig + + arch := "" + if _, isSet := lxcConfig["arch"]; isSet { + arch = lxcConfig["arch"].(string) + } + cmode := "" + if _, isSet := lxcConfig["cmode"]; isSet { + cmode = lxcConfig["cmode"].(string) + } + console := true + if _, isSet := lxcConfig["console"]; isSet { + console = Itob(int(lxcConfig["console"].(float64))) + } + cores := 1 + if _, isSet := lxcConfig["cores"]; isSet { + cores = int(lxcConfig["cores"].(float64)) + } + cpulimit := 0 + if _, isSet := lxcConfig["cpulimit"]; isSet { + cpulimit, _ = strconv.Atoi(lxcConfig["cpulimit"].(string)) + } + cpuunits := 1024 + if _, isSet := lxcConfig["cpuunits"]; isSet { + cpuunits = int(lxcConfig["cpuunits"].(float64)) + } + description := "" + if _, isSet := lxcConfig["description"]; isSet { + description = lxcConfig["description"].(string) + } + + // add features, if any + if features, isSet := lxcConfig["features"]; isSet { + featureList := strings.Split(features.(string), ",") + + // create new device map to store features + featureMap := QemuDevice{} + // add all features to device map + featureMap.readDeviceConfig(featureList) + // prepare empty feature map + if config.Features == nil { + config.Features = QemuDevice{} + } + // and device config to networks + if len(featureMap) > 0 { + config.Features = featureMap + } + } + + hookscript := "" + if _, isSet := lxcConfig["hookscript"]; isSet { + hookscript = lxcConfig["hookscript"].(string) + } + hostname := "" + if _, isSet := lxcConfig["hostname"]; isSet { + hostname = lxcConfig["hostname"].(string) + } + lock := "" + if _, isSet := lxcConfig["lock"]; isSet { + lock = lxcConfig["lock"].(string) + } + memory := 512 + if _, isSet := lxcConfig["memory"]; isSet { + memory = int(lxcConfig["memory"].(float64)) + } + + // add mountpoints + mpNames := []string{} + + for k, _ := range lxcConfig { + if mpName:= rxMpName.FindStringSubmatch(k); len(mpName) > 0 { + mpNames = append(mpNames, mpName[0]) + } + } + + for _, mpName := range mpNames { + mpConfStr := lxcConfig[mpName] + mpConfList := strings.Split(mpConfStr.(string), ",") + + id := rxDeviceID.FindStringSubmatch(mpName) + mpID, _ := strconv.Atoi(id[0]) + // add mp id + mpConfMap := QemuDevice{ + "id": mpID, + } + // add rest of device config + mpConfMap.readDeviceConfig(mpConfList) + // prepare empty mountpoint map + if config.Mountpoints == nil { + config.Mountpoints = QemuDevices{} + } + // and device config to mountpoints + if len(mpConfMap) > 0 { + config.Mountpoints[mpID] = mpConfMap + } + } + + nameserver := "" + if _, isSet := lxcConfig["nameserver"]; isSet { + nameserver = lxcConfig["nameserver"].(string) + } + + // add networks + nicNames := []string{} + + for k, _ := range lxcConfig { + if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 { + nicNames = append(nicNames, nicName[0]) + } + } + + for _, nicName := range nicNames { + nicConfStr := lxcConfig[nicName] + nicConfList := strings.Split(nicConfStr.(string), ",") + + id := rxDeviceID.FindStringSubmatch(nicName) + nicID, _ := strconv.Atoi(id[0]) + // add nic id + nicConfMap := QemuDevice{ + "id": nicID, + } + // add rest of device config + nicConfMap.readDeviceConfig(nicConfList) + // prepare empty network map + if config.Networks == nil { + config.Networks = QemuDevices{} + } + // and device config to networks + if len(nicConfMap) > 0 { + config.Networks[nicID] = nicConfMap + } + } + + onboot := false + if _, isSet := lxcConfig["onboot"]; isSet { + onboot = Itob(int(lxcConfig["onboot"].(float64))) + } + ostype := "" + if _, isSet := lxcConfig["ostype"]; isSet { + ostype = lxcConfig["ostype"].(string) + } + protection := false + if _, isSet := lxcConfig["protection"]; isSet { + protection = Itob(int(lxcConfig["protection"].(float64))) + } + rootfs := "" + if _, isSet := lxcConfig["rootfs"]; isSet { + rootfs = lxcConfig["rootfs"].(string) + } + searchdomain := "" + if _, isSet := lxcConfig["searchdomain"]; isSet { + searchdomain = lxcConfig["searchdomain"].(string) + } + startup := "" + if _, isSet := lxcConfig["startup"]; isSet { + startup = lxcConfig["startup"].(string) + } + swap := 512 + if _, isSet := lxcConfig["swap"]; isSet { + swap = int(lxcConfig["swap"].(float64)) + } + template := false + if _, isSet := lxcConfig["template"]; isSet { + template = Itob(int(lxcConfig["template"].(float64))) + } + tty := 2 + if _, isSet := lxcConfig["tty"]; isSet { + tty = int(lxcConfig["tty"].(float64)) + } + unprivileged := false + if _, isset := lxcConfig["unprivileged"]; isset { + unprivileged = Itob(int(lxcConfig["unprivileged"].(float64))) + } + var unused []string + if _, isset := lxcConfig["unused"]; isset { + unused = lxcConfig["unused"].([]string) + } + + config.Arch = arch + config.CMode = cmode + config.Console = console + config.Cores = cores + config.CPULimit = cpulimit + config.CPUUnits = cpuunits + config.Description = description + config.OnBoot = onboot + config.Hookscript = hookscript + config.Hostname = hostname + config.Lock = lock + config.Memory = memory + config.Nameserver = nameserver + config.OnBoot = onboot + config.OsType = ostype + config.Protection = protection + config.RootFs = rootfs + config.SearchDomain = searchdomain + config.Startup = startup + config.Swap = swap + config.Template = template + config.Tty = tty + config.Unprivileged = unprivileged + config.Unused = unused + + return +} + +// create LXC container using the Proxmox API +func (config configLxc) CreateLxc(vmr *VmRef, client *Client) (err error) { + vmr.SetVmType("lxc") + + // convert config to map + params, _ := json.Marshal(&config) + var paramMap map[string]interface{} + json.Unmarshal(params, ¶mMap) + + // build list of features + // add features as parameter list to lxc parameters + // this overwrites the orginal formatting with a + // comma separated list of "key=value" pairs + featuresParam := QemuDeviceParam{} + featuresParam = featuresParam.createDeviceParam(config.Features, nil) + paramMap["features"] = strings.Join(featuresParam, ",") + + // build list of mountpoints + // this does the same as for the feature list + // except that there can be multiple of these mountpoint sets + // and each mountpoint set comes with a new id + for mpID, mpConfMap := range config.Mountpoints { + mpConfParam := QemuDeviceParam{} + mpConfParam = mpConfParam.createDeviceParam(mpConfMap, nil) + + // add mp to lxc parameters + mpName := fmt.Sprintf("mp%v", mpID) + paramMap[mpName] = strings.Join(mpConfParam, ",") + } + + // build list of network parameters + for nicID, nicConfMap := range config.Networks { + nicConfParam := QemuDeviceParam{} + nicConfParam = nicConfParam.createDeviceParam(nicConfMap, nil) + + // add nic to lxc parameters + nicName := fmt.Sprintf("net%v", nicID) + paramMap[nicName] = strings.Join(nicConfParam, ",") + } + + // build list of unused volumes for sake of completenes, + // even if it is not recommended to change these volumes manually + for volID, vol := range config.Unused { + // add volume to lxc parameters + volName := fmt.Sprintf("unused%v", volID) + paramMap[volName] = vol + } + + // now that we concatenated the key value parameter + // list for the networks, mountpoints and unused volumes, + // remove the original keys, since the Proxmox API does + // not know how to handle this key + delete(paramMap, "networks") + delete(paramMap, "mountpoints") + delete(paramMap, "unused") + + // amend vmid + paramMap["vmid"] = vmr.vmId + + exitStatus, err := client.CreateLxcContainer(vmr.node, paramMap) + if err != nil { + return fmt.Errorf("Error creating LXC container: %v, error status: %s (params: %v)", err, exitStatus, params) + } + return +} + +func (config configLxc) UpdateConfig(vmr *VmRef, client *Client) (err error) { + // convert config to map + params, _ := json.Marshal(&config) + var paramMap map[string]interface{} + json.Unmarshal(params, ¶mMap) + + // build list of features + // add features as parameter list to lxc parameters + // this overwrites the orginal formatting with a + // comma separated list of "key=value" pairs + featuresParam := QemuDeviceParam{} + featuresParam = featuresParam.createDeviceParam(config.Features, nil) + paramMap["features"] = strings.Join(featuresParam, ",") + + // build list of mountpoints + // this does the same as for the feature list + // except that there can be multiple of these mountpoint sets + // and each mountpoint set comes with a new id + for mpID, mpConfMap := range config.Mountpoints { + mpConfParam := QemuDeviceParam{} + mpConfParam = mpConfParam.createDeviceParam(mpConfMap, nil) + + // add mp to lxc parameters + mpName := fmt.Sprintf("mp%v", mpID) + paramMap[mpName] = strings.Join(mpConfParam, ",") + } + + // build list of network parameters + for nicID, nicConfMap := range config.Networks { + nicConfParam := QemuDeviceParam{} + nicConfParam = nicConfParam.createDeviceParam(nicConfMap, nil) + + // add nic to lxc parameters + nicName := fmt.Sprintf("net%v", nicID) + paramMap[nicName] = strings.Join(nicConfParam, ",") + } + + // build list of unused volumes for sake of completenes, + // even if it is not recommended to change these volumes manually + for volID, vol := range config.Unused { + // add volume to lxc parameters + volName := fmt.Sprintf("unused%v", volID) + paramMap[volName] = vol + } + + // now that we concatenated the key value parameter + // list for the networks, mountpoints and unused volumes, + // remove the original keys, since the Proxmox API does + // not know how to handle this key + delete(paramMap, "networks") + delete(paramMap, "mountpoints") + delete(paramMap, "unused") + + // delete parameters wich are not supported in updated operations + delete(paramMap, "pool") + delete(paramMap, "storage") + delete(paramMap, "password") + delete(paramMap, "ostemplate") + delete(paramMap, "start") + // even though it is listed as a PUT option in the API documentation + // we remove it here because "it should not be modified manually"; + // also, error "500 unable to modify read-only option: 'unprivileged'" + delete(paramMap, "unprivileged") + + _, err = client.SetLxcConfig(vmr, paramMap) + return err +} 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 f0ee911b5..eaffb1801 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 @@ -31,10 +31,17 @@ type ConfigQemu struct { QemuOs string `json:"os"` QemuCores int `json:"cores"` QemuSockets int `json:"sockets"` + QemuCpu string `json:"cpu"` + QemuNuma bool `json:"numa"` + Hotplug string `json:"hotplug"` QemuIso string `json:"iso"` FullClone *int `json:"fullclone"` + Boot string `json:"boot"` + BootDisk string `json:"bootdisk,omitempty"` + Scsihw string `json:"scsihw,omitempty"` QemuDisks QemuDevices `json:"disk"` QemuNetworks QemuDevices `json:"network"` + QemuSerials QemuDevices `json:"serial,omitempty"` // Deprecated single disk. DiskSize float64 `json:"diskGB"` @@ -50,6 +57,7 @@ type ConfigQemu struct { // cloud-init options CIuser string `json:"ciuser"` CIpassword string `json:"cipassword"` + CIcustom string `json:"cicustom"` Searchdomain string `json:"searchdomain"` Nameserver string `json:"nameserver"` @@ -76,10 +84,24 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { "ostype": config.QemuOs, "sockets": config.QemuSockets, "cores": config.QemuCores, - "cpu": "host", + "cpu": config.QemuCpu, + "numa": config.QemuNuma, + "hotplug": config.Hotplug, "memory": config.Memory, + "boot": config.Boot, "description": config.Description, } + if vmr.pool != "" { + params["pool"] = vmr.pool + } + + if config.BootDisk != "" { + params["bootdisk"] = config.BootDisk + } + + if config.Scsihw != "" { + params["scsihw"] = config.Scsihw + } // Create disks config. config.CreateQemuDisksParams(vmr.vmId, params, false) @@ -87,6 +109,9 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { // Create networks config. config.CreateQemuNetworksParams(vmr.vmId, params) + // Create serial interfaces + config.CreateQemuSerialsParams(vmr.vmId, params) + exitStatus, err := client.CreateQemuVm(vmr.node, params) if err != nil { return fmt.Errorf("Error creating VM: %v, error status: %s (params: %v)", err, exitStatus, params) @@ -102,7 +127,8 @@ func (config ConfigQemu) HasCloudInit() bool { config.Nameserver != "" || config.Sshkeys != "" || config.Ipconfig0 != "" || - config.Ipconfig1 != "" + config.Ipconfig1 != "" || + config.CIcustom != "" } /* @@ -136,6 +162,10 @@ func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) ( "storage": storage, "full": fullclone, } + if vmr.pool != "" { + params["pool"] = vmr.pool + } + _, err = client.CloneQemuVm(sourceVmr, params) if err != nil { return @@ -151,7 +181,19 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { "agent": config.Agent, "sockets": config.QemuSockets, "cores": config.QemuCores, + "cpu": config.QemuCpu, + "numa": config.QemuNuma, + "hotplug": config.Hotplug, "memory": config.Memory, + "boot": config.Boot, + } + + if config.BootDisk != "" { + configParams["bootdisk"] = config.BootDisk + } + + if config.Scsihw != "" { + configParams["scsihw"] = config.Scsihw } // Create disks config. @@ -160,6 +202,9 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { // Create networks config. config.CreateQemuNetworksParams(vmr.vmId, configParams) + // Create serial interfaces + config.CreateQemuSerialsParams(vmr.vmId, configParams) + // cloud-init options if config.CIuser != "" { configParams["ciuser"] = config.CIuser @@ -167,6 +212,9 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { if config.CIpassword != "" { configParams["cipassword"] = config.CIpassword } + if config.CIcustom != "" { + configParams["cicustom"] = config.CIcustom + } if config.Searchdomain != "" { configParams["searchdomain"] = config.Searchdomain } @@ -202,11 +250,13 @@ func NewConfigQemuFromJson(io io.Reader) (config *ConfigQemu, err error) { } var ( - rxIso = regexp.MustCompile(`(.*?),media`) - rxDeviceID = regexp.MustCompile(`\d+`) - rxDiskName = regexp.MustCompile(`(virtio|scsi)\d+`) - rxDiskType = regexp.MustCompile(`\D+`) - rxNicName = regexp.MustCompile(`net\d+`) + rxIso = regexp.MustCompile(`(.*?),media`) + rxDeviceID = regexp.MustCompile(`\d+`) + rxDiskName = regexp.MustCompile(`(virtio|scsi)\d+`) + rxDiskType = regexp.MustCompile(`\D+`) + rxNicName = regexp.MustCompile(`net\d+`) + rxMpName = regexp.MustCompile(`mp\d+`) + rxSerialName = regexp.MustCompile(`serial\d+`) ) func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err error) { @@ -278,6 +328,32 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e if _, isSet := vmConfig["sockets"]; isSet { sockets = vmConfig["sockets"].(float64) } + cpu := "host" + if _, isSet := vmConfig["cpu"]; isSet { + cpu = vmConfig["cpu"].(string) + } + numa := false + if _, isSet := vmConfig["numa"]; isSet { + numa = Itob(int(vmConfig["numa"].(float64))) + } + //Can be network,disk,cpu,memory,usb + hotplug := "network,disk,usb" + if _, isSet := vmConfig["hotplug"]; isSet { + hotplug = vmConfig["hotplug"].(string) + } + //boot by default from hard disk (c), CD-ROM (d), network (n). + boot := "cdn" + if _, isSet := vmConfig["boot"]; isSet { + boot = vmConfig["boot"].(string) + } + bootdisk := "" + if _, isSet := vmConfig["bootdisk"]; isSet { + bootdisk = vmConfig["bootdisk"].(string) + } + scsihw := "lsi" + if _, isSet := vmConfig["scsihw"]; isSet { + scsihw = vmConfig["scsihw"].(string) + } config = &ConfigQemu{ Name: name, Description: strings.TrimSpace(description), @@ -287,9 +363,16 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e Memory: int(memory), QemuCores: int(cores), QemuSockets: int(sockets), + QemuCpu: cpu, + QemuNuma: numa, + Hotplug: hotplug, QemuVlanTag: -1, + Boot: boot, + BootDisk: bootdisk, + Scsihw: scsihw, QemuDisks: QemuDevices{}, QemuNetworks: QemuDevices{}, + QemuSerials: QemuDevices{}, } if vmConfig["ide2"] != nil { @@ -303,6 +386,9 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e if _, isSet := vmConfig["cipassword"]; isSet { config.CIpassword = vmConfig["cipassword"].(string) } + if _, isSet := vmConfig["cicustom"]; isSet { + config.CIcustom = vmConfig["cicustom"].(string) + } if _, isSet := vmConfig["searchdomain"]; isSet { config.Searchdomain = vmConfig["searchdomain"].(string) } @@ -388,6 +474,30 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e } } + // Add serials + serialNames := []string{} + + for k, _ := range vmConfig { + if serialName := rxSerialName.FindStringSubmatch(k); len(serialName) > 0 { + serialNames = append(serialNames, serialName[0]) + } + } + + for _, serialName := range serialNames { + id := rxDeviceID.FindStringSubmatch(serialName) + serialID, _ := strconv.Atoi(id[0]) + + serialConfMap := QemuDevice{ + "id": serialID, + "type": vmConfig[serialName], + } + + // And device config to serials map. + if len(serialConfMap) > 0 { + config.QemuSerials[serialID] = serialConfMap + } + } + return } @@ -620,7 +730,9 @@ func (c ConfigQemu) CreateQemuDisksParams( "storage_type": "lvm", // default old style "cache": "none", // default old value } - + if c.QemuDisks == nil { + c.QemuDisks = make(QemuDevices) + } c.QemuDisks[0] = deprecatedStyleMap } @@ -646,9 +758,9 @@ func (c ConfigQemu) CreateQemuDisksParams( // Disk name. var diskFile string - // Currently ZFS local, LVM, and Directory are considered. + // Currently ZFS local, LVM, Ceph RBD, and Directory are considered. // Other formats are not verified, but could be added if they're needed. - rxStorageTypes := `(zfspool|lvm)` + rxStorageTypes := `(zfspool|lvm|rbd)` storageType := diskConfMap["storage_type"].(string) if matched, _ := regexp.MatchString(rxStorageTypes, storageType); matched { diskFile = fmt.Sprintf("file=%v:vm-%v-disk-%v", diskConfMap["storage"], vmID, diskID) @@ -716,3 +828,22 @@ func (c ConfigQemu) String() string { jsConf, _ := json.Marshal(c) return string(jsConf) } + +// Create parameters for serial interface +func (c ConfigQemu) CreateQemuSerialsParams( + vmID int, + params map[string]interface{}, +) error { + + // For new style with multi disk device. + for serialID, serialConfMap := range c.QemuSerials { + // Device name. + deviceType := serialConfMap["type"].(string) + qemuSerialName := "serial" + strconv.Itoa(serialID) + + // Add back to Qemu prams. + params[qemuSerialName] = deviceType + } + + return 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 72f72eb6a..759d20e75 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go @@ -106,8 +106,12 @@ func TypedResponse(resp *http.Response, v interface{}) error { return nil } -func (s *Session) Login(username string, password string) (err error) { - reqbody := ParamsToBody(map[string]interface{}{"username": username, "password": password}) +func (s *Session) Login(username string, password string, otp string) (err error) { + reqUser := map[string]interface{}{"username": username, "password": password} + if otp != "" { + reqUser["otp"] = otp + } + reqbody := ParamsToBody(reqUser) olddebug := *Debug *Debug = false // don't share passwords in debug log resp, err := s.Post("/access/ticket", nil, nil, &reqbody) @@ -127,6 +131,10 @@ func (s *Session) Login(username string, password string) (err error) { return fmt.Errorf("Invalid login response:\n-----\n%s\n-----", dr) } dat := jbody["data"].(map[string]interface{}) + //Check if the 2FA was required + if dat["NeedTFA"] == 1.0 { + return errors.New("Missing TFA code") + } s.AuthTicket = dat["ticket"].(string) s.CsrfToken = dat["CSRFPreventionToken"].(string) return nil diff --git a/vendor/modules.txt b/vendor/modules.txt index a15d8dbfa..8fce12087 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -50,7 +50,7 @@ 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-20190614181158-26cd147831a4 +# github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60 github.com/Telmate/proxmox-api-go/proxmox # github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors