Skip to content

Commit f3864c8

Browse files
committed
Merge pull request #1 from hashicorp/f-checksum
Add basic checksum
2 parents 698edbe + 9e68b21 commit f3864c8

File tree

3 files changed

+235
-1
lines changed

3 files changed

+235
-1
lines changed

client.go

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package getter
22

33
import (
4+
"bytes"
5+
"crypto/md5"
6+
"crypto/sha1"
7+
"crypto/sha256"
8+
"crypto/sha512"
9+
"encoding/hex"
410
"fmt"
11+
"hash"
12+
"io"
513
"io/ioutil"
614
"os"
715
"path/filepath"
16+
"strings"
817

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

83+
// Determine if we have a checksum
84+
var checksumHash hash.Hash
85+
var checksumValue []byte
86+
q := u.Query()
87+
if v := q.Get("checksum"); v != "" {
88+
// Delete the query parameter if we have it.
89+
q.Del("checksum")
90+
u.RawQuery = q.Encode()
91+
92+
// If we're getting a directory, then this is an error. You cannot
93+
// checksum a directory. TODO: test
94+
if c.Dir {
95+
return fmt.Errorf(
96+
"checksum cannot be specified for directory download")
97+
}
98+
99+
// Determine the checksum hash type
100+
checksumType := ""
101+
idx := strings.Index(v, ":")
102+
if idx > -1 {
103+
checksumType = v[:idx]
104+
}
105+
switch checksumType {
106+
case "md5":
107+
checksumHash = md5.New()
108+
case "sha1":
109+
checksumHash = sha1.New()
110+
case "sha256":
111+
checksumHash = sha256.New()
112+
case "sha512":
113+
checksumHash = sha512.New()
114+
default:
115+
return fmt.Errorf(
116+
"unsupported checksum type: %s", checksumType)
117+
}
118+
119+
// Get the remainder of the value and parse it into bytes
120+
b, err := hex.DecodeString(v[idx+1:])
121+
if err != nil {
122+
return fmt.Errorf("invalid checksum: %s", err)
123+
}
124+
125+
// Set our value
126+
checksumValue = b
127+
}
128+
74129
// If we're not downloading a directory, then just download the file
75130
// and return.
76131
if !c.Dir {
77-
return g.GetFile(dst, u)
132+
err := g.GetFile(dst, u)
133+
if err != nil {
134+
return err
135+
}
136+
137+
if checksumHash != nil {
138+
return checksum(dst, checksumHash, checksumValue)
139+
}
140+
141+
return nil
78142
}
79143

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

100164
return nil
101165
}
166+
167+
// checksum is a simple method to compute the checksum of a source file
168+
// and compare it to the given expected value.
169+
func checksum(source string, h hash.Hash, v []byte) error {
170+
f, err := os.Open(source)
171+
if err != nil {
172+
return fmt.Errorf("Failed to open file for checksum: %s", err)
173+
}
174+
defer f.Close()
175+
176+
if _, err := io.Copy(h, f); err != nil {
177+
return fmt.Errorf("Failed to hash: %s", err)
178+
}
179+
180+
if actual := h.Sum(nil); !bytes.Equal(actual, v) {
181+
return fmt.Errorf(
182+
"Checksums did not match.\nExpected: %s\nGot: %s",
183+
hex.EncodeToString(v),
184+
hex.EncodeToString(actual))
185+
}
186+
187+
return nil
188+
}

get_mock.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package getter
2+
3+
import (
4+
"net/url"
5+
)
6+
7+
// MockGetter is an implementation of Getter that can be used for tests.
8+
type MockGetter struct {
9+
// Proxy, if set, will be called after recording the calls below.
10+
// If it isn't set, then the *Err values will be returned.
11+
Proxy Getter
12+
13+
GetCalled bool
14+
GetDst string
15+
GetURL *url.URL
16+
GetErr error
17+
18+
GetFileCalled bool
19+
GetFileDst string
20+
GetFileURL *url.URL
21+
GetFileErr error
22+
}
23+
24+
func (g *MockGetter) Get(dst string, u *url.URL) error {
25+
g.GetCalled = true
26+
g.GetDst = dst
27+
g.GetURL = u
28+
29+
if g.Proxy != nil {
30+
return g.Proxy.Get(dst, u)
31+
}
32+
33+
return g.GetErr
34+
}
35+
36+
func (g *MockGetter) GetFile(dst string, u *url.URL) error {
37+
g.GetFileCalled = true
38+
g.GetFileDst = dst
39+
g.GetFileURL = u
40+
41+
if g.Proxy != nil {
42+
return g.Proxy.GetFile(dst, u)
43+
}
44+
return g.GetFileErr
45+
}

