diff --git a/README.md b/README.md index cf34877b2..024b91b9b 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,10 @@ See complete Ubuntu, Windows, and macOS templates in the [examples folder](https * `http_directory`(string) - Path to a directory to serve using a local HTTP server. Beware of [limitations](https://github.com/jetbrains-infra/packer-builder-vsphere/issues/108#issuecomment-449634324). * `http_ip`(string) - Specify IP address on which the HTTP server is started. If not provided the first non-loopback interface is used. * `http_port_min` and `http_port_max` as in other [builders](https://www.packer.io/docs/builders/virtualbox-iso.html#http_port_min). +* `iso_urls`(array of strings) - Multiple URLs for the ISO to download. Packer will try these in order. If anything goes wrong attempting to download or while downloading a single URL, it will move on to the next. All URLs must point to the same file (same checksum). By default this is empty and iso_url is used. Only one of iso_url or iso_urls can be specified. +* `iso_checksum `(string) - The checksum for the OS ISO file. Because ISO files are so large, this is required and Packer will verify it prior to booting a virtual machine with the ISO attached. The type of the checksum is specified with iso_checksum_type, documented below. At least one of iso_checksum and iso_checksum_url must be defined. This has precedence over iso_checksum_url type. +* `iso_checksum_type`(string) - The type of the checksum specified in iso_checksum. Valid values are none, md5, sha1, sha256, or sha512 currently. While none will skip checksumming, this is not recommended since ISO files are generally large and corruption does happen from time to time. +* `iso_checksum_url`(string) - A URL to a GNU or BSD style checksum file containing a checksum for the OS ISO file. At least one of iso_checksum and iso_checksum_url must be defined. This will be ignored if iso_checksum is non empty. * `boot_wait`(string) Amount of time to wait for the VM to boot. Examples 45s and 10m. Defaults to 10 seconds. See [format](https://golang.org/pkg/time/#ParseDuration). * `boot_command`(array of strings) - List of commands to type when the VM is first booted. Used to initalize the operating system installer. See details in [Packer docs](https://www.packer.io/docs/builders/virtualbox-iso.html#boot-command). diff --git a/driver/datastore.go b/driver/datastore.go index 7f4b3b1dc..4e0c191f0 100644 --- a/driver/datastore.go +++ b/driver/datastore.go @@ -100,6 +100,15 @@ func (ds *Datastore) Delete(path string) error { return fm.Delete(ds.driver.ctx, path) } +func (ds *Datastore) MakeDirectory(path string) error { + dc, err := ds.driver.finder.Datacenter(ds.driver.ctx, ds.ds.DatacenterPath) + if err != nil { + return err + } + fm := ds.ds.NewFileManager(dc, false) + return fm.FileManager.MakeDirectory(ds.driver.ctx, path, dc, true) +} + // Cuts out the datastore prefix // Example: "[datastore1] file.ext" --> "file.ext" func RemoveDatastorePrefix(path string) string { diff --git a/iso/builder.go b/iso/builder.go index 991ea86ee..91fc8d630 100644 --- a/iso/builder.go +++ b/iso/builder.go @@ -26,6 +26,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { state := new(multistep.BasicStateBag) + state.Put("cache", cache) state.Put("comm", &b.config.Comm) state.Put("hook", hook) state.Put("ui", ui) @@ -36,6 +37,27 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepConnect{ Config: &b.config.ConnectConfig, }, + ) + + if b.config.ISOUrls != nil { + steps = append(steps, + &packerCommon.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + Extension: b.config.TargetExtension, + ResultKey: "iso_path", + TargetPath: b.config.TargetPath, + Url: b.config.ISOUrls, + }, + &StepRemoteUpload{ + Datastore: b.config.Datastore, + Host: b.config.Host, + }, + ) + } + + steps = append(steps, &StepCreateVM{ Config: &b.config.CreateConfig, Location: &b.config.LocationConfig, diff --git a/iso/config.go b/iso/config.go index 4f88fafce..5e032126f 100644 --- a/iso/config.go +++ b/iso/config.go @@ -19,11 +19,14 @@ type Config struct { common.HardwareConfig `mapstructure:",squash"` common.ConfigParamsConfig `mapstructure:",squash"` - CDRomConfig `mapstructure:",squash"` - FloppyConfig `mapstructure:",squash"` - common.RunConfig `mapstructure:",squash"` - BootConfig `mapstructure:",squash"` - Comm communicator.Config `mapstructure:",squash"` + packerCommon.ISOConfig `mapstructure:",squash"` + + CDRomConfig `mapstructure:",squash"` + FloppyConfig `mapstructure:",squash"` + common.RunConfig `mapstructure:",squash"` + BootConfig `mapstructure:",squash"` + Comm communicator.Config `mapstructure:",squash"` + common.ShutdownConfig `mapstructure:",squash"` CreateSnapshot bool `mapstructure:"create_snapshot"` @@ -47,7 +50,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { return nil, nil, err } + warnings := make([]string, 0) errs := new(packer.MultiError) + + if c.ISOUrls != nil { + isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx) + warnings = append(warnings, isoWarnings...) + errs = packer.MultiErrorAppend(errs, isoErrs...) + } + errs = packer.MultiErrorAppend(errs, c.ConnectConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.CreateConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...) diff --git a/iso/step_add_cdrom.go b/iso/step_add_cdrom.go index 50a0b9250..2969e7551 100644 --- a/iso/step_add_cdrom.go +++ b/iso/step_add_cdrom.go @@ -40,13 +40,21 @@ func (s *StepAddCDRom) Run(_ context.Context, state multistep.StateBag) multiste } ui.Say("Mount ISO images...") - for _, path := range s.Config.ISOPaths { - if err := vm.AddCdrom(s.Config.CdromType, path); err != nil { + if len(s.Config.ISOPaths) > 0 { + for _, path := range s.Config.ISOPaths { + if err := vm.AddCdrom(s.Config.CdromType, path); err != nil { + state.Put("error", fmt.Errorf("error mounting an image: %v", err)) + return multistep.ActionHalt + } + } + } + + if path, ok := state.GetOk("iso_remote_path"); ok { + if err := vm.AddCdrom(s.Config.CdromType, path.(string)); err != nil { state.Put("error", fmt.Errorf("error mounting an image: %v", err)) return multistep.ActionHalt } } - return multistep.ActionContinue } diff --git a/iso/step_remote_upload.go b/iso/step_remote_upload.go new file mode 100644 index 000000000..53a7317af --- /dev/null +++ b/iso/step_remote_upload.go @@ -0,0 +1,57 @@ +package iso + +import ( + "context" + "fmt" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/jetbrains-infra/packer-builder-vsphere/driver" + "path/filepath" +) + +type StepRemoteUpload struct { + Datastore string + Host string +} + +func (s *StepRemoteUpload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + d := state.Get("driver").(*driver.Driver) + + if path, ok := state.GetOk("iso_path"); ok { + filename := filepath.Base(path.(string)) + + ds, err := d.FindDatastore(s.Datastore, s.Host) + if err != nil { + state.Put("error", fmt.Errorf("datastore doesn't exist: %v", err)) + return multistep.ActionHalt + } + + remotepath := fmt.Sprintf("packer_cache/%s", filename) + remotedirectory := fmt.Sprintf("[%s] packer_cache/", ds.Name()) + final_remotepath := fmt.Sprintf("%s/%s", remotedirectory, filename) + + ui.Say(fmt.Sprintf("Uploading %s to %s", filename, remotepath)) + + if exists := ds.FileExists(remotepath); exists == true { + ui.Say("File already upload") + state.Put("iso_remote_path", final_remotepath) + return multistep.ActionContinue + } + + if err := ds.MakeDirectory(remotedirectory); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + if err := ds.UploadFile(path.(string), remotepath, s.Host); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + state.Put("iso_remote_path", final_remotepath) + } + + return multistep.ActionContinue +} + +func (s *StepRemoteUpload) Cleanup(state multistep.StateBag) {}