github.com/tonnydourado/packer@v0.6.1-0.20140701134019-5d0cd9676a37/builder/parallels/iso/builder.go (about)

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