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