github.com/ttysteale/packer@v0.8.2-0.20150708160520-e5f8ea386ed8/builder/vmware/iso/builder.go (about)

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