From a6d4e326a78a05cc09df928b7700c5d62622ce28 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 15 Aug 2013 14:57:29 -0400 Subject: [PATCH] builder/virtualbox: iso_urls for multiple ISO urls [GH-110] --- CHANGELOG.md | 2 ++ builder/virtualbox/builder.go | 33 ++++++++++++----- builder/virtualbox/builder_test.go | 58 ++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 018d04612..36d437738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ FEATURES: parse out components of a template. * Packer will detect its own crashes (always a bug) and save a "crash.log" file. +* builder/virtualbox: You may now specify multiple URLs for an ISO + using "iso_url" in a template. The URLs will be tried in order. IMPROVEMENTS: diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 6862c3d04..970acb94e 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -37,7 +37,7 @@ type config struct { HTTPPortMax uint `mapstructure:"http_port_max"` ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksumType string `mapstructure:"iso_checksum_type"` - ISOUrl string `mapstructure:"iso_url"` + ISOUrls []string `mapstructure:"iso_urls"` OutputDir string `mapstructure:"output_directory"` ShutdownCommand string `mapstructure:"shutdown_command"` SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` @@ -50,6 +50,7 @@ type config struct { VMName string `mapstructure:"vm_name"` RawBootWait string `mapstructure:"boot_wait"` + RawSingleISOUrl string `mapstructure:"iso_url"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` @@ -137,7 +138,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { "http_directory": &b.config.HTTPDir, "iso_checksum": &b.config.ISOChecksum, "iso_checksum_type": &b.config.ISOChecksumType, - "iso_url": &b.config.ISOUrl, + "iso_url": &b.config.RawSingleISOUrl, "output_directory": &b.config.OutputDir, "shutdown_command": &b.config.ShutdownCommand, "ssh_password": &b.config.SSHPassword, @@ -158,6 +159,15 @@ func (b *Builder) Prepare(raws ...interface{}) error { } } + for i, url := range b.config.ISOUrls { + var err error + b.config.ISOUrls[i], err = b.config.tpl.Process(url, nil) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing iso_urls[%d]: %s", i, err)) + } + } + validates := map[string]*string{ "guest_additions_path": &b.config.GuestAdditionsPath, "guest_additions_url": &b.config.GuestAdditionsURL, @@ -211,14 +221,21 @@ func (b *Builder) Prepare(raws ...interface{}) error { } } - if b.config.ISOUrl == "" { + if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { errs = packer.MultiErrorAppend( - errs, errors.New("An iso_url must be specified.")) - } else { - b.config.ISOUrl, err = common.DownloadableURL(b.config.ISOUrl) + errs, errors.New("One of iso_url or iso_urls must be specified.")) + } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("Only one of iso_url or iso_urls may be specified.")) + } else if b.config.RawSingleISOUrl != "" { + b.config.ISOUrls = []string{b.config.RawSingleISOUrl} + } + + for i, url := range b.config.ISOUrls { + b.config.ISOUrls[i], err = common.DownloadableURL(url) if err != nil { errs = packer.MultiErrorAppend( - errs, fmt.Errorf("iso_url: %s", err)) + errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) } } @@ -300,7 +317,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ChecksumType: b.config.ISOChecksumType, Description: "ISO", ResultKey: "iso_path", - Url: []string{b.config.ISOUrl}, + Url: b.config.ISOUrls, }, new(stepPrepareOutputDir), &common.StepCreateFloppy{ diff --git a/builder/virtualbox/builder_test.go b/builder/virtualbox/builder_test.go index 64e24e141..035cb87c8 100644 --- a/builder/virtualbox/builder_test.go +++ b/builder/virtualbox/builder_test.go @@ -84,6 +84,7 @@ func TestBuilderPrepare_BootWait(t *testing.T) { // Test with a good one config["boot_wait"] = "5s" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -210,6 +211,7 @@ func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) { } config["guest_additions_url"] = "http://www.packer.io" + b = Builder{} err = b.Prepare(config) if err != nil { t.Errorf("should not have error: %s", err) @@ -230,6 +232,7 @@ func TestBuilderPrepare_HTTPPort(t *testing.T) { // Bad config["http_port_min"] = -500 + b = Builder{} err = b.Prepare(config) if err == nil { t.Fatal("should have error") @@ -238,6 +241,7 @@ func TestBuilderPrepare_HTTPPort(t *testing.T) { // Good config["http_port_min"] = 500 config["http_port_max"] = 1000 + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -269,6 +273,7 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { // Test good config["iso_checksum"] = "FOo" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -292,6 +297,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test good config["iso_checksum_type"] = "mD5" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -303,6 +309,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { // Test unknown config["iso_checksum_type"] = "fake" + b = Builder{} err = b.Prepare(config) if err == nil { t.Fatal("should have error") @@ -312,18 +319,59 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { func TestBuilderPrepare_ISOUrl(t *testing.T) { var b Builder config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + // Test both epty config["iso_url"] = "" + b = Builder{} err := b.Prepare(config) if err == nil { t.Fatal("should have error") } + // Test iso_url set config["iso_url"] = "http://www.packer.io" + b = Builder{} err = b.Prepare(config) if err != nil { t.Errorf("should not have error: %s", err) } + + expected := []string{"http://www.packer.io"} + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test both set + config["iso_url"] = "http://www.packer.io" + config["iso_urls"] = []string{"http://www.packer.io"} + b = Builder{} + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Test just iso_urls set + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + + b = Builder{} + err = b.Prepare(config) + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } } func TestBuilderPrepare_OutputDir(t *testing.T) { @@ -338,6 +386,7 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { defer os.RemoveAll(dir) config["output_directory"] = dir + b = Builder{} err = b.Prepare(config) if err == nil { t.Fatal("should have error") @@ -345,6 +394,7 @@ func TestBuilderPrepare_OutputDir(t *testing.T) { // Test with a good one config["output_directory"] = "i-hope-i-dont-exist" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -364,6 +414,7 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) { // Test with a good one config["shutdown_timeout"] = "5s" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -377,6 +428,7 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) { // Bad config["ssh_host_port_min"] = 1000 config["ssh_host_port_max"] = 500 + b = Builder{} err := b.Prepare(config) if err == nil { t.Fatal("should have error") @@ -384,6 +436,7 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) { // Bad config["ssh_host_port_min"] = -500 + b = Builder{} err = b.Prepare(config) if err == nil { t.Fatal("should have error") @@ -392,6 +445,7 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) { // Good config["ssh_host_port_min"] = 500 config["ssh_host_port_max"] = 1000 + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -403,12 +457,14 @@ func TestBuilderPrepare_SSHUser(t *testing.T) { config := testConfig() config["ssh_username"] = "" + b = Builder{} err := b.Prepare(config) if err == nil { t.Fatal("should have error") } config["ssh_username"] = "exists" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) @@ -432,6 +488,7 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { // Test with a bad value config["ssh_wait_timeout"] = "this is not good" + b = Builder{} err = b.Prepare(config) if err == nil { t.Fatal("should have error") @@ -439,6 +496,7 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { // Test with a good one config["ssh_wait_timeout"] = "5s" + b = Builder{} err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err)