github.com/phobos182/packer@v0.2.3-0.20130819023704-c84d2aeffc68/builder/vmware/builder.go (about)

     1  package vmware
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/mitchellh/multistep"
     7  	"github.com/mitchellh/packer/common"
     8  	"github.com/mitchellh/packer/packer"
     9  	"log"
    10  	"math/rand"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"text/template"
    15  	"time"
    16  )
    17  
    18  const BuilderId = "mitchellh.vmware"
    19  
    20  type Builder struct {
    21  	config config
    22  	runner multistep.Runner
    23  }
    24  
    25  type config struct {
    26  	common.PackerConfig `mapstructure:",squash"`
    27  
    28  	DiskName          string            `mapstructure:"vmdk_name"`
    29  	DiskSize          uint              `mapstructure:"disk_size"`
    30  	FloppyFiles       []string          `mapstructure:"floppy_files"`
    31  	GuestOSType       string            `mapstructure:"guest_os_type"`
    32  	ISOChecksum       string            `mapstructure:"iso_checksum"`
    33  	ISOChecksumType   string            `mapstructure:"iso_checksum_type"`
    34  	ISOUrls           []string          `mapstructure:"iso_urls"`
    35  	VMName            string            `mapstructure:"vm_name"`
    36  	OutputDir         string            `mapstructure:"output_directory"`
    37  	Headless          bool              `mapstructure:"headless"`
    38  	HTTPDir           string            `mapstructure:"http_directory"`
    39  	HTTPPortMin       uint              `mapstructure:"http_port_min"`
    40  	HTTPPortMax       uint              `mapstructure:"http_port_max"`
    41  	BootCommand       []string          `mapstructure:"boot_command"`
    42  	SkipCompaction    bool              `mapstructure:"skip_compaction"`
    43  	ShutdownCommand   string            `mapstructure:"shutdown_command"`
    44  	SSHUser           string            `mapstructure:"ssh_username"`
    45  	SSHPassword       string            `mapstructure:"ssh_password"`
    46  	SSHPort           uint              `mapstructure:"ssh_port"`
    47  	ToolsUploadFlavor string            `mapstructure:"tools_upload_flavor"`
    48  	ToolsUploadPath   string            `mapstructure:"tools_upload_path"`
    49  	VMXData           map[string]string `mapstructure:"vmx_data"`
    50  	VNCPortMin        uint              `mapstructure:"vnc_port_min"`
    51  	VNCPortMax        uint              `mapstructure:"vnc_port_max"`
    52  
    53  	RawBootWait        string `mapstructure:"boot_wait"`
    54  	RawSingleISOUrl    string `mapstructure:"iso_url"`
    55  	RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
    56  	RawSSHWaitTimeout  string `mapstructure:"ssh_wait_timeout"`
    57  
    58  	bootWait        time.Duration ``
    59  	shutdownTimeout time.Duration ``
    60  	sshWaitTimeout  time.Duration ``
    61  	tpl             *packer.ConfigTemplate
    62  }
    63  
    64  func (b *Builder) Prepare(raws ...interface{}) error {
    65  	md, err := common.DecodeConfig(&b.config, raws...)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	b.config.tpl, err = packer.NewConfigTemplate()
    71  	if err != nil {
    72  		return err
    73  	}
    74  	b.config.tpl.UserVars = b.config.PackerUserVars
    75  
    76  	// Accumulate any errors
    77  	errs := common.CheckUnusedConfig(md)
    78  
    79  	if b.config.DiskName == "" {
    80  		b.config.DiskName = "disk"
    81  	}
    82  
    83  	if b.config.DiskSize == 0 {
    84  		b.config.DiskSize = 40000
    85  	}
    86  
    87  	if b.config.FloppyFiles == nil {
    88  		b.config.FloppyFiles = make([]string, 0)
    89  	}
    90  
    91  	if b.config.GuestOSType == "" {
    92  		b.config.GuestOSType = "other"
    93  	}
    94  
    95  	if b.config.VMName == "" {
    96  		b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
    97  	}
    98  
    99  	if b.config.HTTPPortMin == 0 {
   100  		b.config.HTTPPortMin = 8000
   101  	}
   102  
   103  	if b.config.HTTPPortMax == 0 {
   104  		b.config.HTTPPortMax = 9000
   105  	}
   106  
   107  	if b.config.RawBootWait == "" {
   108  		b.config.RawBootWait = "10s"
   109  	}
   110  
   111  	if b.config.VNCPortMin == 0 {
   112  		b.config.VNCPortMin = 5900
   113  	}
   114  
   115  	if b.config.VNCPortMax == 0 {
   116  		b.config.VNCPortMax = 6000
   117  	}
   118  
   119  	if b.config.OutputDir == "" {
   120  		b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
   121  	}
   122  
   123  	if b.config.SSHPort == 0 {
   124  		b.config.SSHPort = 22
   125  	}
   126  
   127  	if b.config.ToolsUploadPath == "" {
   128  		b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
   129  	}
   130  
   131  	// Errors
   132  	templates := map[string]*string{
   133  		"disk_name":           &b.config.DiskName,
   134  		"guest_os_type":       &b.config.GuestOSType,
   135  		"http_directory":      &b.config.HTTPDir,
   136  		"iso_checksum":        &b.config.ISOChecksum,
   137  		"iso_checksum_type":   &b.config.ISOChecksumType,
   138  		"iso_url":             &b.config.RawSingleISOUrl,
   139  		"output_directory":    &b.config.OutputDir,
   140  		"shutdown_command":    &b.config.ShutdownCommand,
   141  		"ssh_password":        &b.config.SSHPassword,
   142  		"ssh_username":        &b.config.SSHUser,
   143  		"tools_upload_flavor": &b.config.ToolsUploadFlavor,
   144  		"vm_name":             &b.config.VMName,
   145  		"boot_wait":           &b.config.RawBootWait,
   146  		"shutdown_timeout":    &b.config.RawShutdownTimeout,
   147  		"ssh_wait_timeout":    &b.config.RawSSHWaitTimeout,
   148  	}
   149  
   150  	for n, ptr := range templates {
   151  		var err error
   152  		*ptr, err = b.config.tpl.Process(*ptr, nil)
   153  		if err != nil {
   154  			errs = packer.MultiErrorAppend(
   155  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   156  		}
   157  	}
   158  
   159  	for i, url := range b.config.ISOUrls {
   160  		var err error
   161  		b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil)
   162  		if err != nil {
   163  			errs = packer.MultiErrorAppend(
   164  				errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err))
   165  		}
   166  	}
   167  
   168  	for i, command := range b.config.BootCommand {
   169  		if err := b.config.tpl.Validate(command); err != nil {
   170  			errs = packer.MultiErrorAppend(errs,
   171  				fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
   172  		}
   173  	}
   174  
   175  	for i, file := range b.config.FloppyFiles {
   176  		var err error
   177  		b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
   178  		if err != nil {
   179  			errs = packer.MultiErrorAppend(errs,
   180  				fmt.Errorf("Error processing floppy_files[%d]: %s",
   181  					i, err))
   182  		}
   183  	}
   184  
   185  	newVMXData := make(map[string]string)
   186  	for k, v := range b.config.VMXData {
   187  		k, err = b.config.tpl.Process(k, nil)
   188  		if err != nil {
   189  			errs = packer.MultiErrorAppend(errs,
   190  				fmt.Errorf("Error processing VMX data key %s: %s", k, err))
   191  			continue
   192  		}
   193  
   194  		v, err = b.config.tpl.Process(v, nil)
   195  		if err != nil {
   196  			errs = packer.MultiErrorAppend(errs,
   197  				fmt.Errorf("Error processing VMX data value '%s': %s", v, err))
   198  			continue
   199  		}
   200  
   201  		newVMXData[k] = v
   202  	}
   203  
   204  	b.config.VMXData = newVMXData
   205  
   206  	if b.config.HTTPPortMin > b.config.HTTPPortMax {
   207  		errs = packer.MultiErrorAppend(
   208  			errs, errors.New("http_port_min must be less than http_port_max"))
   209  	}
   210  
   211  	if b.config.ISOChecksum == "" {
   212  		errs = packer.MultiErrorAppend(
   213  			errs, errors.New("Due to large file sizes, an iso_checksum is required"))
   214  	} else {
   215  		b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
   216  	}
   217  
   218  	if b.config.ISOChecksumType == "" {
   219  		errs = packer.MultiErrorAppend(
   220  			errs, errors.New("The iso_checksum_type must be specified."))
   221  	} else {
   222  		b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
   223  		if h := common.HashForType(b.config.ISOChecksumType); h == nil {
   224  			errs = packer.MultiErrorAppend(
   225  				errs,
   226  				fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
   227  		}
   228  	}
   229  
   230  	if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 {
   231  		errs = packer.MultiErrorAppend(
   232  			errs, errors.New("One of iso_url or iso_urls must be specified."))
   233  	} else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 {
   234  		errs = packer.MultiErrorAppend(
   235  			errs, errors.New("Only one of iso_url or iso_urls may be specified."))
   236  	} else if b.config.RawSingleISOUrl != "" {
   237  		b.config.ISOUrls = []string{b.config.RawSingleISOUrl}
   238  	}
   239  
   240  	for i, url := range b.config.ISOUrls {
   241  		b.config.ISOUrls[i], err = common.DownloadableURL(url)
   242  		if err != nil {
   243  			errs = packer.MultiErrorAppend(
   244  				errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
   245  		}
   246  	}
   247  
   248  	if !b.config.PackerForce {
   249  		if _, err := os.Stat(b.config.OutputDir); err == nil {
   250  			errs = packer.MultiErrorAppend(
   251  				errs,
   252  				fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
   253  		}
   254  	}
   255  
   256  	if b.config.SSHUser == "" {
   257  		errs = packer.MultiErrorAppend(
   258  			errs, errors.New("An ssh_username must be specified."))
   259  	}
   260  
   261  	if b.config.RawBootWait != "" {
   262  		b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait)
   263  		if err != nil {
   264  			errs = packer.MultiErrorAppend(
   265  				errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
   266  		}
   267  	}
   268  
   269  	if b.config.RawShutdownTimeout == "" {
   270  		b.config.RawShutdownTimeout = "5m"
   271  	}
   272  
   273  	b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
   274  	if err != nil {
   275  		errs = packer.MultiErrorAppend(
   276  			errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
   277  	}
   278  
   279  	if b.config.RawSSHWaitTimeout == "" {
   280  		b.config.RawSSHWaitTimeout = "20m"
   281  	}
   282  
   283  	b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
   284  	if err != nil {
   285  		errs = packer.MultiErrorAppend(
   286  			errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
   287  	}
   288  
   289  	if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil {
   290  		errs = packer.MultiErrorAppend(
   291  			errs, fmt.Errorf("tools_upload_path invalid: %s", err))
   292  	}
   293  
   294  	if b.config.VNCPortMin > b.config.VNCPortMax {
   295  		errs = packer.MultiErrorAppend(
   296  			errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
   297  	}
   298  
   299  	if errs != nil && len(errs.Errors) > 0 {
   300  		return errs
   301  	}
   302  
   303  	return nil
   304  }
   305  
   306  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   307  	// Initialize the driver that will handle our interaction with VMware
   308  	driver, err := NewDriver()
   309  	if err != nil {
   310  		return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
   311  	}
   312  
   313  	// Seed the random number generator
   314  	rand.Seed(time.Now().UTC().UnixNano())
   315  
   316  	steps := []multistep.Step{
   317  		&stepPrepareTools{},
   318  		&common.StepDownload{
   319  			Checksum:     b.config.ISOChecksum,
   320  			ChecksumType: b.config.ISOChecksumType,
   321  			Description:  "ISO",
   322  			ResultKey:    "iso_path",
   323  			Url:          b.config.ISOUrls,
   324  		},
   325  		&stepPrepareOutputDir{},
   326  		&common.StepCreateFloppy{
   327  			Files: b.config.FloppyFiles,
   328  		},
   329  		&stepCreateDisk{},
   330  		&stepCreateVMX{},
   331  		&stepHTTPServer{},
   332  		&stepConfigureVNC{},
   333  		&stepRun{},
   334  		&stepTypeBootCommand{},
   335  		&common.StepConnectSSH{
   336  			SSHAddress:     sshAddress,
   337  			SSHConfig:      sshConfig,
   338  			SSHWaitTimeout: b.config.sshWaitTimeout,
   339  		},
   340  		&stepUploadTools{},
   341  		&common.StepProvision{},
   342  		&stepShutdown{},
   343  		&stepCleanFiles{},
   344  		&stepCleanVMX{},
   345  		&stepCompactDisk{},
   346  	}
   347  
   348  	// Setup the state bag
   349  	state := make(map[string]interface{})
   350  	state["cache"] = cache
   351  	state["config"] = &b.config
   352  	state["driver"] = driver
   353  	state["hook"] = hook
   354  	state["ui"] = ui
   355  
   356  	// Run!
   357  	if b.config.PackerDebug {
   358  		b.runner = &multistep.DebugRunner{
   359  			Steps:   steps,
   360  			PauseFn: common.MultistepDebugFn(ui),
   361  		}
   362  	} else {
   363  		b.runner = &multistep.BasicRunner{Steps: steps}
   364  	}
   365  
   366  	b.runner.Run(state)
   367  
   368  	// If there was an error, return that
   369  	if rawErr, ok := state["error"]; ok {
   370  		return nil, rawErr.(error)
   371  	}
   372  
   373  	// If we were interrupted or cancelled, then just exit.
   374  	if _, ok := state[multistep.StateCancelled]; ok {
   375  		return nil, errors.New("Build was cancelled.")
   376  	}
   377  
   378  	if _, ok := state[multistep.StateHalted]; ok {
   379  		return nil, errors.New("Build was halted.")
   380  	}
   381  
   382  	// Compile the artifact list
   383  	files := make([]string, 0, 10)
   384  	visit := func(path string, info os.FileInfo, err error) error {
   385  		if err != nil {
   386  			return err
   387  		}
   388  
   389  		if !info.IsDir() {
   390  			files = append(files, path)
   391  		}
   392  
   393  		return nil
   394  	}
   395  
   396  	if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
   397  		return nil, err
   398  	}
   399  
   400  	return &Artifact{b.config.OutputDir, files}, nil
   401  }
   402  
   403  func (b *Builder) Cancel() {
   404  	if b.runner != nil {
   405  		log.Println("Cancelling the step runner...")
   406  		b.runner.Cancel()
   407  	}
   408  }