commit
85e3b00e5a
@ -61,7 +61,10 @@ type config struct {
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
ExportFormat string `mapstructure:"export_format"`
|
||||
|
||||
KeepInstance string `mapstructure:"keep_instance"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
@ -84,12 +87,12 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
}
|
||||
|
||||
self.config.tpl, err = packer.NewConfigTemplate()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
self.config.tpl.UserVars = self.config.PackerUserVars
|
||||
|
||||
// Set default vaules
|
||||
// Set default values
|
||||
|
||||
if self.config.HostPortMin == 0 {
|
||||
self.config.HostPortMin = 5900
|
||||
@ -119,10 +122,39 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
self.config.RawSSHWaitTimeout = "200m"
|
||||
}
|
||||
|
||||
if self.config.InstanceMemory == "" {
|
||||
self.config.InstanceMemory = "1024000000"
|
||||
}
|
||||
|
||||
if self.config.CloneTemplate == "" {
|
||||
self.config.CloneTemplate = "Other install media"
|
||||
}
|
||||
|
||||
if self.config.OutputDir == "" {
|
||||
self.config.OutputDir = fmt.Sprintf("output-%s", self.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if self.config.ExportFormat == "" {
|
||||
self.config.ExportFormat = "xva"
|
||||
}
|
||||
|
||||
if self.config.KeepInstance == "" {
|
||||
self.config.KeepInstance = "never"
|
||||
}
|
||||
|
||||
if len(self.config.PlatformArgs) == 0 {
|
||||
pargs := make(map[string]string)
|
||||
pargs["viridian"] = "false"
|
||||
pargs["nx"] = "true"
|
||||
pargs["pae"] = "true"
|
||||
pargs["apic"] = "true"
|
||||
pargs["timeoffset"] = "0"
|
||||
pargs["acpi"] = "1"
|
||||
self.config.PlatformArgs = pargs
|
||||
}
|
||||
|
||||
// Template substitution
|
||||
|
||||
templates := map[string]*string{
|
||||
"username": &self.config.Username,
|
||||
"password": &self.config.Password,
|
||||
@ -146,6 +178,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
"ssh_password": &self.config.SSHPassword,
|
||||
"ssh_key_path": &self.config.SSHKeyPath,
|
||||
"output_directory": &self.config.OutputDir,
|
||||
"export_format": &self.config.ExportFormat,
|
||||
"keep_instance": &self.config.KeepInstance,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
@ -156,6 +190,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
}
|
||||
}
|
||||
|
||||
// Validation
|
||||
|
||||
/*
|
||||
if self.config.IsoUrl == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
@ -166,7 +202,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
self.config.BootWait, err = time.ParseDuration(self.config.RawBootWait)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Failed to parse boot_wait."))
|
||||
errs, fmt.Errorf("Failed to parse boot_wait: %s", err))
|
||||
}
|
||||
|
||||
self.config.SSHWaitTimeout, err = time.ParseDuration(self.config.RawSSHWaitTimeout)
|
||||
@ -223,17 +259,23 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
errs, errors.New("An instance name must be specified."))
|
||||
}
|
||||
|
||||
if self.config.InstanceMemory == "" {
|
||||
self.config.InstanceMemory = "1024000000"
|
||||
}
|
||||
|
||||
if self.config.RootDiskSize == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("A root disk size must be specified."))
|
||||
}
|
||||
|
||||
if self.config.CloneTemplate == "" {
|
||||
self.config.CloneTemplate = "Other install media"
|
||||
switch self.config.ExportFormat {
|
||||
case "xva", "vdi_raw":
|
||||
default:
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("export_format must be one of 'xva', 'vdi_raw'"))
|
||||
}
|
||||
|
||||
switch self.config.KeepInstance {
|
||||
case "always", "never", "on_success":
|
||||
default:
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("keep_instance must be one of 'always', 'never', 'on_success'"))
|
||||
}
|
||||
|
||||
/*
|
||||
@ -243,17 +285,6 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
|
||||
}
|
||||
*/
|
||||
|
||||
if len(self.config.PlatformArgs) == 0 {
|
||||
pargs := make(map[string]string)
|
||||
pargs["viridian"] = "false"
|
||||
pargs["nx"] = "true"
|
||||
pargs["pae"] = "true"
|
||||
pargs["apic"] = "true"
|
||||
pargs["timeoffset"] = "0"
|
||||
pargs["acpi"] = "1"
|
||||
self.config.PlatformArgs = pargs
|
||||
}
|
||||
|
||||
if self.config.HTTPPortMin > self.config.HTTPPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("the HTTP min port must be less than the max"))
|
||||
@ -392,6 +423,23 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
// all steps should check config.ShouldKeepInstance first before cleaning up
|
||||
func (cfg config) ShouldKeepInstance(state multistep.StateBag) bool {
|
||||
switch cfg.KeepInstance {
|
||||
case "always":
|
||||
return true
|
||||
case "never":
|
||||
return false
|
||||
case "on_success":
|
||||
// only keep instance if build was successful
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
return !(cancelled || halted)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown keep_instance value '%s'", cfg.KeepInstance))
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Builder) Cancel() {
|
||||
if self.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
|
25
builder/xenserver/find_port.go
Normal file
25
builder/xenserver/find_port.go
Normal file
@ -0,0 +1,25 @@
|
||||
package xenserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
// FindPort finds and starts listening on a port in the range [portMin, portMax]
|
||||
// returns the listener and the port number on success, or nil, 0 on failure
|
||||
func FindPort(portMin uint, portMax uint) (net.Listener, uint) {
|
||||
log.Printf("Looking for an available port between %d and %d", portMin, portMax)
|
||||
|
||||
for port := portMin; port <= portMax; port++ {
|
||||
log.Printf("Trying port: %d", port)
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err == nil {
|
||||
return l, port
|
||||
} else {
|
||||
log.Printf("Port %d unavailable: %s", port, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
@ -84,19 +84,22 @@ func execute_ssh_cmd(cmd, host, port, username, password string) (stdout string,
|
||||
}
|
||||
|
||||
func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_dest string, remote_port uint) error {
|
||||
defer local_conn.Close()
|
||||
|
||||
ssh_client_conn, err := gossh.Dial("tcp", server+":22", config)
|
||||
if err != nil {
|
||||
log.Printf("local ssh.Dial error: %s", err)
|
||||
return err
|
||||
}
|
||||
defer ssh_client_conn.Close()
|
||||
|
||||
remote_loc := fmt.Sprintf("%s:%d", remote_dest, remote_port)
|
||||
ssh_conn, err := ssh_client_conn.Dial("tcp", remote_loc)
|
||||
if err != nil {
|
||||
log.Printf("ssh.Dial error: %s", err)
|
||||
ssh_client_conn.Close()
|
||||
return err
|
||||
}
|
||||
defer ssh_conn.Close()
|
||||
|
||||
txDone := make(chan struct{})
|
||||
rxDone := make(chan struct{})
|
||||
@ -117,12 +120,8 @@ func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_des
|
||||
close(rxDone)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-txDone
|
||||
<-rxDone
|
||||
ssh_client_conn.Close()
|
||||
ssh_conn.Close()
|
||||
}()
|
||||
<-txDone
|
||||
<-rxDone
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -214,6 +214,11 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
|
||||
}
|
||||
|
||||
func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
||||
config := state.Get("config").(config)
|
||||
if config.ShouldKeepInstance(state) {
|
||||
return
|
||||
}
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if self.instance != nil {
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
type stepForwardPortOverSSH struct {
|
||||
@ -25,32 +23,15 @@ func (self *stepForwardPortOverSSH) Run(state multistep.StateBag) multistep.Step
|
||||
|
||||
// Find a free local port:
|
||||
|
||||
log.Printf("Looking for an available port between %d and %d",
|
||||
self.HostPortMin,
|
||||
self.HostPortMax)
|
||||
l, sshHostPort := FindPort(self.HostPortMin, self.HostPortMax)
|
||||
|
||||
var sshHostPort uint
|
||||
var foundPort bool
|
||||
|
||||
foundPort = false
|
||||
|
||||
for i := self.HostPortMin; i < self.HostPortMax; i++ {
|
||||
sshHostPort = i
|
||||
log.Printf("Trying port: %d", sshHostPort)
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
|
||||
if err == nil {
|
||||
l.Close()
|
||||
foundPort = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !foundPort {
|
||||
ui.Error("Error: unable to find free host port. Try providing a larger range")
|
||||
if l == nil || sshHostPort == 0 {
|
||||
ui.Error("Error: unable to find free host port. Try providing a larger range [host_port_min, host_port_max]")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
l.Close()
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating a local port forward over SSH on local port %d", sshHostPort))
|
||||
|
||||
remotePort, _ := self.RemotePort(state)
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
@ -36,32 +34,18 @@ func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Find an available TCP port for our HTTP server
|
||||
var httpAddr string
|
||||
portRange := int(config.HTTPPortMax - config.HTTPPortMin)
|
||||
for {
|
||||
var err error
|
||||
var offset uint = 0
|
||||
s.l, httpPort = FindPort(config.HTTPPortMin, config.HTTPPortMax)
|
||||
|
||||
if portRange > 0 {
|
||||
// Intn will panic if portRange == 0, so we do a check.
|
||||
offset = uint(rand.Intn(portRange))
|
||||
}
|
||||
|
||||
httpPort = offset + config.HTTPPortMin
|
||||
httpAddr = fmt.Sprintf(":%d", httpPort)
|
||||
log.Printf("Trying port: %d", httpPort)
|
||||
s.l, err = net.Listen("tcp", httpAddr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if s.l == nil || httpPort == 0 {
|
||||
ui.Error("Error: unable to find free HTTP server port. Try providing a larger range [http_port_min, http_port_max]")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
|
||||
|
||||
// Start the HTTP server and run it in the background
|
||||
fileServer := http.FileServer(http.Dir(config.HTTPDir))
|
||||
server := &http.Server{Addr: httpAddr, Handler: fileServer}
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%d", httpPort), Handler: fileServer}
|
||||
go server.Serve(s.l)
|
||||
|
||||
// Save the address into the state so it can be accessed in the future
|
||||
|
@ -65,44 +65,52 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
//Export the VM
|
||||
switch config.ExportFormat {
|
||||
case "xva":
|
||||
// export the VM
|
||||
|
||||
export_url := fmt.Sprintf("https://%s/export?vm=%s&session_id=%s",
|
||||
client.Host,
|
||||
instance.Ref,
|
||||
client.Session.(string),
|
||||
)
|
||||
|
||||
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.InstanceName)
|
||||
ui.Say("Getting metadata " + export_url)
|
||||
downloadFile(export_url, export_filename)
|
||||
|
||||
disks, err := instance.GetDisks()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get VM disks: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, disk := range disks {
|
||||
disk_uuid, err := disk.GetUuid()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get disk with UUID '%s': %s", disk_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Basic auth in URL request is required as session token is not
|
||||
// accepted for some reason.
|
||||
// @todo: raise with XAPI team.
|
||||
disk_export_url := fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s",
|
||||
client.Username,
|
||||
client.Password,
|
||||
export_url := fmt.Sprintf("https://%s/export?vm=%s&session_id=%s",
|
||||
client.Host,
|
||||
disk_uuid,
|
||||
instance_uuid,
|
||||
client.Session.(string),
|
||||
)
|
||||
|
||||
ui.Say("Getting " + disk_export_url)
|
||||
disk_export_filename := fmt.Sprintf("%s/%s.raw", config.OutputDir, disk_uuid)
|
||||
ui.Say("Downloading " + disk_uuid)
|
||||
downloadFile(disk_export_url, disk_export_filename)
|
||||
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.InstanceName)
|
||||
ui.Say("Getting XVA " + export_url)
|
||||
downloadFile(export_url, export_filename)
|
||||
|
||||
case "vdi_raw":
|
||||
// export the disks
|
||||
|
||||
disks, err := instance.GetDisks()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get VM disks: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, disk := range disks {
|
||||
disk_uuid, err := disk.GetUuid()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Could not get disk with UUID '%s': %s", disk_uuid, err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Basic auth in URL request is required as session token is not
|
||||
// accepted for some reason.
|
||||
// @todo: raise with XAPI team.
|
||||
disk_export_url := fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s",
|
||||
client.Username,
|
||||
client.Password,
|
||||
client.Host,
|
||||
disk_uuid,
|
||||
)
|
||||
|
||||
disk_export_filename := fmt.Sprintf("%s/%s.raw", config.OutputDir, disk_uuid)
|
||||
ui.Say("Getting VDI " + disk_export_url)
|
||||
downloadFile(disk_export_url, disk_export_filename)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown export_format '%s'", config.ExportFormat))
|
||||
}
|
||||
|
||||
ui.Say("Download completed: " + config.OutputDir)
|
||||
|
@ -63,7 +63,7 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
|
||||
log.Printf("Ref: %s", instance.Ref)
|
||||
|
||||
//Check for instance.Ref in map
|
||||
if vm_ip, ok := ips[himn_vif.Ref]; ok {
|
||||
if vm_ip, ok := ips[himn_vif.Ref]; ok && vm_ip != "" {
|
||||
ui.Say("Found the VM's IP: " + vm_ip)
|
||||
himn_iface_ip = vm_ip
|
||||
return true, nil
|
||||
@ -76,15 +76,13 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
|
||||
Timeout: 100 * time.Second,
|
||||
}.Wait(state)
|
||||
|
||||
if err != nil || himn_iface_ip == "" {
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to find an IP on the Host-internal management interface: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if himn_iface_ip != "" {
|
||||
state.Put("himn_ssh_address", himn_iface_ip)
|
||||
ui.Say("Stored VM's IP " + himn_iface_ip)
|
||||
}
|
||||
state.Put("himn_ssh_address", himn_iface_ip)
|
||||
ui.Say("Stored VM's IP " + himn_iface_ip)
|
||||
|
||||
// Wait for the VM to boot, and check we can ping this interface
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
[
|
||||
"<tab><wait>",
|
||||
" ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg<enter>"
|
||||
]
|
||||
],
|
||||
"keep_instance": "always"
|
||||
}]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user