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