github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/builder/virtualbox/iso/builder.go (about)

     1  package iso
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"math/rand"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/mitchellh/multistep"
    12  	vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
    13  	"github.com/mitchellh/packer/common"
    14  	"github.com/mitchellh/packer/packer"
    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  	vboxcommon.ExportConfig         `mapstructure:",squash"`
    27  	vboxcommon.ExportOpts           `mapstructure:",squash"`
    28  	vboxcommon.FloppyConfig         `mapstructure:",squash"`
    29  	vboxcommon.OutputConfig         `mapstructure:",squash"`
    30  	vboxcommon.RunConfig            `mapstructure:",squash"`
    31  	vboxcommon.ShutdownConfig       `mapstructure:",squash"`
    32  	vboxcommon.SSHConfig            `mapstructure:",squash"`
    33  	vboxcommon.VBoxManageConfig     `mapstructure:",squash"`
    34  	vboxcommon.VBoxManagePostConfig `mapstructure:",squash"`
    35  	vboxcommon.VBoxVersionConfig    `mapstructure:",squash"`
    36  
    37  	BootCommand          []string `mapstructure:"boot_command"`
    38  	DiskSize             uint     `mapstructure:"disk_size"`
    39  	GuestAdditionsMode   string   `mapstructure:"guest_additions_mode"`
    40  	GuestAdditionsPath   string   `mapstructure:"guest_additions_path"`
    41  	GuestAdditionsURL    string   `mapstructure:"guest_additions_url"`
    42  	GuestAdditionsSHA256 string   `mapstructure:"guest_additions_sha256"`
    43  	GuestOSType          string   `mapstructure:"guest_os_type"`
    44  	HardDriveInterface   string   `mapstructure:"hard_drive_interface"`
    45  	ISOChecksum          string   `mapstructure:"iso_checksum"`
    46  	ISOChecksumType      string   `mapstructure:"iso_checksum_type"`
    47  	ISOInterface         string   `mapstructure:"iso_interface"`
    48  	ISOUrls              []string `mapstructure:"iso_urls"`
    49  	VMName               string   `mapstructure:"vm_name"`
    50  
    51  	RawSingleISOUrl string `mapstructure:"iso_url"`
    52  
    53  	tpl *packer.ConfigTemplate
    54  }
    55  
    56  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    57  	md, err := common.DecodeConfig(&b.config, raws...)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	b.config.tpl, err = packer.NewConfigTemplate()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	b.config.tpl.UserVars = b.config.PackerUserVars
    67  
    68  	// Accumulate any errors and warnings
    69  	errs := common.CheckUnusedConfig(md)
    70  	errs = packer.MultiErrorAppend(errs, b.config.ExportConfig.Prepare(b.config.tpl)...)
    71  	errs = packer.MultiErrorAppend(errs, b.config.ExportOpts.Prepare(b.config.tpl)...)
    72  	errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(b.config.tpl)...)
    73  	errs = packer.MultiErrorAppend(
    74  		errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...)
    75  	errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
    76  	errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...)
    77  	errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...)
    78  	errs = packer.MultiErrorAppend(errs, b.config.VBoxManageConfig.Prepare(b.config.tpl)...)
    79  	errs = packer.MultiErrorAppend(errs, b.config.VBoxManagePostConfig.Prepare(b.config.tpl)...)
    80  	errs = packer.MultiErrorAppend(errs, b.config.VBoxVersionConfig.Prepare(b.config.tpl)...)
    81  	warnings := make([]string, 0)
    82  
    83  	if b.config.DiskSize == 0 {
    84  		b.config.DiskSize = 40000
    85  	}
    86  
    87  	if b.config.GuestAdditionsMode == "" {
    88  		b.config.GuestAdditionsMode = "upload"
    89  	}
    90  
    91  	if b.config.GuestAdditionsPath == "" {
    92  		b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso"
    93  	}
    94  
    95  	if b.config.HardDriveInterface == "" {
    96  		b.config.HardDriveInterface = "ide"
    97  	}
    98  
    99  	if b.config.GuestOSType == "" {
   100  		b.config.GuestOSType = "Other"
   101  	}
   102  
   103  	if b.config.ISOInterface == "" {
   104  		b.config.ISOInterface = "ide"
   105  	}
   106  
   107  	if b.config.VMName == "" {
   108  		b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName)
   109  	}
   110  
   111  	// Errors
   112  	templates := map[string]*string{
   113  		"guest_additions_mode":   &b.config.GuestAdditionsMode,
   114  		"guest_additions_sha256": &b.config.GuestAdditionsSHA256,
   115  		"guest_os_type":          &b.config.GuestOSType,
   116  		"hard_drive_interface":   &b.config.HardDriveInterface,
   117  		"iso_checksum":           &b.config.ISOChecksum,
   118  		"iso_checksum_type":      &b.config.ISOChecksumType,
   119  		"iso_interface":          &b.config.ISOInterface,
   120  		"iso_url":                &b.config.RawSingleISOUrl,
   121  		"vm_name":                &b.config.VMName,
   122  	}
   123  
   124  	for n, ptr := range templates {
   125  		var err error
   126  		*ptr, err = b.config.tpl.Process(*ptr, nil)
   127  		if err != nil {
   128  			errs = packer.MultiErrorAppend(
   129  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   130  		}
   131  	}
   132  
   133  	for i, url := range b.config.ISOUrls {
   134  		var err error
   135  		b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
   136  		if err != nil {
   137  			errs = packer.MultiErrorAppend(
   138  				errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
   139  		}
   140  	}
   141  
   142  	validates := map[string]*string{
   143  		"guest_additions_path": &b.config.GuestAdditionsPath,
   144  		"guest_additions_url":  &b.config.GuestAdditionsURL,
   145  	}
   146  
   147  	for n, ptr := range validates {
   148  		if err := b.config.tpl.Validate(*ptr); err != nil {
   149  			errs = packer.MultiErrorAppend(
   150  				errs, fmt.Errorf("Error parsing %s: %s", n, err))
   151  		}
   152  	}
   153  
   154  	for i, command := range b.config.BootCommand {
   155  		if err := b.config.tpl.Validate(command); err != nil {
   156  			errs = packer.MultiErrorAppend(errs,
   157  				fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
   158  		}
   159  	}
   160  
   161  	if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" {
   162  		errs = packer.MultiErrorAppend(
   163  			errs, errors.New("hard_drive_interface can only be ide or sata"))
   164  	}
   165  
   166  	if b.config.ISOChecksumType == "" {
   167  		errs = packer.MultiErrorAppend(
   168  			errs, errors.New("The iso_checksum_type must be specified."))
   169  	} else {
   170  		b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
   171  		if b.config.ISOChecksumType != "none" {
   172  			if b.config.ISOChecksum == "" {
   173  				errs = packer.MultiErrorAppend(
   174  					errs, errors.New("Due to large file sizes, an iso_checksum is required"))
   175  			} else {
   176  				b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
   177  			}
   178  
   179  			if h := common.HashForType(b.config.ISOChecksumType); h == nil {
   180  				errs = packer.MultiErrorAppend(
   181  					errs,
   182  					fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
   183  			}
   184  		}
   185  	}
   186  
   187  	if b.config.ISOInterface != "ide" && b.config.ISOInterface != "sata" {
   188  		errs = packer.MultiErrorAppend(
   189  			errs, errors.New("iso_interface can only be ide or sata"))
   190  	}
   191  
   192  	if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
   193  		errs = packer.MultiErrorAppend(
   194  			errs, errors.New("One of iso_url or iso_urls must be specified."))
   195  	} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
   196  		errs = packer.MultiErrorAppend(
   197  			errs, errors.New("Only one of iso_url or iso_urls may be specified."))
   198  	} else if b.config.RawSingleISOUrl != "" {
   199  		b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
   200  	}
   201  
   202  	for i, url := range b.config.ISOUrls {
   203  		b.config.ISOUrls[i], err = common.DownloadableURL(url)
   204  		if err != nil {
   205  			errs = packer.MultiErrorAppend(
   206  				errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
   207  		}
   208  	}
   209  
   210  	validMode := false
   211  	validModes := []string{
   212  		vboxcommon.GuestAdditionsModeDisable,
   213  		vboxcommon.GuestAdditionsModeAttach,
   214  		vboxcommon.GuestAdditionsModeUpload,
   215  	}
   216  
   217  	for _, mode := range validModes {
   218  		if b.config.GuestAdditionsMode == mode {
   219  			validMode = true
   220  			break
   221  		}
   222  	}
   223  
   224  	if !validMode {
   225  		errs = packer.MultiErrorAppend(errs,
   226  			fmt.Errorf("guest_additions_mode is invalid. Must be one of: %v", validModes))
   227  	}
   228  
   229  	if b.config.GuestAdditionsSHA256 != "" {
   230  		b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256)
   231  	}
   232  
   233  	// Warnings
   234  	if b.config.ISOChecksumType == "none" {
   235  		warnings = append(warnings,
   236  			"A checksum type of 'none' was specified. Since ISO files are so big,\n"+
   237  				"a checksum is highly recommended.")
   238  	}
   239  
   240  	if b.config.ShutdownCommand == "" {
   241  		warnings = append(warnings,
   242  			"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
   243  				"will forcibly halt the virtual machine, which may result in data loss.")
   244  	}
   245  
   246  	if errs != nil && len(errs.Errors) > 0 {
   247  		return warnings, errs
   248  	}
   249  
   250  	return warnings, nil
   251  }
   252  
   253  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   254  	// Seed the random number generator
   255  	rand.Seed(time.Now().UTC().UnixNano())
   256  
   257  	// Create the driver that we'll use to communicate with VirtualBox
   258  	driver, err := vboxcommon.NewDriver()
   259  	if err != nil {
   260  		return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
   261  	}
   262  
   263  	steps := []multistep.Step{
   264  		&vboxcommon.StepDownloadGuestAdditions{
   265  			GuestAdditionsMode:   b.config.GuestAdditionsMode,
   266  			GuestAdditionsURL:    b.config.GuestAdditionsURL,
   267  			GuestAdditionsSHA256: b.config.GuestAdditionsSHA256,
   268  			Tpl:                  b.config.tpl,
   269  		},
   270  		&common.StepDownload{
   271  			Checksum:     b.config.ISOChecksum,
   272  			ChecksumType: b.config.ISOChecksumType,
   273  			Description:  "ISO",
   274  			ResultKey:    "iso_path",
   275  			Url:          b.config.ISOUrls,
   276  		},
   277  		&vboxcommon.StepOutputDir{
   278  			Force: b.config.PackerForce,
   279  			Path:  b.config.OutputDir,
   280  		},
   281  		&common.StepCreateFloppy{
   282  			Files: b.config.FloppyFiles,
   283  		},
   284  		&vboxcommon.StepHTTPServer{
   285  			HTTPDir:     b.config.HTTPDir,
   286  			HTTPPortMin: b.config.HTTPPortMin,
   287  			HTTPPortMax: b.config.HTTPPortMax,
   288  		},
   289  		new(vboxcommon.StepSuppressMessages),
   290  		new(stepCreateVM),
   291  		new(stepCreateDisk),
   292  		new(stepAttachISO),
   293  		&vboxcommon.StepAttachGuestAdditions{
   294  			GuestAdditionsMode: b.config.GuestAdditionsMode,
   295  		},
   296  		new(vboxcommon.StepAttachFloppy),
   297  		&vboxcommon.StepForwardSSH{
   298  			GuestPort:   b.config.SSHPort,
   299  			HostPortMin: b.config.SSHHostPortMin,
   300  			HostPortMax: b.config.SSHHostPortMax,
   301  		},
   302  		&vboxcommon.StepVBoxManage{
   303  			Commands: b.config.VBoxManage,
   304  			Tpl:      b.config.tpl,
   305  		},
   306  		&vboxcommon.StepRun{
   307  			BootWait: b.config.BootWait,
   308  			Headless: b.config.Headless,
   309  		},
   310  		&vboxcommon.StepTypeBootCommand{
   311  			BootCommand: b.config.BootCommand,
   312  			VMName:      b.config.VMName,
   313  			Tpl:         b.config.tpl,
   314  		},
   315  		&common.StepConnectSSH{
   316  			SSHAddress:     vboxcommon.SSHAddress,
   317  			SSHConfig:      vboxcommon.SSHConfigFunc(b.config.SSHConfig),
   318  			SSHWaitTimeout: b.config.SSHWaitTimeout,
   319  		},
   320  		&vboxcommon.StepUploadVersion{
   321  			Path: b.config.VBoxVersionFile,
   322  		},
   323  		&vboxcommon.StepUploadGuestAdditions{
   324  			GuestAdditionsMode: b.config.GuestAdditionsMode,
   325  			GuestAdditionsPath: b.config.GuestAdditionsPath,
   326  			Tpl:                b.config.tpl,
   327  		},
   328  		new(common.StepProvision),
   329  		&vboxcommon.StepShutdown{
   330  			Command: b.config.ShutdownCommand,
   331  			Timeout: b.config.ShutdownTimeout,
   332  		},
   333  		new(vboxcommon.StepRemoveDevices),
   334  		&vboxcommon.StepVBoxManage{
   335  			Commands: b.config.VBoxManagePost,
   336  			Tpl:      b.config.tpl,
   337  		},
   338  		&vboxcommon.StepExport{
   339  			Format:     b.config.Format,
   340  			OutputDir:  b.config.OutputDir,
   341  			ExportOpts: b.config.ExportOpts.ExportOpts,
   342  		},
   343  	}
   344  
   345  	// Setup the state bag
   346  	state := new(multistep.BasicStateBag)
   347  	state.Put("cache", cache)
   348  	state.Put("config", &b.config)
   349  	state.Put("driver", driver)
   350  	state.Put("hook", hook)
   351  	state.Put("ui", ui)
   352  
   353  	// Run
   354  	if b.config.PackerDebug {
   355  		b.runner = &multistep.DebugRunner{
   356  			Steps:   steps,
   357  			PauseFn: common.MultistepDebugFn(ui),
   358  		}
   359  	} else {
   360  		b.runner = &multistep.BasicRunner{Steps: steps}
   361  	}
   362  
   363  	b.runner.Run(state)
   364  
   365  	// If there was an error, return that
   366  	if rawErr, ok := state.GetOk("error"); ok {
   367  		return nil, rawErr.(error)
   368  	}
   369  
   370  	// If we were interrupted or cancelled, then just exit.
   371  	if _, ok := state.GetOk(multistep.StateCancelled); ok {
   372  		return nil, errors.New("Build was cancelled.")
   373  	}
   374  
   375  	if _, ok := state.GetOk(multistep.StateHalted); ok {
   376  		return nil, errors.New("Build was halted.")
   377  	}
   378  
   379  	return vboxcommon.NewArtifact(b.config.OutputDir)
   380  }
   381  
   382  func (b *Builder) Cancel() {
   383  	if b.runner != nil {
   384  		log.Println("Cancelling the step runner...")
   385  		b.runner.Cancel()
   386  	}
   387  }