get_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,105 @@ func TestGet_fileSubdir(t *testing.T) {
5959
t.Fatalf("err: %s", err)
6060
}
6161
}
62+
63+
func TestGetFile(t *testing.T) {
64+
dst := tempFile(t)
65+
u := testModule("basic-file/foo.txt")
66+
67+
if err := GetFile(dst, u); err != nil {
68+
t.Fatalf("err: %s", err)
69+
}
70+
71+
// Verify the main file exists
72+
assertContents(t, dst, "Hello\n")
73+
}
74+
75+
func TestGetFile_checksum(t *testing.T) {
76+
cases := []struct {
77+
Append string
78+
Err bool
79+
}{
80+
{
81+
"",
82+
false,
83+
},
84+
85+
// MD5
86+
{
87+
"?checksum=md5:09f7e02f1290be211da707a266f153b3",
88+
false,
89+
},
90+
{
91+
"?checksum=md5:09f7e02f1290be211da707a266f153b4",
92+
true,
93+
},
94+
95+
// SHA1
96+
{
97+
"?checksum=sha1:1d229271928d3f9e2bb0375bd6ce5db6c6d348d9",
98+
false,
99+
},
100+
{
101+
"?checksum=sha1:1d229271928d3f9e2bb0375bd6ce5db6c6d348d0",
102+
true,
103+
},
104+
105+
// SHA256
106+
{
107+
"?checksum=sha256:66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f18",
108+
false,
109+
},
110+
{
111+
"?checksum=sha256:66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f19",
112+
true,
113+
},
114+
115+
// SHA512
116+
{
117+
"?checksum=sha512:c2bad2223811194582af4d1508ac02cd69eeeeedeeb98d54fcae4dcefb13cc882e7640328206603d3fb9cd5f949a9be0db054dd34fbfa190c498a5fe09750cef",
118+
false,
119+
},
120+
{
121+
"?checksum=sha512:c2bad2223811194582af4d1508ac02cd69eeeeedeeb98d54fcae4dcefb13cc882e7640328206603d3fb9cd5f949a9be0db054dd34fbfa190c498a5fe09750ced",
122+
true,
123+
},
124+
}
125+
126+
for _, tc := range cases {
127+
u := testModule("basic-file/foo.txt") + tc.Append
128+
129+
func() {
130+
dst := tempFile(t)
131+
defer os.Remove(dst)
132+
if err := GetFile(dst, u); (err != nil) != tc.Err {
133+
t.Fatalf("append: %s\n\nerr: %s", tc.Append, err)
134+
}
135+
136+
// Verify the main file exists
137+
assertContents(t, dst, "Hello\n")
138+
}()
139+
}
140+
}
141+
142+
func TestGetFile_checksumURL(t *testing.T) {
143+
dst := tempFile(t)
144+
u := testModule("basic-file/foo.txt") + "?checksum=md5:09f7e02f1290be211da707a266f153b3"
145+
146+
getter := &MockGetter{Proxy: new(FileGetter)}
147+
client := &Client{
148+
Src: u,
149+
Dst: dst,
150+
Dir: false,
151+
Getters: map[string]Getter{
152+
"file": getter,
153+
},
154+
}
155+
156+
if err := client.Get(); err != nil {
157+
t.Fatalf("err: %s", err)
158+
}
159+
160+
if v := getter.GetFileURL.Query().Get("checksum"); v != "" {
161+
t.Fatalf("bad: %s", v)
162+
}
163+
}

0 commit comments

Comments
 (0)