Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic checksum #1

Merged
merged 4 commits into from
Oct 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package getter

import (
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"

urlhelper "github.com/hashicorp/terraform/helper/url"
)
Expand Down Expand Up @@ -71,10 +80,65 @@ func (c *Client) Get() error {
"download not supported for scheme '%s'", force)
}

// Determine if we have a checksum
var checksumHash hash.Hash
var checksumValue []byte
q := u.Query()
if v := q.Get("checksum"); v != "" {
// Delete the query parameter if we have it.
q.Del("checksum")
u.RawQuery = q.Encode()

// If we're getting a directory, then this is an error. You cannot
// checksum a directory. TODO: test
if c.Dir {
return fmt.Errorf(
"checksum cannot be specified for directory download")
}

// Determine the checksum hash type
checksumType := ""
idx := strings.Index(v, ":")
if idx > -1 {
checksumType = v[:idx]
}
switch checksumType {
case "md5":
checksumHash = md5.New()
case "sha1":
checksumHash = sha1.New()
case "sha256":
checksumHash = sha256.New()
case "sha512":
checksumHash = sha512.New()
default:
return fmt.Errorf(
"unsupported checksum type: %s", checksumType)
}

// Get the remainder of the value and parse it into bytes
b, err := hex.DecodeString(v[idx+1:])
if err != nil {
return fmt.Errorf("invalid checksum: %s", err)
}

// Set our value
checksumValue = b
}

// If we're not downloading a directory, then just download the file
// and return.
if !c.Dir {
return g.GetFile(dst, u)
err := g.GetFile(dst, u)
if err != nil {
return err
}

if checksumHash != nil {
return checksum(dst, checksumHash, checksumValue)
}

return nil
}

// We're downloading a directory, which might require a bit more work
Expand All @@ -99,3 +163,26 @@ func (c *Client) Get() error {

return nil
}

// checksum is a simple method to compute the checksum of a source file
// and compare it to the given expected value.
func checksum(source string, h hash.Hash, v []byte) error {
f, err := os.Open(source)
if err != nil {
return fmt.Errorf("Failed to open file for checksum: %s", err)
}
defer f.Close()

if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("Failed to hash: %s", err)
}

if actual := h.Sum(nil); !bytes.Equal(actual, v) {
return fmt.Errorf(
"Checksums did not match.\nExpected: %s\nGot: %s",
hex.EncodeToString(v),
hex.EncodeToString(actual))
}

return nil
}
45 changes: 45 additions & 0 deletions get_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package getter

import (
"net/url"
)

// MockGetter is an implementation of Getter that can be used for tests.
type MockGetter struct {
// Proxy, if set, will be called after recording the calls below.
// If it isn't set, then the *Err values will be returned.
Proxy Getter

GetCalled bool
GetDst string
GetURL *url.URL
GetErr error

GetFileCalled bool
GetFileDst string
GetFileURL *url.URL
GetFileErr error
}

func (g *MockGetter) Get(dst string, u *url.URL) error {
g.GetCalled = true
g.GetDst = dst
g.GetURL = u

if g.Proxy != nil {
return g.Proxy.Get(dst, u)
}

return g.GetErr
}

func (g *MockGetter) GetFile(dst string, u *url.URL) error {
g.GetFileCalled = true
g.GetFileDst = dst
g.GetFileURL = u

if g.Proxy != nil {
return g.Proxy.GetFile(dst, u)
}
return g.GetFileErr
}
102 changes: 102 additions & 0 deletions get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,105 @@ func TestGet_fileSubdir(t *testing.T) {
t.Fatalf("err: %s", err)
}
}

func TestGetFile(t *testing.T) {
dst := tempFile(t)
u := testModule("basic-file/foo.txt")

if err := GetFile(dst, u); err != nil {
t.Fatalf("err: %s", err)
}

// Verify the main file exists
assertContents(t, dst, "Hello\n")
}

func TestGetFile_checksum(t *testing.T) {
cases := []struct {
Append string
Err bool
}{
{
"",
false,
},

// MD5
{
"?checksum=md5:09f7e02f1290be211da707a266f153b3",
false,
},
{
"?checksum=md5:09f7e02f1290be211da707a266f153b4",
true,
},

// SHA1
{
"?checksum=sha1:1d229271928d3f9e2bb0375bd6ce5db6c6d348d9",
false,
},
{
"?checksum=sha1:1d229271928d3f9e2bb0375bd6ce5db6c6d348d0",
true,
},

// SHA256
{
"?checksum=sha256:66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f18",
false,
},
{
"?checksum=sha256:66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f19",
true,
},

// SHA512
{
"?checksum=sha512:c2bad2223811194582af4d1508ac02cd69eeeeedeeb98d54fcae4dcefb13cc882e7640328206603d3fb9cd5f949a9be0db054dd34fbfa190c498a5fe09750cef",
false,
},
{
"?checksum=sha512:c2bad2223811194582af4d1508ac02cd69eeeeedeeb98d54fcae4dcefb13cc882e7640328206603d3fb9cd5f949a9be0db054dd34fbfa190c498a5fe09750ced",
true,
},
}

for _, tc := range cases {
u := testModule("basic-file/foo.txt") + tc.Append

func() {
dst := tempFile(t)
defer os.Remove(dst)
if err := GetFile(dst, u); (err != nil) != tc.Err {
t.Fatalf("append: %s\n\nerr: %s", tc.Append, err)
}

// Verify the main file exists
assertContents(t, dst, "Hello\n")
}()
}
}

func TestGetFile_checksumURL(t *testing.T) {
dst := tempFile(t)
u := testModule("basic-file/foo.txt") + "?checksum=md5:09f7e02f1290be211da707a266f153b3"

getter := &MockGetter{Proxy: new(FileGetter)}
client := &Client{
Src: u,
Dst: dst,
Dir: false,
Getters: map[string]Getter{
"file": getter,
},
}

if err := client.Get(); err != nil {
t.Fatalf("err: %s", err)
}

if v := getter.GetFileURL.Query().Get("checksum"); v != "" {
t.Fatalf("bad: %s", v)
}
}