github.com/sneal/packer@v0.5.2/builder/virtualbox/iso/builder.go (about)

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