Merge branch 'master' into circle_ci_windows

This commit is contained in:
Adrien Delorme 2019-09-10 12:52:52 +02:00 committed by Adrien Delorme
commit aa9cc919ab
22 changed files with 898 additions and 141 deletions

View File

@ -23,6 +23,7 @@
* core: Fix bug where sensitive variables contianing commas were not being
properly sanitized in UI calls. [GH-7997]
* provisioner/ansible: Fix provisioner dropped errors [GH-8045]
* builder/proxmox: Fix panic caused by cancelling build [GH-8067] [GH-8072]
## 1.4.3 (August 14, 2019)

View File

@ -23,9 +23,10 @@ type Config struct {
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
AMIENASupport *bool `mapstructure:"ena_support"`
AMISriovNetSupport bool `mapstructure:"sriov_support"`
AMIENASupport *bool `mapstructure:"ena_support"`
AMISriovNetSupport bool `mapstructure:"sriov_support"`
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
launchBlockDevices awscommon.BlockDevices
ctx interpolate.Context
@ -120,8 +121,8 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
BlockDevices: b.config.launchBlockDevices,
BlockDurationMinutes: b.config.BlockDurationMinutes,
Ctx: b.config.ctx,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs",
@ -129,12 +130,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotInstanceTypes: b.config.SpotInstanceTypes,
SpotPrice: b.config.SpotPrice,
SpotTags: b.config.SpotTags,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
@ -154,6 +156,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
}
}

View File

