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

     1  package iso
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/mitchellh/multistep"
     7  	vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
     8  	"github.com/mitchellh/packer/common"
     9  	"github.com/mitchellh/packer/packer"
    10  	"io/ioutil"
    11  	"log"
    12  	"math/rand"
    13  	"os"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  const BuilderIdESX = "mitchellh.vmware-esx"
    19  
    20  type Builder struct {
    21  	config config
    22  	runner multistep.Runner
    23  }
    24  
    25  type config struct {
    26  	common.PackerConfig      `mapstructure:",squash"`
    27  	vmwcommon.DriverConfig   `mapstructure:",squash"`
    28  	vmwcommon.OutputConfig   `mapstructure:",squash"`
    29  	vmwcommon.RunConfig      `mapstructure:",squash"`
    30  	vmwcommon.ShutdownConfig `mapstructure:",squash"`
    31  	vmwcommon.SSHConfig      `mapstructure:",squash"`
    32  	vmwcommon.ToolsConfig    `mapstructure:",squash"`
    33  	vmwcommon.VMXConfig      `mapstructure:",squash"`
    34  
    35  	DiskName        string   `mapstructure:"vmdk_name"`
    36  	DiskSize        uint     `mapstructure:"disk_size"`
    37  	DiskTypeId      string   `mapstructure:"disk_type_id"`
    38  	FloppyFiles     []string `mapstructure:"floppy_files"`
    39  	GuestOSType     string   `mapstructure:"guest_os_type"`
    40  	ISOChecksum     string   `mapstructure:"iso_checksum"`
    41  	ISOChecksumType string   `mapstructure:"iso_checksum_type"`
    42  	ISOUrls         []string `mapstructure:"iso_urls"`
    43  	VMName          string   `mapstructure:"vm_name"`
    44  	BootCommand     []string `mapstructure:"boot_command"`
    45  	SkipCompaction  bool     `mapstructure:"skip_compaction"`
    46  	VMXTemplatePath string   `mapstructure:"vmx_template_path"`
    47  
    48  	RemoteType      string `mapstructure:"remote_type"`
    49  	RemoteDatastore string `mapstructure:"remote_datastore"`
    50  	RemoteHost      string `mapstructure:"remote_host"`
    51  	RemotePort      uint   `mapstructure:"remote_port"`
    52  	RemoteUser      string `mapstructure:"remote_username"`
    53  	RemotePassword  string `mapstructure:"remote_password"`
    54  
    55  	RawSingleISOUrl string `mapstructure:"iso_url"`
    56  
    57  	tpl *packer.ConfigTemplate
    58  }
    59  
    60  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    61  	md, err := common.DecodeConfig(&b.config, raws...)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	b.config.tpl, err = packer.NewConfigTemplate()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	b.config.tpl.UserVars = b.config.PackerUserVars
    71  
    72  	// Accumulate any errors
    73  	errs := common.CheckUnusedConfig(md)
    74  	errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(b.config.tpl)...)
    75  	errs = packer.MultiErrorAppend(errs,
    76  		b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...)
    77  	errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
    78  	errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...)
    79  	errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...)
    80  	errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(b.config.tpl)...)
    81  	errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(b.config.tpl)...)
    82  	warnings := make([]string, 0)
    83  
    84  	if b.config.DiskName == "" {
    85  		b.config.DiskName = "disk"
    86  	}
    87  
    88  	if b.config.DiskSize == 0 {
    89  		b.config.DiskSize = 40000
    90  	}
    91  
    92  	if b.config.DiskTypeId == "" {
    93  		// Default is growable virtual disk split in 2GB files.
    94  		b.config.DiskTypeId = "1"
    95  
    96  		if b.config.RemoteType == "esx5" {
    97  			b.config.DiskTypeId = "zeroedthick"
    98  		}
    99  	}
   100  
   101  	if b.config.FloppyFiles == nil {
   102  		b.config.FloppyFiles = make([]string, 0)
   103  	}
   104  
   105  	if b.config.GuestOSType == "" {
   106  		b.config.GuestOSType = "other"
   107  	}
   108  
   109  	if b.config.VMName == "" {
   110  		b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
   111  	}
   112  
   113  	if b.config.RemoteUser == "" {
   114  		b.config.RemoteUser = "root"
   115  	}
   116  
   117  	if b.config.RemoteDatastore == "" {
   118  		b.config.RemoteDatastore = "datastore1"
   119  	}
   120  
   121  	if b.config.RemotePort == 0 {
   122  		b.config.RemotePort = 22
   123  	}
   124  
   125  	// Errors
   126  	templates := map[string]*string{
   127  		"disk_name":         &b.config.DiskName,
   128  		"guest_os_type":     &b.config.GuestOSType,
   129  		"iso_checksum":      &b.config.ISOChecksum,
   130  		"iso_checksum_type": &b.config.ISOChecksumType,
   131  		"iso_url":           &b.config.RawSingleISOUrl,
   132  		"vm_name":           &b.config.VMName,
   133  		"vmx_template_path": &b.config.VMXTemplatePath,
   134  		"remote_type":       &b.config.RemoteType,
   135  		"remote_host":       &b.config.RemoteHost,
   136  		"remote_datastore":  &b.config.RemoteDatastore,
   137  		"remote_user":       &b.config.RemoteUser,
   138  		"remote_password":   &b.config.RemotePassword,
   139  	}
   140  
   141  	for n, ptr := range templates {
   142  		var err error
   143  		*ptr, err = b.config.tpl.Process(*ptr, nil)
   144  		if err != nil {
   145  			errs = packer.MultiErrorAppend(
   146  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   147  		}
   148  	}
   149  
   150  	for i, url := range b.config.ISOUrls {
   151  		var err error
   152  		b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
   153  		if err != nil {
   154  			errs = packer.MultiErrorAppend(
   155  				errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
   156  		}
   157  	}
   158  
   159  	for i, command := range b.config.BootCommand {
   160  		if err := b.config.tpl.Validate(command); err != nil {
   161  			errs = packer.MultiErrorAppend(errs,
   162  				fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
   163  		}
   164  	}
   165  
   166  	for i, file := range b.config.FloppyFiles {
   167  		var err error
   168  		b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
   169  		if err != nil {
   170  			errs = packer.MultiErrorAppend(errs,
   171  				fmt.Errorf("Error processing floppy_files[%d]: %s",
   172  					i, err))
   173  		}
   174  	}
   175  
   176  	if b.config.ISOChecksumType == "" {
   177  		errs = packer.MultiErrorAppend(
   178  			errs, errors.New("The iso_checksum_type must be specified."))
   179  	} else {
   180  		b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
   181  		if b.config.ISOChecksumType != "none" {
   182  			if b.config.ISOChecksum == "" {
   183  				errs = packer.MultiErrorAppend(
   184  					errs, errors.New("Due to large file sizes, an iso_checksum is required"))
   185  			} else {
   186  				b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
   187  			}
   188  
   189  			if h := common.HashForType(b.config.ISOChecksumType); h == nil {
   190  				errs = packer.MultiErrorAppend(
   191  					errs,
   192  					fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
   193  			}
   194  		}
   195  	}
   196  
   197  	if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
   198  		errs = packer.MultiErrorAppend(
   199  			errs, errors.New("One of iso_url or iso_urls must be specified."))
   200  	} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
   201  		errs = packer.MultiErrorAppend(
   202  			errs, errors.New("Only one of iso_url or iso_urls may be specified."))
   203  	} else if b.config.RawSingleISOUrl != "" {
   204  		b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
   205  	}
   206  
   207  	for i, url := range b.config.ISOUrls {
   208  		b.config.ISOUrls[i], err = common.DownloadableURL(url)
   209  		if err != nil {
   210  			errs = packer.MultiErrorAppend(
   211  				errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
   212  		}
   213  	}
   214  
   215  	if b.config.VMXTemplatePath != "" {
   216  		if err := b.validateVMXTemplatePath(); err != nil {
   217  			errs = packer.MultiErrorAppend(
   218  				errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
   219  		}
   220  
   221  	}
   222  
   223  	// Remote configuration validation
   224  	if b.config.RemoteType != "" {
   225  		if b.config.RemoteHost == "" {
   226  			errs = packer.MultiErrorAppend(errs,
   227  				fmt.Errorf("remote_host must be specified"))
   228  		}
   229  	}
   230  
   231  	// Warnings
   232  	if b.config.ISOChecksumType == "none" {
   233  		warnings = append(warnings,
   234  			"A checksum type of 'none' was specified. Since ISO files are so big,\n"+
   235  				"a checksum is highly recommended.")
   236  	}
   237  
   238  	if b.config.ShutdownCommand == "" {
   239  		warnings = append(warnings,
   240  			"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
   241  				"will forcibly halt the virtual machine, which may result in data loss.")
   242  	}
   243  
   244  	if errs != nil && len(errs.Errors) > 0 {
   245  		return warnings, errs
   246  	}
   247  
   248  	return warnings, nil
   249  }
   250  
   251  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   252  	driver, err := NewDriver(&b.config)
   253  	if err != nil {
   254  		return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
   255  	}
   256  
   257  	// Determine the output dir implementation
   258  	var dir OutputDir
   259  	switch d := driver.(type) {
   260  	case OutputDir:
   261  		dir = d
   262  	default:
   263  		dir = new(vmwcommon.LocalOutputDir)
   264  	}
   265  	dir.SetOutputDir(b.config.OutputDir)
   266  
   267  	// Setup the state bag
   268  	state := new(multistep.BasicStateBag)
   269  	state.Put("cache", cache)
   270  	state.Put("config", &b.config)
   271  	state.Put("dir", dir)
   272  	state.Put("driver", driver)
   273  	state.Put("hook", hook)
   274  	state.Put("ui", ui)
   275  
   276  	// Seed the random number generator
   277  	rand.Seed(time.Now().UTC().UnixNano())
   278  
   279  	steps := []multistep.Step{
   280  		&vmwcommon.StepPrepareTools{
   281  			RemoteType:        b.config.RemoteType,
   282  			ToolsUploadFlavor: b.config.ToolsUploadFlavor,
   283  		},
   284  		&common.StepDownload{
   285  			Checksum:     b.config.ISOChecksum,
   286  			ChecksumType: b.config.ISOChecksumType,
   287  			Description:  "ISO",
   288  			ResultKey:    "iso_path",
   289  			Url:          b.config.ISOUrls,
   290  		},
   291  		&vmwcommon.StepOutputDir{
   292  			Force: b.config.PackerForce,
   293  		},
   294  		&common.StepCreateFloppy{
   295  			Files: b.config.FloppyFiles,
   296  		},
   297  		&stepRemoteUpload{
   298  			Key:     "floppy_path",
   299  			Message: "Uploading Floppy to remote machine...",
   300  		},
   301  		&stepRemoteUpload{
   302  			Key:     "iso_path",
   303  			Message: "Uploading ISO to remote machine...",
   304  		},
   305  		&stepCreateDisk{},
   306  		&stepCreateVMX{},
   307  		&vmwcommon.StepConfigureVMX{
   308  			CustomData: b.config.VMXData,
   309  		},
   310  		&vmwcommon.StepSuppressMessages{},
   311  		&vmwcommon.StepHTTPServer{
   312  			HTTPDir:     b.config.HTTPDir,
   313  			HTTPPortMin: b.config.HTTPPortMin,
   314  			HTTPPortMax: b.config.HTTPPortMax,
   315  		},
   316  		&vmwcommon.StepConfigureVNC{
   317  			VNCPortMin: b.config.VNCPortMin,
   318  			VNCPortMax: b.config.VNCPortMax,
   319  		},
   320  		&StepRegister{},
   321  		&vmwcommon.StepRun{
   322  			BootWait:           b.config.BootWait,
   323  			DurationBeforeStop: 5 * time.Second,
   324  			Headless:           b.config.Headless,
   325  		},
   326  		&vmwcommon.StepTypeBootCommand{
   327  			BootCommand: b.config.BootCommand,
   328  			VMName:      b.config.VMName,
   329  			Tpl:         b.config.tpl,
   330  		},
   331  		&common.StepConnectSSH{
   332  			SSHAddress:     driver.SSHAddress,
   333  			SSHConfig:      vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
   334  			SSHWaitTimeout: b.config.SSHWaitTimeout,
   335  			NoPty:          b.config.SSHSkipRequestPty,
   336  		},
   337  		&vmwcommon.StepUploadTools{
   338  			RemoteType:        b.config.RemoteType,
   339  			ToolsUploadFlavor: b.config.ToolsUploadFlavor,
   340  			ToolsUploadPath:   b.config.ToolsUploadPath,
   341  			Tpl:               b.config.tpl,
   342  		},
   343  		&common.StepProvision{},
   344  		&vmwcommon.StepShutdown{
   345  			Command: b.config.ShutdownCommand,
   346  			Timeout: b.config.ShutdownTimeout,
   347  		},
   348  		&vmwcommon.StepCleanFiles{},
   349  		&vmwcommon.StepConfigureVMX{
   350  			CustomData: b.config.VMXDataPost,
   351  			SkipFloppy: true,
   352  		},
   353  		&vmwcommon.StepCleanVMX{},
   354  		&vmwcommon.StepCompactDisk{
   355  			Skip: b.config.SkipCompaction,
   356  		},
   357  	}
   358  
   359  	// Run!
   360  	if b.config.PackerDebug {
   361  		b.runner = &multistep.DebugRunner{
   362  			Steps:   steps,
   363  			PauseFn: common.MultistepDebugFn(ui),
   364  		}
   365  	} else {
   366  		b.runner = &multistep.BasicRunner{Steps: steps}
   367  	}
   368  
   369  	b.runner.Run(state)
   370  
   371  	// If there was an error, return that
   372  	if rawErr, ok := state.GetOk("error"); ok {
   373  		return nil, rawErr.(error)
   374  	}
   375  
   376  	// If we were interrupted or cancelled, then just exit.
   377  	if _, ok := state.GetOk(multistep.StateCancelled); ok {
   378  		return nil, errors.New("Build was cancelled.")
   379  	}
   380  
   381  	if _, ok := state.GetOk(multistep.StateHalted); ok {
   382  		return nil, errors.New("Build was halted.")
   383  	}
   384  
   385  	// Compile the artifact list
   386  	files, err := state.Get("dir").(OutputDir).ListFiles()
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	// Set the proper builder ID
   392  	builderId := vmwcommon.BuilderId
   393  	if b.config.RemoteType != "" {
   394  		builderId = BuilderIdESX
   395  	}
   396  
   397  	return &Artifact{
   398  		builderId: builderId,
   399  		dir:       dir,
   400  		f:         files,
   401  	}, nil
   402  }
   403  
   404  func (b *Builder) Cancel() {
   405  	if b.runner != nil {
   406  		log.Println("Cancelling the step runner...")
   407  		b.runner.Cancel()
   408  	}
   409  }
   410  
   411  func (b *Builder) validateVMXTemplatePath() error {
   412  	f, err := os.Open(b.config.VMXTemplatePath)
   413  	if err != nil {
   414  		return err
   415  	}
   416  	defer f.Close()
   417  
   418  	data, err := ioutil.ReadAll(f)
   419  	if err != nil {
   420  		return err
   421  	}
   422  
   423  	return b.config.tpl.Validate(string(data))
   424  }