github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/vmware/iso/builder.go (about)

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