github.com/mitchellh/packer@v1.3.2/builder/vmware/iso/builder.go (about)

     1  package iso
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"strconv"
    10  	"time"
    11  
    12  	vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
    13  	"github.com/hashicorp/packer/common"
    14  	"github.com/hashicorp/packer/common/bootcommand"
    15  	"github.com/hashicorp/packer/helper/communicator"
    16  	"github.com/hashicorp/packer/helper/config"
    17  	"github.com/hashicorp/packer/helper/multistep"
    18  	"github.com/hashicorp/packer/packer"
    19  	"github.com/hashicorp/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  	common.HTTPConfig        `mapstructure:",squash"`
    32  	common.ISOConfig         `mapstructure:",squash"`
    33  	common.FloppyConfig      `mapstructure:",squash"`
    34  	bootcommand.VNCConfig    `mapstructure:",squash"`
    35  	vmwcommon.DriverConfig   `mapstructure:",squash"`
    36  	vmwcommon.OutputConfig   `mapstructure:",squash"`
    37  	vmwcommon.RunConfig      `mapstructure:",squash"`
    38  	vmwcommon.ShutdownConfig `mapstructure:",squash"`
    39  	vmwcommon.SSHConfig      `mapstructure:",squash"`
    40  	vmwcommon.ToolsConfig    `mapstructure:",squash"`
    41  	vmwcommon.VMXConfig      `mapstructure:",squash"`
    42  
    43  	// disk drives
    44  	AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
    45  	DiskAdapterType    string `mapstructure:"disk_adapter_type"`
    46  	DiskName           string `mapstructure:"vmdk_name"`
    47  	DiskSize           uint   `mapstructure:"disk_size"`
    48  	DiskTypeId         string `mapstructure:"disk_type_id"`
    49  	Format             string `mapstructure:"format"`
    50  
    51  	// cdrom drive
    52  	CdromAdapterType string `mapstructure:"cdrom_adapter_type"`
    53  
    54  	// platform information
    55  	GuestOSType string `mapstructure:"guest_os_type"`
    56  	Version     string `mapstructure:"version"`
    57  	VMName      string `mapstructure:"vm_name"`
    58  
    59  	// Network adapter and type
    60  	NetworkAdapterType string `mapstructure:"network_adapter_type"`
    61  	Network            string `mapstructure:"network"`
    62  
    63  	// device presence
    64  	Sound bool `mapstructure:"sound"`
    65  	USB   bool `mapstructure:"usb"`
    66  
    67  	// communication ports
    68  	Serial   string `mapstructure:"serial"`
    69  	Parallel string `mapstructure:"parallel"`
    70  
    71  	// booting a guest
    72  	KeepRegistered      bool     `mapstructure:"keep_registered"`
    73  	OVFToolOptions      []string `mapstructure:"ovftool_options"`
    74  	SkipCompaction      bool     `mapstructure:"skip_compaction"`
    75  	SkipExport          bool     `mapstructure:"skip_export"`
    76  	VMXDiskTemplatePath string   `mapstructure:"vmx_disk_template_path"`
    77  	VMXTemplatePath     string   `mapstructure:"vmx_template_path"`
    78  
    79  	// remote vsphere
    80  	RemoteType           string `mapstructure:"remote_type"`
    81  	RemoteDatastore      string `mapstructure:"remote_datastore"`
    82  	RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
    83  	RemoteCacheDirectory string `mapstructure:"remote_cache_directory"`
    84  	RemoteHost           string `mapstructure:"remote_host"`
    85  	RemotePort           uint   `mapstructure:"remote_port"`
    86  	RemoteUser           string `mapstructure:"remote_username"`
    87  	RemotePassword       string `mapstructure:"remote_password"`
    88  	RemotePrivateKey     string `mapstructure:"remote_private_key_file"`
    89  
    90  	CommConfig communicator.Config `mapstructure:",squash"`
    91  
    92  	ctx interpolate.Context
    93  }
    94  
    95  func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
    96  	err := config.Decode(&b.config, &config.DecodeOpts{
    97  		Interpolate:        true,
    98  		InterpolateContext: &b.config.ctx,
    99  		InterpolateFilter: &interpolate.RenderFilter{
   100  			Exclude: []string{
   101  				"boot_command",
   102  				"tools_upload_path",
   103  			},
   104  		},
   105  	}, raws...)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	// Accumulate any errors and warnings
   111  	var errs *packer.MultiError
   112  	warnings := make([]string, 0)
   113  
   114  	isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx)
   115  	warnings = append(warnings, isoWarnings...)
   116  	errs = packer.MultiErrorAppend(errs, isoErrs...)
   117  	errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...)
   118  	errs = packer.MultiErrorAppend(errs, b.config.DriverConfig.Prepare(&b.config.ctx)...)
   119  	errs = packer.MultiErrorAppend(errs,
   120  		b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...)
   121  	errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
   122  	errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
   123  	errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
   124  	errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...)
   125  	errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...)
   126  	errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
   127  	errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...)
   128  
   129  	if b.config.DiskName == "" {
   130  		b.config.DiskName = "disk"
   131  	}
   132  
   133  	if b.config.DiskSize == 0 {
   134  		b.config.DiskSize = 40000
   135  	}
   136  
   137  	if b.config.DiskAdapterType == "" {
   138  		// Default is lsilogic
   139  		b.config.DiskAdapterType = "lsilogic"
   140  	}
   141  
   142  	if !b.config.SkipCompaction {
   143  		if b.config.RemoteType == "esx5" {
   144  			if b.config.DiskTypeId == "" {
   145  				b.config.SkipCompaction = true
   146  			}
   147  		}
   148  	}
   149  
   150  	if b.config.DiskTypeId == "" {
   151  		// Default is growable virtual disk split in 2GB files.
   152  		b.config.DiskTypeId = "1"
   153  
   154  		if b.config.RemoteType == "esx5" {
   155  			b.config.DiskTypeId = "zeroedthick"
   156  		}
   157  	}
   158  
   159  	if b.config.RemoteType == "esx5" {
   160  		if b.config.DiskTypeId != "thin" && !b.config.SkipCompaction {
   161  			errs = packer.MultiErrorAppend(
   162  				errs, fmt.Errorf("skip_compaction must be 'true' for disk_type_id: %s", b.config.DiskTypeId))
   163  		}
   164  	}
   165  
   166  	if b.config.GuestOSType == "" {
   167  		b.config.GuestOSType = "other"
   168  	}
   169  
   170  	if b.config.VMName == "" {
   171  		b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
   172  	}
   173  
   174  	if b.config.Version == "" {
   175  		b.config.Version = "9"
   176  	}
   177  
   178  	if b.config.RemoteUser == "" {
   179  		b.config.RemoteUser = "root"
   180  	}
   181  
   182  	if b.config.RemoteDatastore == "" {
   183  		b.config.RemoteDatastore = "datastore1"
   184  	}
   185  
   186  	if b.config.RemoteCacheDatastore == "" {
   187  		b.config.RemoteCacheDatastore = b.config.RemoteDatastore
   188  	}
   189  
   190  	if b.config.RemoteCacheDirectory == "" {
   191  		b.config.RemoteCacheDirectory = "packer_cache"
   192  	}
   193  
   194  	if b.config.RemotePort == 0 {
   195  		b.config.RemotePort = 22
   196  	}
   197  
   198  	if b.config.VMXTemplatePath != "" {
   199  		if err := b.validateVMXTemplatePath(); err != nil {
   200  			errs = packer.MultiErrorAppend(
   201  				errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
   202  		}
   203  
   204  	}
   205  
   206  	if b.config.Network == "" {
   207  		b.config.Network = "nat"
   208  	}
   209  
   210  	if !b.config.Sound {
   211  		b.config.Sound = false
   212  	}
   213  
   214  	if !b.config.USB {
   215  		b.config.USB = false
   216  	}
   217  
   218  	// Remote configuration validation
   219  	if b.config.RemoteType != "" {
   220  		if b.config.RemoteHost == "" {
   221  			errs = packer.MultiErrorAppend(errs,
   222  				fmt.Errorf("remote_host must be specified"))
   223  		}
   224  		if b.config.RemoteType != "esx5" {
   225  			errs = packer.MultiErrorAppend(errs,
   226  				fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
   227  		}
   228  	}
   229  
   230  	if b.config.Format == "" {
   231  		b.config.Format = "ovf"
   232  	}
   233  
   234  	if !(b.config.Format == "ova" || b.config.Format == "ovf" || b.config.Format == "vmx") {
   235  		errs = packer.MultiErrorAppend(errs,
   236  			fmt.Errorf("format must be one of ova, ovf, or vmx"))
   237  	}
   238  
   239  	if b.config.RemoteType == "esx5" && b.config.SkipExport != true && b.config.RemotePassword == "" {
   240  		errs = packer.MultiErrorAppend(errs,
   241  			fmt.Errorf("exporting the vm (with ovftool) requires that you set a value for remote_password"))
   242  	}
   243  
   244  	// Warnings
   245  	if b.config.ShutdownCommand == "" {
   246  		warnings = append(warnings,
   247  			"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
   248  				"will forcibly halt the virtual machine, which may result in data loss.")
   249  	}
   250  
   251  	if b.config.Headless && b.config.DisableVNC {
   252  		warnings = append(warnings,
   253  			"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
   254  				"you won't be able to see any output.")
   255  	}
   256  
   257  	if errs != nil && len(errs.Errors) > 0 {
   258  		return warnings, errs
   259  	}
   260  
   261  	return warnings, nil
   262  }
   263  
   264  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   265  	driver, err := NewDriver(&b.config)
   266  	if err != nil {
   267  		return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
   268  	}
   269  
   270  	// Determine the output dir implementation
   271  	var dir OutputDir
   272  	switch d := driver.(type) {
   273  	case OutputDir:
   274  		dir = d
   275  	default:
   276  		dir = new(vmwcommon.LocalOutputDir)
   277  	}
   278  
   279  	exportOutputPath := b.config.OutputDir
   280  
   281  	if b.config.RemoteType != "" {
   282  		b.config.OutputDir = b.config.VMName
   283  	}
   284  	dir.SetOutputDir(b.config.OutputDir)
   285  
   286  	// Setup the state bag
   287  	state := new(multistep.BasicStateBag)
   288  	state.Put("cache", cache)
   289  	state.Put("config", &b.config)
   290  	state.Put("debug", b.config.PackerDebug)
   291  	state.Put("dir", dir)
   292  	state.Put("driver", driver)
   293  	state.Put("hook", hook)
   294  	state.Put("ui", ui)
   295  
   296  	steps := []multistep.Step{
   297  		&vmwcommon.StepPrepareTools{
   298  			RemoteType:        b.config.RemoteType,
   299  			ToolsUploadFlavor: b.config.ToolsUploadFlavor,
   300  		},
   301  		&common.StepDownload{
   302  			Checksum:     b.config.ISOChecksum,
   303  			ChecksumType: b.config.ISOChecksumType,
   304  			Description:  "ISO",
   305  			Extension:    b.config.TargetExtension,
   306  			ResultKey:    "iso_path",
   307  			TargetPath:   b.config.TargetPath,
   308  			Url:          b.config.ISOUrls,
   309  		},
   310  		&vmwcommon.StepOutputDir{
   311  			Force: b.config.PackerForce,
   312  		},
   313  		&common.StepCreateFloppy{
   314  			Files:       b.config.FloppyConfig.FloppyFiles,
   315  			Directories: b.config.FloppyConfig.FloppyDirectories,
   316  		},
   317  		&stepRemoteUpload{
   318  			Key:       "floppy_path",
   319  			Message:   "Uploading Floppy to remote machine...",
   320  			DoCleanup: true,
   321  		},
   322  		&stepRemoteUpload{
   323  			Key:     "iso_path",
   324  			Message: "Uploading ISO to remote machine...",
   325  		},
   326  		&stepCreateDisk{},
   327  		&stepCreateVMX{},
   328  		&vmwcommon.StepConfigureVMX{
   329  			CustomData: b.config.VMXData,
   330  		},
   331  		&vmwcommon.StepSuppressMessages{},
   332  		&common.StepHTTPServer{
   333  			HTTPDir:     b.config.HTTPDir,
   334  			HTTPPortMin: b.config.HTTPPortMin,
   335  			HTTPPortMax: b.config.HTTPPortMax,
   336  		},
   337  		&vmwcommon.StepConfigureVNC{
   338  			Enabled:            !b.config.DisableVNC,
   339  			VNCBindAddress:     b.config.VNCBindAddress,
   340  			VNCPortMin:         b.config.VNCPortMin,
   341  			VNCPortMax:         b.config.VNCPortMax,
   342  			VNCDisablePassword: b.config.VNCDisablePassword,
   343  		},
   344  		&StepRegister{
   345  			Format: b.config.Format,
   346  		},
   347  		&vmwcommon.StepRun{
   348  			DurationBeforeStop: 5 * time.Second,
   349  			Headless:           b.config.Headless,
   350  		},
   351  		&vmwcommon.StepTypeBootCommand{
   352  			BootWait:    b.config.BootWait,
   353  			VNCEnabled:  !b.config.DisableVNC,
   354  			BootCommand: b.config.FlatBootCommand(),
   355  			VMName:      b.config.VMName,
   356  			Ctx:         b.config.ctx,
   357  			KeyInterval: b.config.VNCConfig.BootKeyInterval,
   358  		},
   359  		&communicator.StepConnect{
   360  			Config:    &b.config.SSHConfig.Comm,
   361  			Host:      driver.CommHost,
   362  			SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
   363  		},
   364  		&vmwcommon.StepUploadTools{
   365  			RemoteType:        b.config.RemoteType,
   366  			ToolsUploadFlavor: b.config.ToolsUploadFlavor,
   367  			ToolsUploadPath:   b.config.ToolsUploadPath,
   368  			Ctx:               b.config.ctx,
   369  		},
   370  		&common.StepProvision{},
   371  		&common.StepCleanupTempKeys{
   372  			Comm: &b.config.SSHConfig.Comm,
   373  		},
   374  		&vmwcommon.StepShutdown{
   375  			Command: b.config.ShutdownCommand,
   376  			Timeout: b.config.ShutdownTimeout,
   377  		},
   378  		&vmwcommon.StepCleanFiles{},
   379  		&vmwcommon.StepCompactDisk{
   380  			Skip: b.config.SkipCompaction,
   381  		},
   382  		&vmwcommon.StepConfigureVMX{
   383  			CustomData: b.config.VMXDataPost,
   384  			SkipFloppy: true,
   385  		},
   386  		&vmwcommon.StepCleanVMX{
   387  			RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet,
   388  			VNCEnabled:               !b.config.DisableVNC,
   389  		},
   390  		&StepUploadVMX{
   391  			RemoteType: b.config.RemoteType,
   392  		},
   393  		&StepExport{
   394  			Format:     b.config.Format,
   395  			SkipExport: b.config.SkipExport,
   396  			OutputDir:  exportOutputPath,
   397  		},
   398  	}
   399  
   400  	// Run!
   401  	b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
   402  	b.runner.Run(state)
   403  
   404  	// If there was an error, return that
   405  	if rawErr, ok := state.GetOk("error"); ok {
   406  		return nil, rawErr.(error)
   407  	}
   408  
   409  	// If we were interrupted or cancelled, then just exit.
   410  	if _, ok := state.GetOk(multistep.StateCancelled); ok {
   411  		return nil, errors.New("Build was cancelled.")
   412  	}
   413  
   414  	if _, ok := state.GetOk(multistep.StateHalted); ok {
   415  		return nil, errors.New("Build was halted.")
   416  	}
   417  
   418  	// Compile the artifact list
   419  	var files []string
   420  	if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport {
   421  		dir = new(vmwcommon.LocalOutputDir)
   422  		dir.SetOutputDir(exportOutputPath)
   423  		files, err = dir.ListFiles()
   424  	} else {
   425  		files, err = state.Get("dir").(OutputDir).ListFiles()
   426  	}
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  
   431  	// Set the proper builder ID
   432  	builderId := vmwcommon.BuilderId
   433  	if b.config.RemoteType != "" {
   434  		builderId = BuilderIdESX
   435  	}
   436  
   437  	config := make(map[string]string)
   438  	config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered)
   439  	config[ArtifactConfFormat] = b.config.Format
   440  	config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport)
   441  
   442  	return &Artifact{
   443  		builderId: builderId,
   444  		id:        b.config.VMName,
   445  		dir:       dir,
   446  		f:         files,
   447  		config:    config,
   448  	}, nil
   449  }
   450  
   451  func (b *Builder) Cancel() {
   452  	if b.runner != nil {
   453  		log.Println("Cancelling the step runner...")
   454  		b.runner.Cancel()
   455  	}
   456  }
   457  
   458  func (b *Builder) validateVMXTemplatePath() error {
   459  	f, err := os.Open(b.config.VMXTemplatePath)
   460  	if err != nil {
   461  		return err
   462  	}
   463  	defer f.Close()
   464  
   465  	data, err := ioutil.ReadAll(f)
   466  	if err != nil {
   467  		return err
   468  	}
   469  
   470  	return interpolate.Validate(string(data), &b.config.ctx)
   471  }