github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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.DiskTypeId == "" {
   143  		// Default is growable virtual disk split in 2GB files.
   144  		b.config.DiskTypeId = "1"
   145  
   146  		if b.config.RemoteType == "esx5" {
   147  			b.config.DiskTypeId = "zeroedthick"
   148  		}
   149  	}
   150  
   151  	if b.config.GuestOSType == "" {
   152  		b.config.GuestOSType = "other"
   153  	}
   154  
   155  	if b.config.VMName == "" {
   156  		b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
   157  	}
   158  
   159  	if b.config.Version == "" {
   160  		b.config.Version = "9"
   161  	}
   162  
   163  	if b.config.RemoteUser == "" {
   164  		b.config.RemoteUser = "root"
   165  	}
   166  
   167  	if b.config.RemoteDatastore == "" {
   168  		b.config.RemoteDatastore = "datastore1"
   169  	}
   170  
   171  	if b.config.RemoteCacheDatastore == "" {
   172  		b.config.RemoteCacheDatastore = b.config.RemoteDatastore
   173  	}
   174  
   175  	if b.config.RemoteCacheDirectory == "" {
   176  		b.config.RemoteCacheDirectory = "packer_cache"
   177  	}
   178  
   179  	if b.config.RemotePort == 0 {
   180  		b.config.RemotePort = 22
   181  	}
   182  
   183  	if b.config.VMXTemplatePath != "" {
   184  		if err := b.validateVMXTemplatePath(); err != nil {
   185  			errs = packer.MultiErrorAppend(
   186  				errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
   187  		}
   188  
   189  	}
   190  
   191  	if b.config.Network == "" {
   192  		b.config.Network = "nat"
   193  	}
   194  
   195  	if !b.config.Sound {
   196  		b.config.Sound = false
   197  	}
   198  
   199  	if !b.config.USB {
   200  		b.config.USB = false
   201  	}
   202  
   203  	// Remote configuration validation
   204  	if b.config.RemoteType != "" {
   205  		if b.config.RemoteHost == "" {
   206  			errs = packer.MultiErrorAppend(errs,
   207  				fmt.Errorf("remote_host must be specified"))
   208  		}
   209  		if b.config.RemoteType != "esx5" {
   210  			errs = packer.MultiErrorAppend(errs,
   211  				fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
   212  		}
   213  	}
   214  
   215  	if b.config.Format == "" {
   216  		b.config.Format = "ovf"
   217  	}
   218  
   219  	if !(b.config.Format == "ova" || b.config.Format == "ovf" || b.config.Format == "vmx") {
   220  		errs = packer.MultiErrorAppend(errs,
   221  			fmt.Errorf("format must be one of ova, ovf, or vmx"))
   222  	}
   223  
   224  	if b.config.RemoteType == "esx5" && b.config.SkipExport != true && b.config.RemotePassword == "" {
   225  		errs = packer.MultiErrorAppend(errs,
   226  			fmt.Errorf("exporting the vm (with ovftool) requires that you set a value for remote_password"))
   227  	}
   228  
   229  	// Warnings
   230  	if b.config.ShutdownCommand == "" {
   231  		warnings = append(warnings,
   232  			"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
   233  				"will forcibly halt the virtual machine, which may result in data loss.")
   234  	}
   235  
   236  	if b.config.Headless && b.config.DisableVNC {
   237  		warnings = append(warnings,
   238  			"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
   239  				"you won't be able to see any output.")
   240  	}
   241  
   242  	if errs != nil && len(errs.Errors) > 0 {
   243  		return warnings, errs
   244  	}
   245  
   246  	return warnings, nil
   247  }
   248  
   249  func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
   250  	driver, err := NewDriver(&b.config)
   251  	if err != nil {
   252  		return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
   253  	}
   254  
   255  	// Determine the output dir implementation
   256  	var dir OutputDir
   257  	switch d := driver.(type) {
   258  	case OutputDir:
   259  		dir = d
   260  	default:
   261  		dir = new(vmwcommon.LocalOutputDir)
   262  	}
   263  
   264  	exportOutputPath := b.config.OutputDir
   265  
   266  	if b.config.RemoteType != "" {
   267  		b.config.OutputDir = b.config.VMName
   268  	}
   269  	dir.SetOutputDir(b.config.OutputDir)
   270  
   271  	// Setup the state bag
   272  	state := new(multistep.BasicStateBag)
   273  	state.Put("cache", cache)
   274  	state.Put("config", &b.config)
   275  	state.Put("debug", b.config.PackerDebug)
   276  	state.Put("dir", dir)
   277  	state.Put("driver", driver)
   278  	state.Put("hook", hook)
   279  	state.Put("ui", ui)
   280  
   281  	steps := []multistep.Step{
   282  		&vmwcommon.StepPrepareTools{
   283  			RemoteType:        b.config.RemoteType,
   284  			ToolsUploadFlavor: b.config.ToolsUploadFlavor,
   285  		},
   286  		&common.StepDownload{
   287  			Checksum:     b.config.ISOChecksum,
   288  			ChecksumType: b.config.ISOChecksumType,
   289  			Description:  "ISO",
   290  			Extension:    b.config.TargetExtension,
   291  			ResultKey:    "iso_path",
   292  			TargetPath:   b.config.TargetPath,
   293  			Url:          b.config.ISOUrls,
   294  		},
   295  		&vmwcommon.StepOutputDir{
   296  			Force: b.config.PackerForce,
   297  		},
   298  		&common.StepCreateFloppy{
   299  			Files:       b.config.FloppyConfig.FloppyFiles,
   300  			Directories: b.config.FloppyConfig.FloppyDirectories,
   301  		},
   302  		&stepRemoteUpload{
   303  			Key:       "floppy_path",
   304  			Message:   "Uploading Floppy to remote machine...",
   305  			DoCleanup: true,
   306  		},
   307  		&stepRemoteUpload{
   308  			Key:     "iso_path",
   309  			Message: "Uploading ISO to remote machine...",
   310  		},
   311  		&stepCreateDisk{},
   312  		&stepCreateVMX{},
   313  		&vmwcommon.StepConfigureVMX{
   314  			CustomData: b.config.VMXData,
   315  		},
   316  		&vmwcommon.StepSuppressMessages{},
   317  		&common.StepHTTPServer{
   318  			HTTPDir:     b.config.HTTPDir,
   319  			HTTPPortMin: b.config.HTTPPortMin,
   320  			HTTPPortMax: b.config.HTTPPortMax,
   321  		},
   322  		&vmwcommon.StepConfigureVNC{
   323  			Enabled:            !b.config.DisableVNC,
   324  			VNCBindAddress:     b.config.VNCBindAddress,
   325  			VNCPortMin:         b.config.VNCPortMin,
   326  			VNCPortMax:         b.config.VNCPortMax,
   327  			VNCDisablePassword: b.config.VNCDisablePassword,
   328  		},
   329  		&StepRegister{
   330  			Format: b.config.Format,
   331  		},
   332  		&vmwcommon.StepRun{
   333  			DurationBeforeStop: 5 * time.Second,
   334  			Headless:           b.config.Headless,
   335  		},
   336  		&vmwcommon.StepTypeBootCommand{
   337  			BootWait:    b.config.BootWait,
   338  			VNCEnabled:  !b.config.DisableVNC,
   339  			BootCommand: b.config.FlatBootCommand(),
   340  			VMName:      b.config.VMName,
   341  			Ctx:         b.config.ctx,
   342  		},
   343  		&communicator.StepConnect{
   344  			Config:    &b.config.SSHConfig.Comm,
   345  			Host:      driver.CommHost,
   346  			SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
   347  		},
   348  		&vmwcommon.StepUploadTools{
   349  			RemoteType:        b.config.RemoteType,
   350  			ToolsUploadFlavor: b.config.ToolsUploadFlavor,
   351  			ToolsUploadPath:   b.config.ToolsUploadPath,
   352  			Ctx:               b.config.ctx,
   353  		},
   354  		&common.StepProvision{},
   355  		&vmwcommon.StepShutdown{
   356  			Command: b.config.ShutdownCommand,
   357  			Timeout: b.config.ShutdownTimeout,
   358  		},
   359  		&vmwcommon.StepCleanFiles{},
   360  		&vmwcommon.StepCompactDisk{
   361  			Skip: b.config.SkipCompaction,
   362  		},
   363  		&vmwcommon.StepConfigureVMX{
   364  			CustomData: b.config.VMXDataPost,
   365  			SkipFloppy: true,
   366  		},
   367  		&vmwcommon.StepCleanVMX{
   368  			RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet,
   369  			VNCEnabled:               !b.config.DisableVNC,
   370  		},
   371  		&StepUploadVMX{
   372  			RemoteType: b.config.RemoteType,
   373  		},
   374  		&StepExport{
   375  			Format:     b.config.Format,
   376  			SkipExport: b.config.SkipExport,
   377  			OutputDir:  exportOutputPath,
   378  		},
   379  	}
   380  
   381  	// Run!
   382  	b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
   383  	b.runner.Run(state)
   384  
   385  	// If there was an error, return that
   386  	if rawErr, ok := state.GetOk("error"); ok {
   387  		return nil, rawErr.(error)
   388  	}
   389  
   390  	// If we were interrupted or cancelled, then just exit.
   391  	if _, ok := state.GetOk(multistep.StateCancelled); ok {
   392  		return nil, errors.New("Build was cancelled.")
   393  	}
   394  
   395  	if _, ok := state.GetOk(multistep.StateHalted); ok {
   396  		return nil, errors.New("Build was halted.")
   397  	}
   398  
   399  	// Compile the artifact list
   400  	var files []string
   401  	if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport {
   402  		dir = new(vmwcommon.LocalOutputDir)
   403  		dir.SetOutputDir(exportOutputPath)
   404  		files, err = dir.ListFiles()
   405  	} else {
   406  		files, err = state.Get("dir").(OutputDir).ListFiles()
   407  	}
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  
   412  	// Set the proper builder ID
   413  	builderId := vmwcommon.BuilderId
   414  	if b.config.RemoteType != "" {
   415  		builderId = BuilderIdESX
   416  	}
   417  
   418  	config := make(map[string]string)
   419  	config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered)
   420  	config[ArtifactConfFormat] = b.config.Format
   421  	config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport)
   422  
   423  	return &Artifact{
   424  		builderId: builderId,
   425  		id:        b.config.VMName,
   426  		dir:       dir,
   427  		f:         files,
   428  		config:    config,
   429  	}, nil
   430  }
   431  
   432  func (b *Builder) Cancel() {
   433  	if b.runner != nil {
   434  		log.Println("Cancelling the step runner...")
   435  		b.runner.Cancel()
   436  	}
   437  }
   438  
   439  func (b *Builder) validateVMXTemplatePath() error {
   440  	f, err := os.Open(b.config.VMXTemplatePath)
   441  	if err != nil {
   442  		return err
   443  	}
   444  	defer f.Close()
   445  
   446  	data, err := ioutil.ReadAll(f)
   447  	if err != nil {
   448  		return err
   449  	}
   450  
   451  	return interpolate.Validate(string(data), &b.config.ctx)
   452  }