Merge pull request #8083 from carlpett/bump-proxmox-api-dep

Bump proxmox api dep
This commit is contained in:
Adrien Delorme 2019-09-09 10:33:18 +02:00 committed by GitHub
commit 78bfda582f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 731 additions and 76 deletions

View File

@ -102,13 +102,10 @@ func (p *proxmoxDriver) SendSpecial(special string, action bootcommand.KeyAction
}
func (p *proxmoxDriver) send(keys string) error {
res, err := p.client.MonitorCmd(p.vmRef, "sendkey "+keys)
err := p.client.Sendkey(p.vmRef, keys)
if err != nil {
return err
}
if data, ok := res["data"].(string); ok && len(data) > 0 {
return fmt.Errorf("failed to send keys: %s", data)
}
time.Sleep(p.interval)
return nil

View File

@ -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
}

View File

@ -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
}

View File

@ -30,7 +30,7 @@ type bootCommandTemplateData struct {
}
type commandTyper interface {
MonitorCmd(*proxmox.VmRef, string) (map[string]interface{}, error)
Sendkey(*proxmox.VmRef, string) error
}
var _ commandTyper = &proxmox.Client{}

View File

@ -13,75 +13,74 @@ import (
)
type commandTyperMock struct {
monitorCmd func(*proxmox.VmRef, string) (map[string]interface{}, error)
sendkey func(*proxmox.VmRef, string) error
}
func (m commandTyperMock) MonitorCmd(ref *proxmox.VmRef, cmd string) (map[string]interface{}, error) {
return m.monitorCmd(ref, cmd)
func (m commandTyperMock) Sendkey(ref *proxmox.VmRef, cmd string) error {
return m.sendkey(ref, cmd)
}
var _ commandTyper = commandTyperMock{}
func TestTypeBootCommand(t *testing.T) {
cs := []struct {
name string
builderConfig *Config
expectCallMonitorCmd bool
monitorCmdErr error
monitorCmdRet map[string]interface{}
expectedKeysSent string
expectedAction multistep.StepAction
name string
builderConfig *Config
expectCallSendkey bool
sendkeyErr error
expectedKeysSent string
expectedAction multistep.StepAction
}{
{
name: "simple boot command is typed",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello"}}},
expectCallMonitorCmd: true,
expectedKeysSent: "hello",
expectedAction: multistep.ActionContinue,
name: "simple boot command is typed",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello"}}},
expectCallSendkey: true,
expectedKeysSent: "hello",
expectedAction: multistep.ActionContinue,
},
{
name: "interpolated boot command",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello<enter>world"}}},
expectCallMonitorCmd: true,
expectedKeysSent: "helloretworld",
expectedAction: multistep.ActionContinue,
name: "interpolated boot command",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello<enter>world"}}},
expectCallSendkey: true,
expectedKeysSent: "helloretworld",
expectedAction: multistep.ActionContinue,
},
{
name: "merge multiple interpolated boot command",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"Hello World 2.0", "foo!bar@baz"}}},
expectCallMonitorCmd: true,
expectedKeysSent: "shift-hellospcshift-worldspc2dot0fooshift-1barshift-2baz",
expectedAction: multistep.ActionContinue,
name: "merge multiple interpolated boot command",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"Hello World 2.0", "foo!bar@baz"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-hellospcshift-worldspc2dot0fooshift-1barshift-2baz",
expectedAction: multistep.ActionContinue,
},
{
name: "without boot command monitorcmd should not be called",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{}}},
expectCallMonitorCmd: false,
expectedAction: multistep.ActionContinue,
name: "without boot command sendkey should not be called",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{}}},
expectCallSendkey: false,
expectedAction: multistep.ActionContinue,
},
{
name: "invalid boot command template function",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"{{ foo }}"}}},
expectCallMonitorCmd: false,
expectedAction: multistep.ActionHalt,
name: "invalid boot command template function",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"{{ foo }}"}}},
expectCallSendkey: false,
expectedAction: multistep.ActionHalt,
},
{
// When proxmox (or Qemu, really) doesn't recognize the keycode we send, we get no error back, but
// a map {"data": "invalid parameter: X"}, where X is the keycode.
name: "invalid keys sent to proxmox",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"x"}}},
expectCallMonitorCmd: true,
monitorCmdRet: map[string]interface{}{"data": "invalid parameter: x"},
expectedKeysSent: "x",
expectedAction: multistep.ActionHalt,
name: "invalid keys sent to proxmox",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"x"}}},
expectCallSendkey: true,
sendkeyErr: fmt.Errorf("invalid parameter: x"),
expectedKeysSent: "x",
expectedAction: multistep.ActionHalt,
},
{
name: "error in typing should return halt",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello"}}},
expectCallMonitorCmd: true,
monitorCmdErr: fmt.Errorf("some error"),
expectedKeysSent: "h",
expectedAction: multistep.ActionHalt,
name: "error in typing should return halt",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"hello"}}},
expectCallSendkey: true,
sendkeyErr: fmt.Errorf("some error"),
expectedKeysSent: "h",
expectedAction: multistep.ActionHalt,
},
}
@ -89,17 +88,14 @@ func TestTypeBootCommand(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
accumulator := strings.Builder{}
typer := commandTyperMock{
monitorCmd: func(ref *proxmox.VmRef, cmd string) (map[string]interface{}, error) {
if !c.expectCallMonitorCmd {
t.Error("Did not expect MonitorCmd to be called")
}
if !strings.HasPrefix(cmd, "sendkey ") {
t.Errorf("Expected all commands to be sendkey, got %s", cmd)
sendkey: func(ref *proxmox.VmRef, cmd string) error {
if !c.expectCallSendkey {
t.Error("Did not expect sendkey to be called")
}
accumulator.WriteString(strings.TrimPrefix(cmd, "sendkey "))
accumulator.WriteString(cmd)
return c.monitorCmdRet, c.monitorCmdErr
return c.sendkeyErr
},
}

2
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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{}

View File

@ -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, &paramMap)
// 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, &paramMap)
// 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
}

View File

@ -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
}

View File

@ -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

2
vendor/modules.txt vendored
View File

@ -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