github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/builder/virtualbox/builder.go (about)

     1  package virtualbox
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/mitchellh/multistep"
     7  	"github.com/mitchellh/packer/common"
     8  	"github.com/mitchellh/packer/packer"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  )
    16  
    17  const BuilderId = "mitchellh.virtualbox"
    18  
    19  type Builder struct {
    20  	config config
    21  	runner multistep.Runner
    22  }
    23  
    24  type config struct {
    25  	common.PackerConfig `mapstructure:",squash"`
    26  
    27  	BootCommand          []string   `mapstructure:"boot_command"`
    28  	DiskSize             uint       `mapstructure:"disk_size"`
    29  	FloppyFiles          []string   `mapstructure:"floppy_files"`
    30  	Format               string     `mapstructure:"format"`
    31  	GuestAdditionsAttach bool       `mapstructure:"guest_additions_attach"`
    32  	GuestAdditionsPath   string     `mapstructure:"guest_additions_path"`
    33  	GuestAdditionsURL    string     `mapstructure:"guest_additions_url"`
    34  	GuestAdditionsSHA256 string     `mapstructure:"guest_additions_sha256"`
    35  	GuestOSType          string     `mapstructure:"guest_os_type"`
    36  	HardDriveInterface   string     `mapstructure:"hard_drive_interface"`
    37  	Headless             bool       `mapstructure:"headless"`
    38  	HTTPDir              string     `mapstructure:"http_directory"`
    39  	HTTPPortMin          uint       `mapstructure:"http_port_min"`
    40  	HTTPPortMax          uint       `mapstructure:"http_port_max"`
    41  	ISOChecksum          string     `mapstructure:"iso_checksum"`
    42  	ISOChecksumType      string     `mapstructure:"iso_checksum_type"`
    43  	ISOUrls              []string   `mapstructure:"iso_urls"`
    44  	OutputDir            string     `mapstructure:"output_directory"`
    45  	ShutdownCommand      string     `mapstructure:"shutdown_command"`
    46  	SSHHostPortMin       uint       `mapstructure:"ssh_host_port_min"`
    47  	SSHHostPortMax       uint       `mapstructure:"ssh_host_port_max"`
    48  	SSHKeyPath           string     `mapstructure:"ssh_key_path"`
    49  	SSHPassword          string     `mapstructure:"ssh_password"`
    50  	SSHPort              uint       `mapstructure:"ssh_port"`
    51  	SSHUser              string     `mapstructure:"ssh_username"`
    52  	VBoxVersionFile      string     `mapstructure:"virtualbox_version_file"`
    53  	VBoxManage           [][]string `mapstructure:"vboxmanage"`
    54  	VMName               string     `mapstructure:"vm_name"`
    55  
    56  	RawBootWait        string `mapstructure:"boot_wait"`
    57  	RawSingleISOUrl    string `mapstructure:"iso_url"`
    58  	RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
    59  	RawSSHWaitTimeout  string `mapstructure:"ssh_wait_timeout"`
    60  
    61  	bootWait        time.Duration ``
    62  	shutdownTimeout time.Duration ``
    63  	sshWaitTimeout  time.Duration ``
    64  	tpl             *packer.ConfigTemplate
    65  }
    66  
    67  func (b *Builder) Prepare(raws ...interface{}) error {
    68  	md, err := common.DecodeConfig(&b.config, raws...)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	b.config.tpl, err = packer.NewConfigTemplate()
    74  	if err != nil {
    75  		return err
    76  	}
    77  	b.config.tpl.UserVars = b.config.PackerUserVars
    78  
    79  	// Accumulate any errors
    80  	errs := common.CheckUnusedConfig(md)
    81  
    82  	if b.config.DiskSize == 0 {
    83  		b.config.DiskSize = 40000
    84  	}
    85  
    86  	if b.config.FloppyFiles == nil {
    87  		b.config.FloppyFiles = make([]string, 0)
    88  	}
    89  
    90  	if b.config.GuestAdditionsPath == "" {
    91  		b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso"
    92  	}
    93  
    94  	if b.config.HardDriveInterface == "" {
    95  		b.config.HardDriveInterface = "ide"
    96  	}
    97  
    98  	if b.config.GuestOSType == "" {
    99  		b.config.GuestOSType = "Other"
   100  	}
   101  
   102  	if b.config.HTTPPortMin == 0 {
   103  		b.config.HTTPPortMin = 8000
   104  	}
   105  
   106  	if b.config.HTTPPortMax == 0 {
   107  		b.config.HTTPPortMax = 9000
   108  	}
   109  
   110  	if b.config.OutputDir == "" {
   111  		b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
   112  	}
   113  
   114  	if b.config.RawBootWait == "" {
   115  		b.config.RawBootWait = "10s"
   116  	}
   117  
   118  	if b.config.SSHHostPortMin == 0 {
   119  		b.config.SSHHostPortMin = 2222
   120  	}
   121  
   122  	if b.config.SSHHostPortMax == 0 {
   123  		b.config.SSHHostPortMax = 4444
   124  	}
   125  
   126  	if b.config.SSHPort == 0 {
   127  		b.config.SSHPort = 22
   128  	}
   129  
   130  	if b.config.VBoxManage == nil {
   131  		b.config.VBoxManage = make([][]string, 0)
   132  	}
   133  
   134  	if b.config.VBoxVersionFile == "" {
   135  		b.config.VBoxVersionFile = ".vbox_version"
   136  	}
   137  
   138  	if b.config.VMName == "" {
   139  		b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
   140  	}
   141  
   142  	if b.config.Format == "" {
   143  		b.config.Format = "ovf"
   144  	}
   145  
   146  	// Errors
   147  	templates := map[string]*string{
   148  		"guest_additions_sha256":  &b.config.GuestAdditionsSHA256,
   149  		"guest_os_type":           &b.config.GuestOSType,
   150  		"hard_drive_interface":    &b.config.HardDriveInterface,
   151  		"http_directory":          &b.config.HTTPDir,
   152  		"iso_checksum":            &b.config.ISOChecksum,
   153  		"iso_checksum_type":       &b.config.ISOChecksumType,
   154  		"iso_url":                 &b.config.RawSingleISOUrl,
   155  		"output_directory":        &b.config.OutputDir,
   156  		"shutdown_command":        &b.config.ShutdownCommand,
   157  		"ssh_password":            &b.config.SSHPassword,
   158  		"ssh_username":            &b.config.SSHUser,
   159  		"virtualbox_version_file": &b.config.VBoxVersionFile,
   160  		"vm_name":                 &b.config.VMName,
   161  		"format":                  &b.config.Format,
   162  		"boot_wait":               &b.config.RawBootWait,
   163  		"shutdown_timeout":        &b.config.RawShutdownTimeout,
   164  		"ssh_wait_timeout":        &b.config.RawSSHWaitTimeout,
   165  	}
   166  
   167  	for n, ptr := range templates {
   168  		var err error
   169  		*ptr, err = b.config.tpl.Process(*ptr, nil)
   170  		if err != nil {
   171  			errs = packer.MultiErrorAppend(
   172  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   173  		}
   174  	}
   175  
   176  	for i, url := range b.config.ISOUrls {
   177  		var err error
   178  		b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
   179  		if err != nil {
   180  			errs = packer.MultiErrorAppend(
   181  				errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
   182  		}
   183  	}
   184  
   185  	validates := map[string]*string{
   186  		"guest_additions_path": &b.config.GuestAdditionsPath,
   187  		"guest_additions_url":  &b.config.GuestAdditionsURL,
   188  	}
   189  
   190  	for n, ptr := range validates {
   191  		if err := b.config.tpl.Validate(*ptr); err != nil {
   192  			errs = packer.MultiErrorAppend(
   193  				errs, fmt.Errorf("Error parsing %s: %s", n, err))
   194  		}
   195  	}
   196  
   197  	for i, command := range b.config.BootCommand {
   198  		if err := b.config.tpl.Validate(command); err != nil {
   199  			errs = packer.MultiErrorAppend(errs,
   200  				fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
   201  		}
   202  	}
   203  
   204  	for i, file := range b.config.FloppyFiles {
   205  		var err error
   206  		b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
   207  		if err != nil {
   208  			errs = packer.MultiErrorAppend(errs,
   209  				fmt.Errorf("Error processing floppy_files[%d]: %s",
   210  					i, err))
   211  		}
   212  	}
   213  
   214  	if !(b.config.Format == "ovf" || b.config.Format == "ova") {
   215  		errs = packer.MultiErrorAppend(
   216  			errs, errors.New("invalid format, only 'ovf' or 'ova' are allowed"))
   217  	}
   218  
   219  	if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" {
   220  		errs = packer.MultiErrorAppend(
   221  			errs, errors.New("hard_drive_interface can only be ide or sata"))
   222  	}
   223  
   224  	if b.config.HTTPPortMin > b.config.HTTPPortMax {
   225  		errs = packer.MultiErrorAppend(
   226  			errs, errors.New("http_port_min must be less than http_port_max"))
   227  	}
   228  
   229  	if b.config.ISOChecksum == "" {
   230  		errs = packer.MultiErrorAppend(
   231  			errs, errors.New("Due to large file sizes, an iso_checksum is required"))
   232  	} else {
   233  		b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
   234  	}
   235  
   236  	if b.config.ISOChecksumType == "" {
   237  		errs = packer.MultiErrorAppend(
   238  			errs, errors.New("The iso_checksum_type must be specified."))
   239  	} else {
   240  		b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
   241  		if h := common.HashForType(b.config.ISOChecksumType); h == nil {
   242  			errs = packer.MultiErrorAppend(
   243  				errs,
   244  				fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
   245  		}
   246  	}
   247  
   248  	if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
   249  		errs = packer.MultiErrorAppend(
   250  			errs, errors.New("One of iso_url or iso_urls must be specified."))
   251  	} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
   252  		errs = packer.MultiErrorAppend(
   253  			errs, errors.New("Only one of iso_url or iso_urls may be specified."))
   254  	} else if b.config.RawSingleISOUrl != "" {
   255  		b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
   256  	}
   257  
   258  	for i, url := range b.config.ISOUrls {
   259  		b.config.ISOUrls[i], err = common.DownloadableURL(url)
   260  		if err != nil {
   261  			errs = packer.MultiErrorAppend(
   262  				errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
   263  		}
   264  	}
   265  
   266  	if b.config.GuestAdditionsSHA256 != "" {
   267  		b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256)
   268  	}
   269  
   270  	if !b.config.PackerForce {
   271  		if _, err := os.Stat(b.config.OutputDir); err == nil {
   272  			errs = packer.MultiErrorAppend(
   273  				errs,
   274  				fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
   275  		}
   276  	}
   277  
   278  	b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait)
   279  	if err != nil {
   280  		errs = packer.MultiErrorAppend(
   281  			errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
   282  	}
   283  
   284  	if b.config.RawShutdownTimeout == "" {
   285  		b.config.RawShutdownTimeout = "5m"
   286  	}
   287  
   288  	if b.config.RawSSHWaitTimeout == "" {
   289  		b.config.RawSSHWaitTimeout = "20m"
   290  	}
   291  
   292  	b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
   293  	if err != nil {
   294  		errs = packer.MultiErrorAppend(
   295  			errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
   296  	}
   297  
   298  	if b.config.SSHKeyPath != "" {
   299  		if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
   300  			errs = packer.MultiErrorAppend(
   301  				errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
   302  		} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
   303  			errs = packer.MultiErrorAppend(
   304  				errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
   305  		}
   306  	}
   307  
   308  	if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
   309  		errs = packer.MultiErrorAppend(
   310  			errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
   311  	}
   312  
   313  	if b.config.SSHUser == "" {
   314  		errs = packer.MultiErrorAppend(
   315  			errs, errors.New("An ssh_username must be specified."))
   316  	}
   317  
   318  	b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
   319  	if err != nil {
   320  		errs = packer.MultiErrorAppend(
   321  			errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
   322  	}
   323  
   324  	for i, args := range b.config.VBoxManage {
   325  		for j, arg := range args {
   326  			if err := b.config.tpl.Validate(arg); err != nil {
   327  				errs = packer.MultiErrorAppend(errs,
   328  					fmt.Errorf("Error processing vboxmanage[%d][%d]: %s", i, j, err))
   329  			}
   330  		}
   331  	}
   332  
   333  	if errs != nil && len(errs.Errors) > 0 {
   334  		return errs
   335  	}
   336  
   337  	return nil
   338  }
   339  
   340  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   341  	// Create the driver that we'll use to communicate with VirtualBox
   342  	driver, err := b.newDriver()
   343  	if err != nil {
   344  		return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
   345  	}
   346  
   347  	steps := []multistep.Step{
   348  		new(stepDownloadGuestAdditions),
   349  		&common.StepDownload{
   350  			Checksum:     b.config.ISOChecksum,
   351  			ChecksumType: b.config.ISOChecksumType,
   352  			Description:  "ISO",
   353  			ResultKey:    "iso_path",
   354  			Url:          b.config.ISOUrls,
   355  		},
   356  		new(stepPrepareOutputDir),
   357  		&common.StepCreateFloppy{
   358  			Files: b.config.FloppyFiles,
   359  		},
   360  		new(stepHTTPServer),
   361  		new(stepSuppressMessages),
   362  		new(stepCreateVM),
   363  		new(stepCreateDisk),
   364  		new(stepAttachISO),
   365  		new(stepAttachGuestAdditions),
   366  		new(stepAttachFloppy),
   367  		new(stepForwardSSH),
   368  		new(stepVBoxManage),
   369  		new(stepRun),
   370  		new(stepTypeBootCommand),
   371  		&common.StepConnectSSH{
   372  			SSHAddress:     sshAddress,
   373  			SSHConfig:      sshConfig,
   374  			SSHWaitTimeout: b.config.sshWaitTimeout,
   375  		},
   376  		new(stepUploadVersion),
   377  		new(stepUploadGuestAdditions),
   378  		new(common.StepProvision),
   379  		new(stepShutdown),
   380  		new(stepExport),
   381  	}
   382  
   383  	// Setup the state bag
   384  	state := new(multistep.BasicStateBag)
   385  	state.Put("cache", cache)
   386  	state.Put("config", &b.config)
   387  	state.Put("driver", driver)
   388  	state.Put("hook", hook)
   389  	state.Put("ui", ui)
   390  
   391  	// Run
   392  	if b.config.PackerDebug {
   393  		b.runner = &multistep.DebugRunner{
   394  			Steps:   steps,
   395  			PauseFn: common.MultistepDebugFn(ui),
   396  		}
   397  	} else {
   398  		b.runner = &multistep.BasicRunner{Steps: steps}
   399  	}
   400  
   401  	b.runner.Run(state)
   402  
   403  	// If there was an error, return that
   404  	if rawErr, ok := state.GetOk("error"); ok {
   405  		return nil, rawErr.(error)
   406  	}
   407  
   408  	// If we were interrupted or cancelled, then just exit.
   409  	if _, ok := state.GetOk(multistep.StateCancelled); ok {
   410  		return nil, errors.New("Build was cancelled.")
   411  	}
   412  
   413  	if _, ok := state.GetOk(multistep.StateHalted); ok {
   414  		return nil, errors.New("Build was halted.")
   415  	}
   416  
   417  	// Compile the artifact list
   418  	files := make([]string, 0, 5)
   419  	visit := func(path string, info os.FileInfo, err error) error {
   420  		if !info.IsDir() {
   421  			files = append(files, path)
   422  		}
   423  
   424  		return err
   425  	}
   426  
   427  	if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
   428  		return nil, err
   429  	}
   430  
   431  	artifact := &Artifact{
   432  		dir: b.config.OutputDir,
   433  		f:   files,
   434  	}
   435  
   436  	return artifact, nil
   437  }
   438  
   439  func (b *Builder) Cancel() {
   440  	if b.runner != nil {
   441  		log.Println("Cancelling the step runner...")
   442  		b.runner.Cancel()
   443  	}
   444  }
   445  
   446  func (b *Builder) newDriver() (Driver, error) {
   447  	vboxmanagePath, err := exec.LookPath("VBoxManage")
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  
   452  	log.Printf("VBoxManage path: %s", vboxmanagePath)
   453  	driver := &VBox42Driver{vboxmanagePath}
   454  	if err := driver.Verify(); err != nil {
   455  		return nil, err
   456  	}
   457  
   458  	return driver, nil
   459  }