@ -3,7 +3,9 @@ package ebsvolume
import (
"context"
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@ -19,6 +21,7 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
volumes := make(EbsVolumes)
for _, instanceBlockDevices := range instance.BlockDeviceMappings {
@ -36,8 +39,50 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
state.Put("ebsvolumes", volumes)
if len(s.VolumeMapping) > 0 {
ui.Say("Tagging EBS volumes...")
// If run_volume_tags were set in the template any attached EBS
// volume will have had these tags applied when the instance was
// created. We now need to remove these tags to ensure only the EBS
// volume tags are applied (if any)
if config.VolumeRunTags.IsSet() {
ui.Say("Removing any tags applied to EBS volumes when the source instance was created...")
ui.Message("Compiling list of existing tags to remove...")
existingTags, err := config.VolumeRunTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error generating list of tags to remove: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
existingTags.Report(ui)
// Generate the list of volumes with tags to delete.
// Looping over the instance block device mappings allows us to
// obtain the volumeId
volumeIds := []string{}
for _, mapping := range s.VolumeMapping {
for _, v := range instance.BlockDeviceMappings {
if *v.DeviceName == mapping.DeviceName {
volumeIds = append(volumeIds, *v.Ebs.VolumeId)
}
}
}
// Delete the tags
ui.Message(fmt.Sprintf("Deleting 'run_volume_tags' on EBS Volumes: %s", strings.Join(volumeIds, ", ")))
_, err = ec2conn.DeleteTags(&ec2.DeleteTagsInput{
Resources: aws.StringSlice(volumeIds),
Tags: existingTags,
})
if err != nil {
err := fmt.Errorf("Error deleting tags on EBS Volumes %s: %s", strings.Join(volumeIds, ", "), err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Tagging EBS volumes...")
toTag := map[string][]*ec2.Tag{}
for _, mapping := range s.VolumeMapping {
if len(mapping.Tags) == 0 {
@ -45,15 +90,19 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
continue
}
ui.Message(fmt.Sprintf("Compiling list of tags to apply to volume on %s...", mapping.DeviceName))
tags, err := mapping.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, state)
if err != nil {
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
err := fmt.Errorf("Error generating tags for device %s: %s", mapping.DeviceName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
tags.Report(ui)
// Generate the map of volumes and associated tags to apply.
// Looping over the instance block device mappings allows us to
// obtain the volumeId
for _, v := range instance.BlockDeviceMappings {
if *v.DeviceName == mapping.DeviceName {
toTag[*v.Ebs.VolumeId] = tags
@ -61,9 +110,11 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
}
}
// Apply the tags
for volumeId, tags := range toTag {
ui.Message(fmt.Sprintf("Applying tags to EBS Volume: %s", volumeId))
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
Resources: []*string{&volumeId},
Resources: aws.StringSlice([]string{volumeId}),
Tags: tags,
})
if err != nil {
@ -72,7 +123,6 @@ func (s *stepTagEBSVolumes) Run(ctx context.Context, state multistep.StateBag) m
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}

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

@ -103,7 +103,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
c.RawBootKeyInterval = os.Getenv(common.PackerKeyEnv)
}
if c.RawBootKeyInterval == "" {
c.BootKeyInterval = common.PackerKeyDefault
c.BootKeyInterval = 5 * time.Millisecond
} else {
if interval, err := time.ParseDuration(c.RawBootKeyInterval); err == nil {
c.BootKeyInterval = interval
@ -151,6 +151,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
log.Printf("Disk %d cache mode not set, using default 'none'", idx)
c.Disks[idx].CacheMode = "none"
}
// For any storage pool types which aren't in rxStorageTypes in proxmox-api/proxmox/config_qemu.go:651
// (currently zfspool and lvm), the format parameter is mandatory. Make sure this is still up to date
// when updating the vendored code!
if !contains([]string{"zfspool", "lvm"}, c.Disks[idx].StoragePoolType) && c.Disks[idx].DiskFormat == "" {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("disk format must be specified for pool type %q", c.Disks[idx].StoragePoolType)))
}
}
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
@ -197,3 +203,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
packer.LogSecretFilter.Set(c.Password)
return c, nil, nil
}
func contains(haystack []string, needle string) bool {
for _, candidate := range haystack {
if candidate == needle {
return true
}
}
return false
}

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

View File

@ -35,21 +35,19 @@ param([string]$switchName, [int]$addressIndex)
$HostVMAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $switchName
if ($HostVMAdapter){
$HostNetAdapter = Get-NetAdapter | Where-Object { $_.DeviceId -eq $HostVMAdapter.DeviceId }
if ($HostNetAdapter){
$HostNetAdapterIfIndex = @()
$HostNetAdapterIfIndex += $HostNetAdapter.ifIndex
$HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE'") | Where-Object { $HostNetAdapterIfIndex.Contains($_.InterfaceIndex) }
if ($HostNetAdapterConfiguration){
return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex]
}
}
}
else {
if ($HostNetAdapter){
$HostNetAdapterIfIndex = @()
$HostNetAdapterIfIndex += $HostNetAdapter.ifIndex
$HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE'") | Where-Object { $HostNetAdapterIfIndex.Contains($_.InterfaceIndex)
if ($HostNetAdapterConfiguration){
return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex]
}
}
} else {
$HostNetAdapterConfiguration=@(Get-NetIPAddress -CimSession $env:computername -AddressFamily IPv4 | Where-Object { ( $_.InterfaceAlias -notmatch 'Loopback' ) -and ( $_.SuffixOrigin -notmatch "Link" )})
if ($HostNetAdapterConfiguration) {
return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex]
}
else {
} else {
return $false
}
}

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/common
github.com/NaverCloudPlatform/ncloud-sdk-go/oauth
github.com/NaverCloudPlatform/ncloud-sdk-go/request
github.com/NaverCloudPlatform/ncloud-sdk-go/sdk
# 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

View File

