diff --git a/vendor/github.com/Telmate/proxmox-api-go/LICENSE b/vendor/github.com/Telmate/proxmox-api-go/LICENSE new file mode 100644 index 000000000..57395f1b0 --- /dev/null +++ b/vendor/github.com/Telmate/proxmox-api-go/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go new file mode 100644 index 000000000..7b97b9505 --- /dev/null +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go @@ -0,0 +1,597 @@ +package proxmox + +// inspired by https://github.com/Telmate/vagrant-proxmox/blob/master/lib/vagrant-proxmox/proxmox/connection.rb + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "regexp" + "strconv" + "strings" + "time" +) + +// TaskTimeout - default async task call timeout in seconds +const TaskTimeout = 300 + +// TaskStatusCheckInterval - time between async checks in seconds +const TaskStatusCheckInterval = 2 + +const exitStatusSuccess = "OK" + +// Client - URL, user and password to specifc Proxmox node +type Client struct { + session *Session + ApiUrl string + Username string + Password string +} + +// VmRef - virtual machine ref parts +// map[type:qemu node:proxmox1-xx id:qemu/132 diskread:5.57424738e+08 disk:0 netin:5.9297450593e+10 mem:3.3235968e+09 uptime:1.4567097e+07 vmid:132 template:0 maxcpu:2 netout:6.053310416e+09 maxdisk:3.4359738368e+10 maxmem:8.592031744e+09 diskwrite:1.49663619584e+12 status:running cpu:0.00386980694947209 name:appt-app1-dev.xxx.xx] +type VmRef struct { + vmId int + node string + vmType string +} + +func (vmr *VmRef) SetNode(node string) { + vmr.node = node + return +} + +func (vmr *VmRef) SetVmType(vmType string) { + vmr.vmType = vmType + return +} + +func (vmr *VmRef) VmId() int { + return vmr.vmId +} + +func (vmr *VmRef) Node() string { + return vmr.node +} + +func NewVmRef(vmId int) (vmr *VmRef) { + vmr = &VmRef{vmId: vmId, node: "", vmType: ""} + return +} + +func NewClient(apiUrl string, hclient *http.Client, tls *tls.Config) (client *Client, err error) { + var sess *Session + sess, err = NewSession(apiUrl, hclient, tls) + if err == nil { + client = &Client{session: sess, ApiUrl: apiUrl} + } + return client, err +} + +func (c *Client) Login(username string, password string) (err error) { + c.Username = username + c.Password = password + return c.session.Login(username, password) +} + +func (c *Client) GetJsonRetryable(url string, data *map[string]interface{}, tries int) error { + var statErr error + for ii := 0; ii < tries; ii++ { + _, statErr = c.session.GetJSON(url, nil, nil, data) + if statErr == nil { + return nil + } + // if statErr != io.ErrUnexpectedEOF { // don't give up on ErrUnexpectedEOF + // return statErr + // } + time.Sleep(5 * time.Second) + } + return statErr +} + +func (c *Client) GetNodeList() (list map[string]interface{}, err error) { + err = c.GetJsonRetryable("/nodes", &list, 3) + return +} + +func (c *Client) GetVmList() (list map[string]interface{}, err error) { + err = c.GetJsonRetryable("/cluster/resources?type=vm", &list, 3) + return +} + +func (c *Client) CheckVmRef(vmr *VmRef) (err error) { + if vmr.node == "" || vmr.vmType == "" { + _, err = c.GetVmInfo(vmr) + } + return +} + +func (c *Client) GetVmInfo(vmr *VmRef) (vmInfo map[string]interface{}, err error) { + resp, err := c.GetVmList() + vms := resp["data"].([]interface{}) + for vmii := range vms { + vm := vms[vmii].(map[string]interface{}) + if int(vm["vmid"].(float64)) == vmr.vmId { + vmInfo = vm + vmr.node = vmInfo["node"].(string) + vmr.vmType = vmInfo["type"].(string) + return + } + } + return nil, errors.New(fmt.Sprintf("Vm '%d' not found", vmr.vmId)) +} + +func (c *Client) GetVmRefByName(vmName string) (vmr *VmRef, err error) { + resp, err := c.GetVmList() + vms := resp["data"].([]interface{}) + for vmii := range vms { + vm := vms[vmii].(map[string]interface{}) + if vm["name"] != nil && vm["name"].(string) == vmName { + vmr = NewVmRef(int(vm["vmid"].(float64))) + vmr.node = vm["node"].(string) + vmr.vmType = vm["type"].(string) + return + } + } + return nil, errors.New(fmt.Sprintf("Vm '%s' not found", vmName)) +} + +func (c *Client) GetVmState(vmr *VmRef) (vmState map[string]interface{}, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return nil, err + } + var data map[string]interface{} + url := fmt.Sprintf("/nodes/%s/%s/%d/status/current", vmr.node, vmr.vmType, vmr.vmId) + err = c.GetJsonRetryable(url, &data, 3) + if err != nil { + return nil, err + } + if data["data"] == nil { + return nil, errors.New("Vm STATE not readable") + } + vmState = data["data"].(map[string]interface{}) + return +} + +func (c *Client) GetVmConfig(vmr *VmRef) (vmConfig map[string]interface{}, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return nil, err + } + var data map[string]interface{} + url := fmt.Sprintf("/nodes/%s/%s/%d/config", vmr.node, vmr.vmType, vmr.vmId) + err = c.GetJsonRetryable(url, &data, 3) + if err != nil { + return nil, err + } + if data["data"] == nil { + return nil, errors.New("Vm CONFIG not readable") + } + vmConfig = data["data"].(map[string]interface{}) + return +} + +func (c *Client) GetVmSpiceProxy(vmr *VmRef) (vmSpiceProxy map[string]interface{}, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return nil, err + } + var data map[string]interface{} + url := fmt.Sprintf("/nodes/%s/%s/%d/spiceproxy", vmr.node, vmr.vmType, vmr.vmId) + _, err = c.session.PostJSON(url, nil, nil, nil, &data) + if err != nil { + return nil, err + } + if data["data"] == nil { + return nil, errors.New("Vm SpiceProxy not readable") + } + vmSpiceProxy = data["data"].(map[string]interface{}) + return +} + +type AgentNetworkInterface struct { + MACAddress string + IPAddresses []net.IP + Name string + Statistics map[string]int64 +} + +func (a *AgentNetworkInterface) UnmarshalJSON(b []byte) error { + var intermediate struct { + HardwareAddress string `json:"hardware-address"` + IPAddresses []struct { + IPAddress string `json:"ip-address"` + IPAddressType string `json:"ip-address-type"` + Prefix int `json:"prefix"` + } `json:"ip-addresses"` + Name string `json:"name"` + Statistics map[string]int64 `json:"statistics"` + } + err := json.Unmarshal(b, &intermediate) + if err != nil { + return err + } + + a.IPAddresses = make([]net.IP, len(intermediate.IPAddresses)) + for idx, ip := range intermediate.IPAddresses { + a.IPAddresses[idx] = net.ParseIP(ip.IPAddress) + if a.IPAddresses[idx] == nil { + return fmt.Errorf("Could not parse %s as IP", ip.IPAddress) + } + } + a.MACAddress = intermediate.HardwareAddress + a.Name = intermediate.Name + a.Statistics = intermediate.Statistics + return nil +} + +func (c *Client) GetVmAgentNetworkInterfaces(vmr *VmRef) ([]AgentNetworkInterface, error) { + var ifs []AgentNetworkInterface + err := c.doAgentGet(vmr, "network-get-interfaces", &ifs) + return ifs, err +} + +func (c *Client) doAgentGet(vmr *VmRef, command string, output interface{}) error { + err := c.CheckVmRef(vmr) + if err != nil { + return err + } + + url := fmt.Sprintf("/nodes/%s/%s/%d/agent/%s", vmr.node, vmr.vmType, vmr.vmId, command) + resp, err := c.session.Get(url, nil, nil) + if err != nil { + return err + } + + return TypedResponse(resp, output) +} + +func (c *Client) CreateTemplate(vmr *VmRef) error { + err := c.CheckVmRef(vmr) + if err != nil { + return err + } + + url := fmt.Sprintf("/nodes/%s/%s/%d/template", vmr.node, vmr.vmType, vmr.vmId) + _, err = c.session.Post(url, nil, nil, nil) + if err != nil { + return err + } + + return nil +} + +func (c *Client) MonitorCmd(vmr *VmRef, command string) (monitorRes map[string]interface{}, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return nil, err + } + 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) + monitorRes, err = ResponseJSON(resp) + return +} + +// WaitForCompletion - poll the API for task completion +func (c *Client) WaitForCompletion(taskResponse map[string]interface{}) (waitExitStatus string, err error) { + if taskResponse["errors"] != nil { + errJSON, _ := json.MarshalIndent(taskResponse["errors"], "", " ") + return string(errJSON), errors.New("Error reponse") + } + if taskResponse["data"] == nil { + return "", nil + } + waited := 0 + taskUpid := taskResponse["data"].(string) + for waited < TaskTimeout { + exitStatus, statErr := c.GetTaskExitstatus(taskUpid) + if statErr != nil { + if statErr != io.ErrUnexpectedEOF { // don't give up on ErrUnexpectedEOF + return "", statErr + } + } + if exitStatus != nil { + waitExitStatus = exitStatus.(string) + return + } + time.Sleep(TaskStatusCheckInterval * time.Second) + waited = waited + TaskStatusCheckInterval + } + return "", errors.New("Wait timeout for:" + taskUpid) +} + +var rxTaskNode = regexp.MustCompile("UPID:(.*?):") + +func (c *Client) GetTaskExitstatus(taskUpid string) (exitStatus interface{}, err error) { + node := rxTaskNode.FindStringSubmatch(taskUpid)[1] + url := fmt.Sprintf("/nodes/%s/tasks/%s/status", node, taskUpid) + var data map[string]interface{} + _, err = c.session.GetJSON(url, nil, nil, &data) + if err == nil { + exitStatus = data["data"].(map[string]interface{})["exitstatus"] + } + if exitStatus != nil && exitStatus != exitStatusSuccess { + err = errors.New(exitStatus.(string)) + } + return +} + +func (c *Client) StatusChangeVm(vmr *VmRef, setStatus string) (exitStatus string, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return "", err + } + + url := fmt.Sprintf("/nodes/%s/%s/%d/status/%s", vmr.node, vmr.vmType, vmr.vmId, setStatus) + var taskResponse map[string]interface{} + for i := 0; i < 3; i++ { + _, err = c.session.PostJSON(url, nil, nil, nil, &taskResponse) + exitStatus, err = c.WaitForCompletion(taskResponse) + if exitStatus == "" { + time.Sleep(TaskStatusCheckInterval * time.Second) + } else { + return + } + } + return +} + +func (c *Client) StartVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, "start") +} + +func (c *Client) StopVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, "stop") +} + +func (c *Client) ShutdownVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, "shutdown") +} + +func (c *Client) ResetVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, "reset") +} + +func (c *Client) SuspendVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, "suspend") +} + +func (c *Client) ResumeVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, "resume") +} + +func (c *Client) DeleteVm(vmr *VmRef) (exitStatus string, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return "", err + } + url := fmt.Sprintf("/nodes/%s/%s/%d", vmr.node, vmr.vmType, vmr.vmId) + var taskResponse map[string]interface{} + _, err = c.session.RequestJSON("DELETE", url, nil, nil, nil, &taskResponse) + exitStatus, err = c.WaitForCompletion(taskResponse) + return +} + +func (c *Client) CreateQemuVm(node string, vmParams map[string]interface{}) (exitStatus string, err error) { + // Create VM disks first to ensure disks names. + createdDisks, createdDisksErr := c.createVMDisks(node, vmParams) + if createdDisksErr != nil { + return "", createdDisksErr + } + + // Then create the VM itself. + reqbody := ParamsToBody(vmParams) + url := fmt.Sprintf("/nodes/%s/qemu", node) + var resp *http.Response + resp, err = c.session.Post(url, nil, nil, &reqbody) + defer resp.Body.Close() + if err != nil { + b, _ := ioutil.ReadAll(resp.Body) + exitStatus = string(b) + return + } + + taskResponse, err := ResponseJSON(resp) + if err != nil { + return + } + exitStatus, err = c.WaitForCompletion(taskResponse) + // Delete VM disks if the VM didn't create. + if exitStatus != "OK" { + deleteDisksErr := c.DeleteVMDisks(node, createdDisks) + if deleteDisksErr != nil { + return "", deleteDisksErr + } + } + + 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) + resp, err := c.session.Post(url, nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return "", err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + } + return +} + +func (c *Client) RollbackQemuVm(vmr *VmRef, snapshot string) (exitStatus string, err error) { + err = c.CheckVmRef(vmr) + if err != nil { + return "", err + } + url := fmt.Sprintf("/nodes/%s/%s/%d/snapshot/%s/rollback", vmr.node, vmr.vmType, vmr.vmId, snapshot) + var taskResponse map[string]interface{} + _, err = c.session.PostJSON(url, nil, nil, nil, &taskResponse) + exitStatus, err = c.WaitForCompletion(taskResponse) + return +} + +// SetVmConfig - send config options +func (c *Client) SetVmConfig(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.Post(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 + //size:+2G + if disk == "" { + disk = "virtio0" + } + size := fmt.Sprintf("+%dG", moreSizeGB) + reqbody := ParamsToBody(map[string]interface{}{"disk": disk, "size": size}) + url := fmt.Sprintf("/nodes/%s/%s/%d/resize", 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 +} + +// GetNextID - Get next free VMID +func (c *Client) GetNextID(currentID int) (nextID int, err error) { + var data map[string]interface{} + var url string + if currentID >= 100 { + url = fmt.Sprintf("/cluster/nextid?vmid=%d", currentID) + } else { + url = "/cluster/nextid" + } + _, err = c.session.GetJSON(url, nil, nil, &data) + if err == nil { + if data["errors"] != nil { + if currentID >= 100 { + return c.GetNextID(currentID + 1) + } else { + return -1, errors.New("error using /cluster/nextid") + } + } + nextID, err = strconv.Atoi(data["data"].(string)) + } + return +} + +// CreateVMDisk - Create single disk for VM on host node. +func (c *Client) CreateVMDisk( + nodeName string, + storageName string, + fullDiskName string, + diskParams map[string]interface{}, +) error { + + reqbody := ParamsToBody(diskParams) + url := fmt.Sprintf("/nodes/%s/storage/%s/content", nodeName, storageName) + resp, err := c.session.Post(url, nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return err + } + if diskName, containsData := taskResponse["data"]; !containsData || diskName != fullDiskName { + return errors.New(fmt.Sprintf("Cannot create VM disk %s", fullDiskName)) + } + } else { + return err + } + + return nil +} + +// createVMDisks - Make disks parameters and create all VM disks on host node. +func (c *Client) createVMDisks( + node string, + vmParams map[string]interface{}, +) (disks []string, err error) { + var createdDisks []string + vmID := vmParams["vmid"].(int) + for deviceName, deviceConf := range vmParams { + rxStorageModels := `(ide|sata|scsi|virtio)\d+` + if matched, _ := regexp.MatchString(rxStorageModels, deviceName); matched { + deviceConfMap := ParseConf(deviceConf.(string), ",", "=") + // This if condition to differentiate between `disk` and `cdrom`. + if media, containsFile := deviceConfMap["media"]; containsFile && media == "disk" { + fullDiskName := deviceConfMap["file"].(string) + storageName, volumeName := getStorageAndVolumeName(fullDiskName, ":") + diskParams := map[string]interface{}{ + "vmid": vmID, + "filename": volumeName, + "size": deviceConfMap["size"], + } + err := c.CreateVMDisk(node, storageName, fullDiskName, diskParams) + if err != nil { + return createdDisks, err + } else { + createdDisks = append(createdDisks, fullDiskName) + } + } + } + } + + return createdDisks, nil +} + +// DeleteVMDisks - Delete VM disks from host node. +// By default the VM disks are deteled when the VM is deleted, +// so mainly this is used to delete the disks in case VM creation didn't complete. +func (c *Client) DeleteVMDisks( + node string, + disks []string, +) error { + for _, fullDiskName := range disks { + storageName, volumeName := getStorageAndVolumeName(fullDiskName, ":") + url := fmt.Sprintf("/nodes/%s/storage/%s/content/%s", node, storageName, volumeName) + _, err := c.session.Post(url, nil, nil, nil) + if err != nil { + return err + } + } + + return nil +} + +// getStorageAndVolumeName - Extract disk storage and disk volume, since disk name is saved +// in Proxmox with its storage. +func getStorageAndVolumeName( + fullDiskName string, + separator string, +) (storageName string, diskName string) { + storageAndVolumeName := strings.Split(fullDiskName, separator) + storageName, volumeName := storageAndVolumeName[0], storageAndVolumeName[1] + + // when disk type is dir, volumeName is `file=local:100/vm-100-disk-0.raw` + re := regexp.MustCompile(`\d+/(?P\S+.\S+)`) + match := re.FindStringSubmatch(volumeName) + if len(match) == 2 { + volumeName = match[1] + } + + return storageName, volumeName +} 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 new file mode 100644 index 000000000..7c0ffd897 --- /dev/null +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go @@ -0,0 +1,706 @@ +package proxmox + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "math/rand" + "net" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ( + QemuDevices map[int]map[string]interface{} + QemuDevice map[string]interface{} + QemuDeviceParam []string +) + +// ConfigQemu - Proxmox API QEMU options +type ConfigQemu struct { + Name string `json:"name"` + Description string `json:"desc"` + Onboot bool `json:"onboot"` + Agent string `json:"agent"` + Memory int `json:"memory"` + QemuOs string `json:"os"` + QemuCores int `json:"cores"` + QemuSockets int `json:"sockets"` + QemuIso string `json:"iso"` + FullClone *int `json:"fullclone"` + QemuDisks QemuDevices `json:"disk"` + QemuNetworks QemuDevices `json:"network"` + + // Deprecated single disk. + DiskSize float64 `json:"diskGB"` + Storage string `json:"storage"` + StorageType string `json:"storageType"` // virtio|scsi (cloud-init defaults to scsi) + + // Deprecated single nic. + QemuNicModel string `json:"nic"` + QemuBrige string `json:"bridge"` + QemuVlanTag int `json:"vlan"` + QemuMacAddr string `json:"mac"` + + // cloud-init options + CIuser string `json:"ciuser"` + CIpassword string `json:"cipassword"` + + Searchdomain string `json:"searchdomain"` + Nameserver string `json:"nameserver"` + Sshkeys string `json:"sshkeys"` + + // arrays are hard, support 2 interfaces for now + Ipconfig0 string `json:"ipconfig0"` + Ipconfig1 string `json:"ipconfig1"` +} + +// CreateVm - Tell Proxmox API to make the VM +func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { + if config.HasCloudInit() { + return errors.New("Cloud-init parameters only supported on clones or updates") + } + vmr.SetVmType("qemu") + + params := map[string]interface{}{ + "vmid": vmr.vmId, + "name": config.Name, + "onboot": config.Onboot, + "agent": config.Agent, + "ide2": config.QemuIso + ",media=cdrom", + "ostype": config.QemuOs, + "sockets": config.QemuSockets, + "cores": config.QemuCores, + "cpu": "host", + "memory": config.Memory, + "description": config.Description, + } + + // Create disks config. + config.CreateQemuDisksParams(vmr.vmId, params, false) + + // Create networks config. + config.CreateQemuNetworksParams(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) + } + return +} + +// HasCloudInit - are there cloud-init options? +func (config ConfigQemu) HasCloudInit() bool { + return config.CIuser != "" || + config.CIpassword != "" || + config.Searchdomain != "" || + config.Nameserver != "" || + config.Sshkeys != "" || + config.Ipconfig0 != "" || + config.Ipconfig1 != "" +} + +/* + +CloneVm +Example: Request + +nodes/proxmox1-xx/qemu/1012/clone + +newid:145 +name:tf-clone1 +target:proxmox1-xx +full:1 +storage:xxx + +*/ +func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) (err error) { + vmr.SetVmType("qemu") + fullclone := "1" + if config.FullClone != nil { + fullclone = strconv.Itoa(*config.FullClone) + } + storage := config.Storage + if disk0Storage, ok := config.QemuDisks[0]["storage"].(string); ok && len(disk0Storage) > 0 { + storage = disk0Storage + } + params := map[string]interface{}{ + "newid": vmr.vmId, + "target": vmr.node, + "name": config.Name, + "storage": storage, + "full": fullclone, + } + _, err = client.CloneQemuVm(sourceVmr, params) + if err != nil { + return + } + return config.UpdateConfig(vmr, client) +} + +func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { + configParams := map[string]interface{}{ + "name": config.Name, + "description": config.Description, + "onboot": config.Onboot, + "agent": config.Agent, + "sockets": config.QemuSockets, + "cores": config.QemuCores, + "memory": config.Memory, + } + + // Create disks config. + config.CreateQemuDisksParams(vmr.vmId, configParams, true) + + // Create networks config. + config.CreateQemuNetworksParams(vmr.vmId, configParams) + + // cloud-init options + if config.CIuser != "" { + configParams["ciuser"] = config.CIuser + } + if config.CIpassword != "" { + configParams["cipassword"] = config.CIpassword + } + if config.Searchdomain != "" { + configParams["searchdomain"] = config.Searchdomain + } + if config.Nameserver != "" { + configParams["nameserver"] = config.Nameserver + } + if config.Sshkeys != "" { + sshkeyEnc := url.PathEscape(config.Sshkeys + "\n") + sshkeyEnc = strings.Replace(sshkeyEnc, "+", "%2B", -1) + sshkeyEnc = strings.Replace(sshkeyEnc, "@", "%40", -1) + sshkeyEnc = strings.Replace(sshkeyEnc, "=", "%3D", -1) + configParams["sshkeys"] = sshkeyEnc + } + if config.Ipconfig0 != "" { + configParams["ipconfig0"] = config.Ipconfig0 + } + if config.Ipconfig1 != "" { + configParams["ipconfig1"] = config.Ipconfig1 + } + _, err = client.SetVmConfig(vmr, configParams) + return err +} + +func NewConfigQemuFromJson(io io.Reader) (config *ConfigQemu, err error) { + config = &ConfigQemu{QemuVlanTag: -1} + err = json.NewDecoder(io).Decode(config) + if err != nil { + log.Fatal(err) + return nil, err + } + log.Println(config) + return +} + +var ( + rxIso = regexp.MustCompile(`(.*?),media`) + rxDeviceID = regexp.MustCompile(`\d+`) + rxDiskName = regexp.MustCompile(`(virtio|scsi)\d+`) + rxDiskType = regexp.MustCompile(`\D+`) + rxNicName = regexp.MustCompile(`net\d+`) +) + +func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err error) { + var vmConfig map[string]interface{} + for ii := 0; ii < 3; ii++ { + vmConfig, err = client.GetVmConfig(vmr) + if err != nil { + log.Fatal(err) + return nil, err + } + // this can happen: + // {"data":{"lock":"clone","digest":"eb54fb9d9f120ba0c3bdf694f73b10002c375c38","description":" qmclone temporary file\n"}}) + if vmConfig["lock"] == nil { + break + } else { + time.Sleep(8 * time.Second) + } + } + + if vmConfig["lock"] != nil { + return nil, errors.New("vm locked, could not obtain config") + } + + // vmConfig Sample: map[ cpu:host + // net0:virtio=62:DF:XX:XX:XX:XX,bridge=vmbr0 + // ide2:local:iso/xxx-xx.iso,media=cdrom memory:2048 + // smbios1:uuid=8b3bf833-aad8-4545-xxx-xxxxxxx digest:aa6ce5xxxxx1b9ce33e4aaeff564d4 sockets:1 + // name:terraform-ubuntu1404-template bootdisk:virtio0 + // virtio0:ProxmoxxxxISCSI:vm-1014-disk-2,size=4G + // description:Base image + // cores:2 ostype:l26 + + name := "" + if _, isSet := vmConfig["name"]; isSet { + name = vmConfig["name"].(string) + } + description := "" + if _, isSet := vmConfig["description"]; isSet { + description = vmConfig["description"].(string) + } + onboot := true + if _, isSet := vmConfig["onboot"]; isSet { + onboot = Itob(int(vmConfig["onboot"].(float64))) + } + agent := "1" + if _, isSet := vmConfig["agent"]; isSet { + agent = vmConfig["agent"].(string) + } + ostype := "other" + if _, isSet := vmConfig["ostype"]; isSet { + ostype = vmConfig["ostype"].(string) + } + memory := 0.0 + if _, isSet := vmConfig["memory"]; isSet { + memory = vmConfig["memory"].(float64) + } + cores := 1.0 + if _, isSet := vmConfig["cores"]; isSet { + cores = vmConfig["cores"].(float64) + } + sockets := 1.0 + if _, isSet := vmConfig["sockets"]; isSet { + sockets = vmConfig["sockets"].(float64) + } + config = &ConfigQemu{ + Name: name, + Description: strings.TrimSpace(description), + Onboot: onboot, + Agent: agent, + QemuOs: ostype, + Memory: int(memory), + QemuCores: int(cores), + QemuSockets: int(sockets), + QemuVlanTag: -1, + QemuDisks: QemuDevices{}, + QemuNetworks: QemuDevices{}, + } + + if vmConfig["ide2"] != nil { + isoMatch := rxIso.FindStringSubmatch(vmConfig["ide2"].(string)) + config.QemuIso = isoMatch[1] + } + + if _, isSet := vmConfig["ciuser"]; isSet { + config.CIuser = vmConfig["ciuser"].(string) + } + if _, isSet := vmConfig["cipassword"]; isSet { + config.CIpassword = vmConfig["cipassword"].(string) + } + if _, isSet := vmConfig["searchdomain"]; isSet { + config.Searchdomain = vmConfig["searchdomain"].(string) + } + if _, isSet := vmConfig["sshkeys"]; isSet { + config.Sshkeys, _ = url.PathUnescape(vmConfig["sshkeys"].(string)) + } + if _, isSet := vmConfig["ipconfig0"]; isSet { + config.Ipconfig0 = vmConfig["ipconfig0"].(string) + } + if _, isSet := vmConfig["ipconfig1"]; isSet { + config.Ipconfig1 = vmConfig["ipconfig1"].(string) + } + + // Add disks. + diskNames := []string{} + + for k, _ := range vmConfig { + if diskName := rxDiskName.FindStringSubmatch(k); len(diskName) > 0 { + diskNames = append(diskNames, diskName[0]) + } + } + + for _, diskName := range diskNames { + diskConfStr := vmConfig[diskName] + diskConfList := strings.Split(diskConfStr.(string), ",") + + // + id := rxDeviceID.FindStringSubmatch(diskName) + diskID, _ := strconv.Atoi(id[0]) + diskType := rxDiskType.FindStringSubmatch(diskName)[0] + storageName, fileName := ParseSubConf(diskConfList[0], ":") + + // + diskConfMap := QemuDevice{ + "type": diskType, + "storage": storageName, + "file": fileName, + } + + // Add rest of device config. + diskConfMap.readDeviceConfig(diskConfList[1:]) + + // And device config to disks map. + if len(diskConfMap) > 0 { + config.QemuDisks[diskID] = diskConfMap + } + } + + // Add networks. + nicNameRe := regexp.MustCompile(`net\d+`) + nicNames := []string{} + + for k, _ := range vmConfig { + if nicName := nicNameRe.FindStringSubmatch(k); len(nicName) > 0 { + nicNames = append(nicNames, nicName[0]) + } + } + + for _, nicName := range nicNames { + nicConfStr := vmConfig[nicName] + nicConfList := strings.Split(nicConfStr.(string), ",") + + // + id := rxDeviceID.FindStringSubmatch(nicName) + nicID, _ := strconv.Atoi(id[0]) + model, macaddr := ParseSubConf(nicConfList[0], "=") + + // Add model and MAC address. + nicConfMap := QemuDevice{ + "model": model, + "macaddr": macaddr, + } + + // Add rest of device config. + nicConfMap.readDeviceConfig(nicConfList[1:]) + + // And device config to networks. + if len(nicConfMap) > 0 { + config.QemuNetworks[nicID] = nicConfMap + } + } + + return +} + +// Useful waiting for ISO install to complete +func WaitForShutdown(vmr *VmRef, client *Client) (err error) { + for ii := 0; ii < 100; ii++ { + vmState, err := client.GetVmState(vmr) + if err != nil { + log.Print("Wait error:") + log.Println(err) + } else if vmState["status"] == "stopped" { + return nil + } + time.Sleep(5 * time.Second) + } + return errors.New("Not shutdown within wait time") +} + +// This is because proxmox create/config API won't let us make usernet devices +func SshForwardUsernet(vmr *VmRef, client *Client) (sshPort string, err error) { + vmState, err := client.GetVmState(vmr) + if err != nil { + return "", err + } + if vmState["status"] == "stopped" { + return "", errors.New("VM must be running first") + } + sshPort = strconv.Itoa(vmr.VmId() + 22000) + _, err = client.MonitorCmd(vmr, "netdev_add user,id=net1,hostfwd=tcp::"+sshPort+"-:22") + if err != nil { + return "", err + } + _, err = client.MonitorCmd(vmr, "device_add virtio-net-pci,id=net1,netdev=net1,addr=0x13") + if err != nil { + return "", err + } + return +} + +// device_del net1 +// netdev_del net1 +func RemoveSshForwardUsernet(vmr *VmRef, client *Client) (err error) { + vmState, err := client.GetVmState(vmr) + if err != nil { + return err + } + if vmState["status"] == "stopped" { + return errors.New("VM must be running first") + } + _, err = client.MonitorCmd(vmr, "device_del net1") + if err != nil { + return err + } + _, err = client.MonitorCmd(vmr, "netdev_del net1") + if err != nil { + return err + } + return nil +} + +func MaxVmId(client *Client) (max int, err error) { + resp, err := client.GetVmList() + vms := resp["data"].([]interface{}) + max = 0 + for vmii := range vms { + vm := vms[vmii].(map[string]interface{}) + vmid := int(vm["vmid"].(float64)) + if vmid > max { + max = vmid + } + } + return +} + +func SendKeysString(vmr *VmRef, client *Client, keys string) (err error) { + vmState, err := client.GetVmState(vmr) + if err != nil { + return err + } + if vmState["status"] == "stopped" { + return errors.New("VM must be running first") + } + for _, r := range keys { + c := string(r) + lower := strings.ToLower(c) + if c != lower { + c = "shift-" + lower + } else { + switch c { + case "!": + c = "shift-1" + case "@": + c = "shift-2" + case "#": + c = "shift-3" + case "$": + c = "shift-4" + case "%%": + c = "shift-5" + case "^": + c = "shift-6" + case "&": + c = "shift-7" + case "*": + c = "shift-8" + case "(": + c = "shift-9" + case ")": + c = "shift-0" + case "_": + c = "shift-minus" + case "+": + c = "shift-equal" + case " ": + c = "spc" + case "/": + c = "slash" + case "\\": + c = "backslash" + case ",": + c = "comma" + case "-": + c = "minus" + case "=": + c = "equal" + case ".": + c = "dot" + case "?": + c = "shift-slash" + } + } + _, err = client.MonitorCmd(vmr, "sendkey "+c) + if err != nil { + return err + } + time.Sleep(100) + } + return nil +} + +// Create parameters for each Nic device. +func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interface{}) error { + + // For backward compatibility. + if len(c.QemuNetworks) == 0 && len(c.QemuNicModel) > 0 { + deprecatedStyleMap := QemuDevice{ + "model": c.QemuNicModel, + "bridge": c.QemuBrige, + "macaddr": c.QemuMacAddr, + } + + if c.QemuVlanTag > 0 { + deprecatedStyleMap["tag"] = strconv.Itoa(c.QemuVlanTag) + } + + c.QemuNetworks[0] = deprecatedStyleMap + } + + // For new style with multi net device. + for nicID, nicConfMap := range c.QemuNetworks { + + nicConfParam := QemuDeviceParam{} + + // Set Nic name. + qemuNicName := "net" + strconv.Itoa(nicID) + + // Set Mac address. + if nicConfMap["macaddr"] == nil || nicConfMap["macaddr"].(string) == "" { + // Generate Mac based on VmID and NicID so it will be the same always. + macaddr := make(net.HardwareAddr, 6) + rand.Seed(time.Now().UnixNano()) + rand.Read(macaddr) + macaddr[0] = (macaddr[0] | 2) & 0xfe // fix from github issue #18 + macAddrUppr := strings.ToUpper(fmt.Sprintf("%v", macaddr)) + // use model=mac format for older proxmox compatability + macAddr := fmt.Sprintf("%v=%v", nicConfMap["model"], macAddrUppr) + + // Add Mac to source map so it will be returned. (useful for some use case like Terraform) + nicConfMap["macaddr"] = macAddrUppr + // and also add it to the parameters which will be sent to Proxmox API. + nicConfParam = append(nicConfParam, macAddr) + } else { + macAddr := fmt.Sprintf("%v=%v", nicConfMap["model"], nicConfMap["macaddr"].(string)) + nicConfParam = append(nicConfParam, macAddr) + } + + // Set bridge if not nat. + if nicConfMap["bridge"].(string) != "nat" { + bridge := fmt.Sprintf("bridge=%v", nicConfMap["bridge"]) + nicConfParam = append(nicConfParam, bridge) + } + + // Keys that are not used as real/direct conf. + ignoredKeys := []string{"id", "bridge", "macaddr", "model"} + + // Rest of config. + nicConfParam = nicConfParam.createDeviceParam(nicConfMap, ignoredKeys) + + // Add nic to Qemu prams. + params[qemuNicName] = strings.Join(nicConfParam, ",") + } + + return nil +} + +// Create parameters for each disk. +func (c ConfigQemu) CreateQemuDisksParams( + vmID int, + params map[string]interface{}, + cloned bool, +) error { + + // For backward compatibility. + if len(c.QemuDisks) == 0 && len(c.Storage) > 0 { + + dType := c.StorageType + if dType == "" { + if c.HasCloudInit() { + dType = "scsi" + } else { + dType = "virtio" + } + } + deprecatedStyleMap := QemuDevice{ + "type": dType, + "storage": c.Storage, + "size": c.DiskSize, + "storage_type": "lvm", // default old style + "cache": "none", // default old value + } + + c.QemuDisks[0] = deprecatedStyleMap + } + + // For new style with multi disk device. + for diskID, diskConfMap := range c.QemuDisks { + + // skip the first disk for clones (may not always be right, but a template probably has at least 1 disk) + if diskID == 0 && cloned { + continue + } + diskConfParam := QemuDeviceParam{ + "media=disk", + } + + // Device name. + deviceType := diskConfMap["type"].(string) + qemuDiskName := deviceType + strconv.Itoa(diskID) + + // Set disk storage. + // Disk size. + diskSizeGB := fmt.Sprintf("size=%v", diskConfMap["size"]) + diskConfParam = append(diskConfParam, diskSizeGB) + + // Disk name. + var diskFile string + // Currently ZFS local, LVM, and Directory are considered. + // Other formats are not verified, but could be added if they're needed. + rxStorageTypes := `(zfspool|lvm)` + 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) + } else { + diskFile = fmt.Sprintf("file=%v:%v/vm-%v-disk-%v.%v", diskConfMap["storage"], vmID, vmID, diskID, diskConfMap["format"]) + } + diskConfParam = append(diskConfParam, diskFile) + + // Set cache if not none (default). + if diskConfMap["cache"].(string) != "none" { + diskCache := fmt.Sprintf("cache=%v", diskConfMap["cache"]) + diskConfParam = append(diskConfParam, diskCache) + } + + // Keys that are not used as real/direct conf. + ignoredKeys := []string{"id", "type", "storage", "storage_type", "size", "cache"} + + // Rest of config. + diskConfParam = diskConfParam.createDeviceParam(diskConfMap, ignoredKeys) + + // Add back to Qemu prams. + params[qemuDiskName] = strings.Join(diskConfParam, ",") + } + + return nil +} + +// Create the parameters for each device that will be sent to Proxmox API. +func (p QemuDeviceParam) createDeviceParam( + deviceConfMap QemuDevice, + ignoredKeys []string, +) QemuDeviceParam { + + for key, value := range deviceConfMap { + if ignored := inArray(ignoredKeys, key); !ignored { + var confValue interface{} + if bValue, ok := value.(bool); ok && bValue { + confValue = "1" + } else if sValue, ok := value.(string); ok && len(sValue) > 0 { + confValue = sValue + } else if iValue, ok := value.(int); ok && iValue > 0 { + confValue = iValue + } + if confValue != nil { + deviceConf := fmt.Sprintf("%v=%v", key, confValue) + p = append(p, deviceConf) + } + } + } + + return p +} + +// readDeviceConfig - get standard sub-conf strings where `key=value` and update conf map. +func (confMap QemuDevice) readDeviceConfig(confList []string) error { + // Add device config. + for _, conf := range confList { + key, value := ParseSubConf(conf, "=") + confMap[key] = value + } + return nil +} + +func (c ConfigQemu) String() string { + jsConf, _ := json.Marshal(c) + return string(jsConf) +} diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go new file mode 100644 index 000000000..72f72eb6a --- /dev/null +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/session.go @@ -0,0 +1,319 @@ +package proxmox + +// inspired by https://github.com/openstack/golang-client/blob/master/openstack/session.go + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "net/url" +) + +var Debug = new(bool) + +type Response struct { + Resp *http.Response + Body []byte +} + +type Session struct { + httpClient *http.Client + ApiUrl string + AuthTicket string + CsrfToken string + Headers http.Header +} + +func NewSession(apiUrl string, hclient *http.Client, tls *tls.Config) (session *Session, err error) { + if hclient == nil { + // Only build a transport if we're also building the client + tr := &http.Transport{ + TLSClientConfig: tls, + DisableCompression: true, + } + hclient = &http.Client{Transport: tr} + } + session = &Session{ + httpClient: hclient, + ApiUrl: apiUrl, + AuthTicket: "", + CsrfToken: "", + Headers: http.Header{}, + } + return session, nil +} + +func ParamsToBody(params map[string]interface{}) (body []byte) { + vals := url.Values{} + for k, intrV := range params { + var v string + switch intrV.(type) { + // Convert true/false bool to 1/0 string where Proxmox API can understand it. + case bool: + if intrV.(bool) { + v = "1" + } else { + v = "0" + } + default: + v = fmt.Sprintf("%v", intrV) + } + vals.Set(k, v) + } + body = bytes.NewBufferString(vals.Encode()).Bytes() + return +} + +func decodeResponse(resp *http.Response, v interface{}) error { + if resp.Body == nil { + return nil + } + rbody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("error reading response body: %s", err) + } + if err = json.Unmarshal(rbody, &v); err != nil { + return err + } + return nil +} + +func ResponseJSON(resp *http.Response) (jbody map[string]interface{}, err error) { + err = decodeResponse(resp, &jbody) + return jbody, err +} + +func TypedResponse(resp *http.Response, v interface{}) error { + var intermediate struct { + Data struct { + Result json.RawMessage `json:"result"` + } `json:"data"` + } + err := decodeResponse(resp, &intermediate) + if err != nil { + return fmt.Errorf("error reading response envelope: %v", err) + } + if err = json.Unmarshal(intermediate.Data.Result, v); err != nil { + return fmt.Errorf("error unmarshalling result %v", err) + } + return nil +} + +func (s *Session) Login(username string, password string) (err error) { + reqbody := ParamsToBody(map[string]interface{}{"username": username, "password": password}) + olddebug := *Debug + *Debug = false // don't share passwords in debug log + resp, err := s.Post("/access/ticket", nil, nil, &reqbody) + *Debug = olddebug + if err != nil { + return err + } + if resp == nil { + return errors.New("Login error reading response") + } + dr, _ := httputil.DumpResponse(resp, true) + jbody, err := ResponseJSON(resp) + if err != nil { + return err + } + if jbody == nil || jbody["data"] == nil { + return fmt.Errorf("Invalid login response:\n-----\n%s\n-----", dr) + } + dat := jbody["data"].(map[string]interface{}) + s.AuthTicket = dat["ticket"].(string) + s.CsrfToken = dat["CSRFPreventionToken"].(string) + return nil +} + +func (s *Session) NewRequest(method, url string, headers *http.Header, body io.Reader) (req *http.Request, err error) { + req, err = http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + if headers != nil { + req.Header = *headers + } + if s.AuthTicket != "" { + req.Header.Add("Cookie", "PVEAuthCookie="+s.AuthTicket) + req.Header.Add("CSRFPreventionToken", s.CsrfToken) + } + return +} + +func (s *Session) Do(req *http.Request) (*http.Response, error) { + // Add session headers + for k := range s.Headers { + req.Header.Set(k, s.Headers.Get(k)) + } + + if *Debug { + d, _ := httputil.DumpRequestOut(req, true) + log.Printf(">>>>>>>>>> REQUEST:\n", string(d)) + } + + resp, err := s.httpClient.Do(req) + + if err != nil { + return nil, err + } + + if *Debug { + dr, _ := httputil.DumpResponse(resp, true) + log.Printf("<<<<<<<<<< RESULT:\n", string(dr)) + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return resp, errors.New(resp.Status) + } + + return resp, nil +} + +// Perform a simple get to an endpoint +func (s *Session) Request( + method string, + url string, + params *url.Values, + headers *http.Header, + body *[]byte, +) (resp *http.Response, err error) { + // add params to url here + url = s.ApiUrl + url + if params != nil { + url = url + "?" + params.Encode() + } + + // Get the body if one is present + var buf io.Reader + if body != nil { + buf = bytes.NewReader(*body) + } + + req, err := s.NewRequest(method, url, headers, buf) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + + return s.Do(req) +} + +// Perform a simple get to an endpoint and unmarshall returned JSON +func (s *Session) RequestJSON( + method string, + url string, + params *url.Values, + headers *http.Header, + body interface{}, + responseContainer interface{}, +) (resp *http.Response, err error) { + var bodyjson []byte + if body != nil { + bodyjson, err = json.Marshal(body) + if err != nil { + return nil, err + } + } + + // if headers == nil { + // headers = &http.Header{} + // headers.Add("Content-Type", "application/json") + // } + + resp, err = s.Request(method, url, params, headers, &bodyjson) + if err != nil { + return resp, err + } + + // err = util.CheckHTTPResponseStatusCode(resp) + // if err != nil { + // return nil, err + // } + + rbody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return resp, errors.New("error reading response body") + } + if err = json.Unmarshal(rbody, &responseContainer); err != nil { + return resp, err + } + + return resp, nil +} + +func (s *Session) Delete( + url string, + params *url.Values, + headers *http.Header, +) (resp *http.Response, err error) { + return s.Request("DELETE", url, params, headers, nil) +} + +func (s *Session) Get( + url string, + params *url.Values, + headers *http.Header, +) (resp *http.Response, err error) { + return s.Request("GET", url, params, headers, nil) +} + +func (s *Session) GetJSON( + url string, + params *url.Values, + headers *http.Header, + responseContainer interface{}, +) (resp *http.Response, err error) { + return s.RequestJSON("GET", url, params, headers, nil, responseContainer) +} + +func (s *Session) Head( + url string, + params *url.Values, + headers *http.Header, +) (resp *http.Response, err error) { + return s.Request("HEAD", url, params, headers, nil) +} + +func (s *Session) Post( + url string, + params *url.Values, + headers *http.Header, + body *[]byte, +) (resp *http.Response, err error) { + if headers == nil { + headers = &http.Header{} + headers.Add("Content-Type", "application/x-www-form-urlencoded") + } + return s.Request("POST", url, params, headers, body) +} + +func (s *Session) PostJSON( + url string, + params *url.Values, + headers *http.Header, + body interface{}, + responseContainer interface{}, +) (resp *http.Response, err error) { + return s.RequestJSON("POST", url, params, headers, body, responseContainer) +} + +func (s *Session) Put( + url string, + params *url.Values, + headers *http.Header, + body *[]byte, +) (resp *http.Response, err error) { + if headers == nil { + headers = &http.Header{} + headers.Add("Content-Type", "application/x-www-form-urlencoded") + } + return s.Request("PUT", url, params, headers, body) +} diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/util.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/util.go new file mode 100644 index 000000000..5841c421f --- /dev/null +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/util.go @@ -0,0 +1,62 @@ +package proxmox + +import ( + "strconv" + "strings" +) + +func inArray(arr []string, str string) bool { + for _, elem := range arr { + if elem == str { + return true + } + } + + return false +} + +func Itob(i int) bool { + if i == 1 { + return true + } + return false +} + +// ParseSubConf - Parse standard sub-conf strings `key=value`. +func ParseSubConf( + element string, + separator string, +) (key string, value interface{}) { + if strings.Contains(element, separator) { + conf := strings.Split(element, separator) + key, value := conf[0], conf[1] + var interValue interface{} + + // Make sure to add value in right type, + // because all subconfig are returned as strings from Proxmox API. + if iValue, err := strconv.ParseInt(value, 10, 64); err == nil { + interValue = int(iValue) + } else if bValue, err := strconv.ParseBool(value); err == nil { + interValue = bValue + } else { + interValue = value + } + return key, interValue + } + return +} + +// ParseConf - Parse standard device conf string `key1=val1,key2=val2`. +func ParseConf( + kvString string, + confSeparator string, + subConfSeparator string, +) QemuDevice { + var confMap = QemuDevice{} + confList := strings.Split(kvString, confSeparator) + for _, item := range confList { + key, value := ParseSubConf(item, subConfSeparator) + confMap[key] = value + } + return confMap +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 44b60e07e..738aef533 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -230,6 +230,12 @@ "revision": "c2e73f942591b0f033a3c6df00f44badb2347c38", "revisionTime": "2018-01-10T05:50:12Z" }, + { + "checksumSHA1": "xp5QPaPy/Viwiv5P8lsJVYlaQ+U=", + "path": "github.com/Telmate/proxmox-api-go/proxmox", + "revision": "7402b5d3034a3e4dea5af6afdfd106a2e353f2da", + "revisionTime": "2019-03-16T14:21:38Z" + }, { "checksumSHA1": "HttiPj314X1a0i2Jen1p6lRH/vE=", "path": "github.com/aliyun/aliyun-oss-go-sdk/oss",