From 520a2706eaf55e54dabf1834412fc3c9834520a2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 9 Jun 2013 23:00:07 -0700 Subject: [PATCH] builder/vmware: Validate MD5 --- builder/vmware/builder.go | 10 +-- builder/vmware/builder_test.go | 8 ++- builder/vmware/step_download_iso.go | 94 ++++++++++++++++++++--------- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/builder/vmware/builder.go b/builder/vmware/builder.go index c53eb778b..5a824bd43 100644 --- a/builder/vmware/builder.go +++ b/builder/vmware/builder.go @@ -49,10 +49,9 @@ type config struct { RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` } -func (b *Builder) Prepare(raw interface{}) (err error) { - err = mapstructure.Decode(raw, &b.config) - if err != nil { - return +func (b *Builder) Prepare(raw interface{}) error { + if err := mapstructure.Decode(raw, &b.config); err != nil { + return err } if b.config.DiskName == "" { @@ -88,6 +87,7 @@ func (b *Builder) Prepare(raw interface{}) (err error) { } // Accumulate any errors + var err error errs := make([]error, 0) if b.config.HTTPPortMin > b.config.HTTPPortMax { @@ -96,6 +96,8 @@ func (b *Builder) Prepare(raw interface{}) (err error) { if b.config.ISOMD5 == "" { errs = append(errs, errors.New("Due to large file sizes, an iso_md5 is required")) + } else { + b.config.ISOMD5 = strings.ToLower(b.config.ISOMD5) } if b.config.ISOUrl == "" { diff --git a/builder/vmware/builder_test.go b/builder/vmware/builder_test.go index 9230280d5..2ecac2d32 100644 --- a/builder/vmware/builder_test.go +++ b/builder/vmware/builder_test.go @@ -10,7 +10,7 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "iso_md5": "foo", + "iso_md5": "foo", "iso_url": "http://www.packer.io", "ssh_username": "foo", } @@ -108,11 +108,15 @@ func TestBuilderPrepare_ISOMD5(t *testing.T) { } // Test good - config["iso_md5"] = "foo" + config["iso_md5"] = "FOo" err = b.Prepare(config) if err != nil { t.Fatalf("should not have error: %s", err) } + + if b.config.ISOMD5 != "foo" { + t.Fatalf("should've lowercased: %s", b.config.ISOMD5) + } } func TestBuilderPrepare_ISOUrl(t *testing.T) { diff --git a/builder/vmware/step_download_iso.go b/builder/vmware/step_download_iso.go index 4a3555f4d..e34416281 100644 --- a/builder/vmware/step_download_iso.go +++ b/builder/vmware/step_download_iso.go @@ -1,6 +1,7 @@ package vmware import ( + "crypto/md5" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -9,6 +10,7 @@ import ( "net/http" "net/url" "os" + "strings" "time" ) @@ -32,43 +34,59 @@ func (s stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction cachePath := cache.Lock(config.ISOUrl) defer cache.Unlock(config.ISOUrl) - url, err := url.Parse(config.ISOUrl) + err := s.checkMD5(cachePath, config.ISOMD5) + haveFile := err == nil if err != nil { - ui.Error(fmt.Sprintf("Error parsing iso_url: %s", err)) - return multistep.ActionHalt + if !os.IsNotExist(err) { + ui.Say(fmt.Sprintf("Error validating MD5 of ISO: %s", err)) + return multistep.ActionHalt + } } - // Start the download in a goroutine so that we cancel it and such. - var progress uint - downloadComplete := make(chan bool, 1) - go func() { - ui.Say("Copying or downloading ISO. Progress will be shown periodically.") - cachePath, err = s.downloadUrl(cachePath, url, &progress) - downloadComplete <- true - }() - - progressTimer := time.NewTicker(15 * time.Second) - defer progressTimer.Stop() + if !haveFile { + url, err := url.Parse(config.ISOUrl) + if err != nil { + ui.Error(fmt.Sprintf("Error parsing iso_url: %s", err)) + return multistep.ActionHalt + } -DownloadWaitLoop: - for { - select { - case <-downloadComplete: - log.Println("Download of ISO completed.") - break DownloadWaitLoop - case <-progressTimer.C: - ui.Say(fmt.Sprintf("Download progress: %d%%", progress)) - case <-time.After(1 * time.Second): - if _, ok := state[multistep.StateCancelled]; ok { - ui.Say("Interrupt received. Cancelling download...") - return multistep.ActionHalt + // Start the download in a goroutine so that we cancel it and such. + var progress uint + downloadComplete := make(chan bool, 1) + go func() { + ui.Say("Copying or downloading ISO. Progress will be shown periodically.") + cachePath, err = s.downloadUrl(cachePath, url, &progress) + downloadComplete <- true + }() + + progressTimer := time.NewTicker(15 * time.Second) + defer progressTimer.Stop() + + DownloadWaitLoop: + for { + select { + case <-downloadComplete: + log.Println("Download of ISO completed.") + break DownloadWaitLoop + case <-progressTimer.C: + ui.Say(fmt.Sprintf("Download progress: %d%%", progress)) + case <-time.After(1 * time.Second): + if _, ok := state[multistep.StateCancelled]; ok { + ui.Say("Interrupt received. Cancelling download...") + return multistep.ActionHalt + } } } - } - if err != nil { - ui.Error(fmt.Sprintf("Error downloading ISO: %s", err)) - return multistep.ActionHalt + if err != nil { + ui.Error(fmt.Sprintf("Error downloading ISO: %s", err)) + return multistep.ActionHalt + } + + if err = s.checkMD5(cachePath, config.ISOMD5); err != nil { + ui.Say(fmt.Sprintf("Error validating MD5 of ISO: %s", err)) + return multistep.ActionHalt + } } log.Printf("Path to ISO on disk: %s", cachePath) @@ -79,6 +97,22 @@ DownloadWaitLoop: func (stepDownloadISO) Cleanup(map[string]interface{}) {} +func (stepDownloadISO) checkMD5(path string, expected string) error { + f, err := os.Open(path) + if err != nil { + return err + } + + hash := md5.New() + io.Copy(hash, f) + result := strings.ToLower(string(hash.Sum(nil))) + if result != expected { + return fmt.Errorf("result != expected: %s != %s", result, expected) + } + + return nil +} + func (stepDownloadISO) downloadUrl(path string, url *url.URL, progress *uint) (string, error) { if url.Scheme == "file" { // If it is just a file URL, then we already have the ISO