@ -16,16 +16,22 @@ power of Packer templates.
## Third-Party plugins
This is an incomplete list. If you have written your own plugin, please make a
pull request to the website so that we can document your contribution here!
The plugins listed below have been built by the community of Packer users and
vendors. These plugins are not officially tested nor officially maintained by
HashiCorp, and are listed here in order to help users find them easily.
To learn more about how to use community plugins, or how to build your own,
check out the docs on [extending Packer](/docs/extending/plugins.html)
If you have built a plugin and would like to add it to this community list,
please make a pull request to the website so that we can document your
contribution here!
### Community Builders
- [ARM builder](https://github.com/solo-io/packer-builder-arm-image) - A builder
for creating ARM images
- [Comment Provisioner](https://github.com/SwampDragons/packer-provisioner-comment) -
Example provisioner that allows you to annotate your build with bubble-text
comments.
- [vSphere builder](https://github.com/jetbrains-infra/packer-builder-vsphere) -
A builder for interacting directly with the vSphere API rather than the esx
host directly.
@ -33,6 +39,13 @@ pull request to the website so that we can document your contribution here!
- [Vultr builder](https://github.com/vultr/packer-builder-vultr) - A builder
for creating [Vultr](https://www.vultr.com/) snapshots.
### Community Provisioners
- [Comment Provisioner](https://github.com/SwampDragons/packer-provisioner-comment) -
Example provisioner that allows you to annotate your build with bubble-text
comments.
- [Windows Update provisioner](https://github.com/rgl/packer-provisioner-windows-update) -
A provisioner for gracefully handling windows updates and the reboots they
cause.

View File

@ -22,6 +22,10 @@ description: |-
<strong>Discussion list:</strong>
<a href="https://groups.google.com/group/packer-tool">Packer Google Group</a>
</p>
<p>
<strong>Community Forum:</strong>
<a href="https://discuss.hashicorp.com/c/packer">Packer Community Forum</a>
</p>
<p>
<strong>Bug Tracker:</strong>
<a href="https://github.com/hashicorp/packer/issues">Issue tracker

View File

@ -38,6 +38,11 @@ builder.
- `access_key` (string) - The access key used to communicate with AWS. [Learn
how to set this](/docs/builders/amazon.html#specifying-amazon-credentials)
- `ami_name` (string) - The name of the resulting AMI that will appear when
managing AMIs in the AWS console or via APIs. This must be unique. To help
make this unique, use a function like `timestamp` (see [template
engine](../templates/engine.html) for more info).
- `instance_type` (string) - The EC2 instance type to use while building the
AMI, such as `m1.small`.

View File

@ -1,7 +1,7 @@
---
description: |
The amazon-ebsvolume Packer builder is like the EBS builder, but is intended to
create EBS volumes rather than a machine image.
The amazon-ebsvolume Packer builder is like the EBS builder, but is
intended to create EBS volumes rather than a machine image.
layout: docs
page_title: 'Amazon EBS Volume - Builders'
sidebar_current: 'docs-builders-amazon-ebsvolume'
@ -14,9 +14,11 @@ Type: `amazon-ebsvolume`
The `amazon-ebsvolume` Packer builder is able to create Amazon Elastic Block
Store volumes which are prepopulated with filesystems or data.
This builder builds EBS volumes by launching an EC2 instance from a source AMI,
provisioning that running machine, and then destroying the source machine,
keeping the volumes intact.
This builder creates EBS volumes by launching an EC2 instance from a source
AMI. One or more EBS volumes are attached to the running instance, allowing
them to be provisioned into from the running machine. Once provisioning is
complete the source machine is destroyed. The provisioned volumes are kept
intact.
This is all done in your own AWS account. The builder will create temporary key
pairs, security group rules, etc. that provide it temporary access to the
@ -44,11 +46,11 @@ builder.
- `access_key` (string) - The access key used to communicate with AWS. [Learn
how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials)
- `instance_type` (string) - The EC2 instance type to use while building the
AMI, such as `m1.small`.
- `instance_type` (string) - The EC2 instance type to use while creating
the EBS volumes, such as `m1.small`.
- `region` (string) - The name of the region, such as `us-east-1`, in which
to launch the EC2 instance to create the AMI.
to launch the EC2 instance to create the new EBS volumes.
- `secret_key` (string) - The secret key used to communicate with AWS. [Learn
how to set this.](/docs/builders/amazon.html#specifying-amazon-credentials)
@ -60,7 +62,7 @@ builder.
### Optional:
- `ebs_volumes` (array of block device mappings) - Add the block device
mappings to the AMI. The block device mappings allow for keys:
mappings to the running instance. The block device mappings allow for keys:
- `device_name` (string) - The device name exposed to the instance (for
example, `/dev/sdh` or `xvdh`). Required for every device in the block
@ -82,33 +84,39 @@ builder.
- `iops` (number) - The number of I/O operations per second (IOPS) that
the volume supports. See the documentation on
[IOPs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html)
for more information
for more information.
- `no_device` (boolean) - Suppresses the specified device included in the
block device mapping of the AMI
block device mapping of the AMI.
- `snapshot_id` (string) - The ID of the snapshot
- `snapshot_id` (string) - The ID of the snapshot.
- `virtual_name` (string) - The virtual device name. See the
documentation on [Block Device
Mapping](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html)
for more information
for more information.
- `volume_size` (number) - The size of the volume, in GiB. Required if
not specifying a `snapshot_id`
not specifying a `snapshot_id`.
- `volume_type` (string) - The volume type. `gp2` for General Purpose
(SSD) volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard`
for Magnetic volumes
for Magnetic volumes.
- `tags` (map) - Tags to apply to the volume. These are retained after
the builder completes. This is a [template
engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
Note: The tags specified here may be *temporarily* overridden by
those specified in `run_volume_tags` - but only while the instance
is being created. Packer will replace all tags on the volume with
those configured here as soon as the instance is reported as
'ready'.
- `associate_public_ip_address` (boolean) - If using a non-default VPC,
public IP addresses are not provided by default. If this is `true`, your
new instance will get a Public IP. default: `false`
new instance will get a Public IP. Default: `false`.
- `availability_zone` (string) - Destination availability zone to launch
instance in. Leave this empty to allow Amazon to auto-assign.
@ -158,11 +166,9 @@ builder.
- `ena_support` (boolean) - Enable enhanced networking (ENA but not
SriovNetSupport) on HVM-compatible AMIs. If set, add
`ec2:ModifyInstanceAttribute` to your AWS IAM policy. If false, this will
disable enhanced networking in the final AMI as opposed to passing the
setting through unchanged from the source. Note: you must make sure
enhanced networking is enabled on your instance. See [Amazon's
documentation on enabling enhanced
`ec2:ModifyInstanceAttribute` to your AWS IAM policy. Note: you must
make sure enhanced networking is enabled on your instance. See
[Amazon's documentation on enabling enhanced
networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
- `enable_t2_unlimited` (boolean) - Enabling T2 Unlimited allows the source
@ -206,11 +212,23 @@ builder.
for more details.
- `run_tags` (object of key/value strings) - Tags to apply to the instance
that is *launched* to create the AMI. These tags are *not* applied to the
resulting AMI unless they're duplicated in `tags`. This is a [template
that is *launched* to create the EBS volumes. This is a [template
engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `run_volume_tags` (object of key/value strings) - Tags to apply to the
volumes of the instance that is *launched* to create EBS Volumes. These
tags will *not* appear in the tags of the resulting EBS volumes unless
they're duplicated under `tags` in the `ebs_volumes` setting. This is a
[template engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
Note: The tags specified here will be *temporarily* applied to volumes
specified in `ebs_volumes` - but only while the instance is being
created. Packer will replace all tags on the volume with the tags
configured in the `ebs_volumes` section as soon as the instance is
reported as 'ready'.
- `security_group_id` (string) - The ID (*not* the name) of the security
group to assign to the instance. By default this is not set and Packer will
automatically create a new temporary security group to allow SSH access.
@ -250,16 +268,6 @@ builder.
- `skip_region_validation` (boolean) - Set to `true` if you want to skip
validation of the region configuration option. Defaults to `false`.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). `all` will make the snapshot
publicly accessible.
- `snapshot_users` (array of strings) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the AMI has permissions to create
volumes from the backing snapshot(s).
- `source_ami_filter` (object) - Filters used to populate the `source_ami`
field. Example:
@ -376,9 +384,12 @@ builder.
`packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
- `temporary_security_group_source_cidrs` (list of string) - A list of IPv4
CIDR blocks to be authorized access to the instance, when packer is creating a temporary security group.
CIDR blocks to be authorized access to the instance, when packer is
creating a temporary security group.
The default is [`0.0.0.0/0`] (i.e., allow any IPv4 source). This is only used when `security_group_id` or `security_group_ids` is not specified.
The default is [`0.0.0.0/0`] (i.e., allow any IPv4 source). This is
only used when `security_group_id` or `security_group_ids` is not
specified.
- `token` (string) - The access token to use. This is different from the
access key and secret key. If you're not sure what this is, then you

View File

@ -14,6 +14,8 @@ interpolations. You may access variables in the Packer config you called the
console with, or provide variables when you call console using the -var or
-var-file command line options.
~> **Note:** `console` is available from version 1.4.2 and above.
Type in the interpolation to test and hit \<enter\> to see the result.
To exit the console, type "exit" and hit \<enter\>, or use Control-C.