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