diff --git a/common/iso_config.go b/common/iso_config.go index 6e3cd1f61..fddea5bb3 100644 --- a/common/iso_config.go +++ b/common/iso_config.go @@ -1,8 +1,14 @@ package common import ( + "bufio" "errors" "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" "strings" "github.com/mitchellh/packer/template/interpolate" @@ -11,6 +17,7 @@ import ( // ISOConfig contains configuration for downloading ISO images. type ISOConfig struct { ISOChecksum string `mapstructure:"iso_checksum"` + ISOChecksumURL string `mapstructure:"iso_checksum_url"` ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls"` TargetPath string `mapstructure:"iso_target_path"` @@ -23,36 +30,81 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) ([]string, []error) { var err error var warnings []string + if c.RawSingleISOUrl == "" && len(c.ISOUrls) == 0 { + errs = append( + errs, errors.New("One of iso_url or iso_urls must be specified.")) + } else if c.RawSingleISOUrl != "" && len(c.ISOUrls) > 0 { + errs = append( + errs, errors.New("Only one of iso_url or iso_urls may be specified.")) + } else if c.RawSingleISOUrl != "" { + c.ISOUrls = []string{c.RawSingleISOUrl} + } + if c.ISOChecksumType == "" { errs = append( errs, errors.New("The iso_checksum_type must be specified.")) } else { c.ISOChecksumType = strings.ToLower(c.ISOChecksumType) if c.ISOChecksumType != "none" { - if c.ISOChecksum == "" { + if c.ISOChecksum == "" && c.ISOChecksumURL == "" { errs = append( errs, errors.New("Due to large file sizes, an iso_checksum is required")) + return warnings, errs } else { - c.ISOChecksum = strings.ToLower(c.ISOChecksum) - } + if h := HashForType(c.ISOChecksumType); h == nil { + errs = append( + errs, fmt.Errorf("Unsupported checksum type: %s", c.ISOChecksumType)) + return warnings, errs + } - if h := HashForType(c.ISOChecksumType); h == nil { - errs = append( - errs, - fmt.Errorf("Unsupported checksum type: %s", c.ISOChecksumType)) + // If iso_checksum has no value use iso_checksum_url instead. + if c.ISOChecksum == "" { + u, err := url.Parse(c.ISOChecksumURL) + if err != nil { + errs = append(errs, + fmt.Errorf("Error parsing checksum: %s", err)) + return warnings, errs + } + switch u.Scheme { + case "http", "https": + res, err := http.Get(c.ISOChecksumURL) + c.ISOChecksum = "" + if err != nil { + errs = append(errs, + fmt.Errorf("Error getting checksum from url: %s", c.ISOChecksumURL)) + return warnings, errs + } + defer res.Body.Close() + err = c.parseCheckSumFile(bufio.NewReader(res.Body)) + if err != nil { + errs = append(errs, err) + return warnings, errs + } + case "file": + file, err := os.Open(u.Path) + if err != nil { + errs = append(errs, err) + return warnings, errs + } + err = c.parseCheckSumFile(bufio.NewReader(file)) + if err != nil { + errs = append(errs, err) + return warnings, errs + } + + case "": + break + default: + errs = append(errs, + fmt.Errorf("Error parsing checksum url: %s, scheme not supported: %s", c.ISOChecksumURL, u.Scheme)) + return warnings, errs + } + } } } } - if c.RawSingleISOUrl == "" && len(c.ISOUrls) == 0 { - errs = append( - errs, errors.New("One of iso_url or iso_urls must be specified.")) - } else if c.RawSingleISOUrl != "" && len(c.ISOUrls) > 0 { - errs = append( - errs, errors.New("Only one of iso_url or iso_urls may be specified.")) - } else if c.RawSingleISOUrl != "" { - c.ISOUrls = []string{c.RawSingleISOUrl} - } + c.ISOChecksum = strings.ToLower(c.ISOChecksum) for i, url := range c.ISOUrls { c.ISOUrls[i], err = DownloadableURL(url) @@ -71,3 +123,38 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) ([]string, []error) { return warnings, errs } + +func (c *ISOConfig) parseCheckSumFile(rd *bufio.Reader) error { + for { + line, err := rd.ReadString('\n') + if err != nil { + if err == io.EOF { + return fmt.Errorf("No checksum for \"%s\" found at: %s", filepath.Base(c.ISOUrls[0]), c.ISOChecksumURL) + } else { + return fmt.Errorf("Error getting checksum from url: %s , %s", c.ISOChecksumURL, err.Error()) + } + } + parts := strings.Fields(line) + if len(parts) < 2 { + continue + } + if strings.ToLower(parts[0]) == c.ISOChecksumType { + // BSD-style checksum + if parts[1] == fmt.Sprintf("(%s)", filepath.Base(c.ISOUrls[0])) { + c.ISOChecksum = parts[3] + break + } + } else { + // Standard checksum + if parts[1][0] == '*' { + // Binary mode + parts[1] = parts[1][1:] + } + if parts[1] == filepath.Base(c.ISOUrls[0]) { + c.ISOChecksum = parts[0] + break + } + } + } + return nil +} diff --git a/common/iso_config_test.go b/common/iso_config_test.go index 01e801ff4..baaeea2b6 100644 --- a/common/iso_config_test.go +++ b/common/iso_config_test.go @@ -1,6 +1,8 @@ package common import ( + "fmt" + "io/ioutil" "reflect" "testing" ) @@ -8,11 +10,22 @@ import ( func testISOConfig() ISOConfig { return ISOConfig{ ISOChecksum: "foo", + ISOChecksumURL: "", ISOChecksumType: "md5", - RawSingleISOUrl: "http://www.packer.io", + RawSingleISOUrl: "http://www.packer.io/the-OS.iso", } } +var cs_bsd_style = ` +MD5 (other.iso) = bAr +MD5 (the-OS.iso) = baZ +` + +var cs_gnu_style = ` +bAr0 *the-OS.iso +baZ0 other.iso +` + func TestISOConfigPrepare_ISOChecksum(t *testing.T) { i := testISOConfig() @@ -42,6 +55,53 @@ func TestISOConfigPrepare_ISOChecksum(t *testing.T) { } } +func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) { + i := testISOConfig() + i.ISOChecksumURL = "file:///not_read" + + // Test ISOChecksum overrides url + warns, err := i.Prepare(nil) + if len(warns) > 0 && len(err) > 0 { + t.Fatalf("bad: %#v, %#v", warns, err) + } + + // Test good - ISOChecksumURL BSD style + i = testISOConfig() + i.ISOChecksum = "" + cs_file, _ := ioutil.TempFile("", "packer-test-") + ioutil.WriteFile(cs_file.Name(), []byte(cs_bsd_style), 0666) + i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name()) + warns, err = i.Prepare(nil) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if i.ISOChecksum != "baz" { + t.Fatalf("should've found \"baz\" got: %s", i.ISOChecksum) + } + + // Test good - ISOChecksumURL GNU style + i = testISOConfig() + i.ISOChecksum = "" + cs_file, _ = ioutil.TempFile("", "packer-test-") + ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style), 0666) + i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name()) + warns, err = i.Prepare(nil) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if i.ISOChecksum != "bar0" { + t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum) + } +} + func TestISOConfigPrepare_ISOChecksumType(t *testing.T) { i := testISOConfig() @@ -113,7 +173,7 @@ func TestISOConfigPrepare_ISOUrl(t *testing.T) { // Test iso_url set i = testISOConfig() - i.RawSingleISOUrl = "http://www.packer.io" + i.RawSingleISOUrl = "http://www.packer.io/the-OS.iso" warns, err = i.Prepare(nil) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -122,15 +182,15 @@ func TestISOConfigPrepare_ISOUrl(t *testing.T) { t.Errorf("should not have error: %s", err) } - expected := []string{"http://www.packer.io"} + expected := []string{"http://www.packer.io/the-OS.iso"} if !reflect.DeepEqual(i.ISOUrls, expected) { t.Fatalf("bad: %#v", i.ISOUrls) } // Test both set i = testISOConfig() - i.RawSingleISOUrl = "http://www.packer.io" - i.ISOUrls = []string{"http://www.packer.io"} + i.RawSingleISOUrl = "http://www.packer.io/the-OS.iso" + i.ISOUrls = []string{"http://www.packer.io/the-OS.iso"} warns, err = i.Prepare(nil) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -143,8 +203,8 @@ func TestISOConfigPrepare_ISOUrl(t *testing.T) { i = testISOConfig() i.RawSingleISOUrl = "" i.ISOUrls = []string{ - "http://www.packer.io", - "http://www.hashicorp.com", + "http://www.packer.io/the-OS.iso", + "http://www.hashicorp.com/the-OS.iso", } warns, err = i.Prepare(nil) @@ -156,8 +216,8 @@ func TestISOConfigPrepare_ISOUrl(t *testing.T) { } expected = []string{ - "http://www.packer.io", - "http://www.hashicorp.com", + "http://www.packer.io/the-OS.iso", + "http://www.hashicorp.com/the-OS.iso", } if !reflect.DeepEqual(i.ISOUrls, expected) { t.Fatalf("bad: %#v", i.ISOUrls) diff --git a/website/source/docs/builders/parallels-iso.html.markdown b/website/source/docs/builders/parallels-iso.html.markdown index e243f54e5..66596cef7 100644 --- a/website/source/docs/builders/parallels-iso.html.markdown +++ b/website/source/docs/builders/parallels-iso.html.markdown @@ -59,7 +59,9 @@ builder. - `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. + 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 @@ -67,6 +69,11 @@ builder. 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. + - `iso_url` (string) - A URL to the ISO containing the installation image. This URL can be either an HTTP URL or a file URL (or path to a file). If this is an HTTP URL, Packer will download it and cache it between runs. diff --git a/website/source/docs/builders/qemu.html.markdown b/website/source/docs/builders/qemu.html.markdown index c074e6fac..ba8f1ec4c 100644 --- a/website/source/docs/builders/qemu.html.markdown +++ b/website/source/docs/builders/qemu.html.markdown @@ -83,11 +83,20 @@ builder. - `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. + 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 "md5", "sha1", "sha256", or - "sha512" currently. + `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. - `iso_url` (string) - A URL to the ISO containing the installation image. This URL can be either an HTTP URL or a file URL (or path to a file). If diff --git a/website/source/docs/builders/virtualbox-iso.html.markdown b/website/source/docs/builders/virtualbox-iso.html.markdown index bd46512b2..79c080762 100644 --- a/website/source/docs/builders/virtualbox-iso.html.markdown +++ b/website/source/docs/builders/virtualbox-iso.html.markdown @@ -57,7 +57,9 @@ builder. - `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. + 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 @@ -65,6 +67,11 @@ builder. 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. + - `iso_url` (string) - A URL to the ISO containing the installation image. This URL can be either an HTTP URL or a file URL (or path to a file). If this is an HTTP URL, Packer will download it and cache it between runs. diff --git a/website/source/docs/builders/vmware-iso.html.markdown b/website/source/docs/builders/vmware-iso.html.markdown index eccaabd1b..93f66a7b0 100644 --- a/website/source/docs/builders/vmware-iso.html.markdown +++ b/website/source/docs/builders/vmware-iso.html.markdown @@ -60,7 +60,9 @@ builder. - `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. + 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 @@ -68,6 +70,11 @@ builder. 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. + - `iso_url` (string) - A URL to the ISO containing the installation image. This URL can be either an HTTP URL or a file URL (or path to a file). If this is an HTTP URL, Packer will download it and cache it between runs.