Skip to content

Commit

Permalink
Merge pull request #118 from go-chef/afiune/cookbook-checksum
Browse files Browse the repository at this point in the history
Afiune/cookbook checksum
  • Loading branch information
MarkGibbons authored Dec 1, 2019
2 parents fbf9234 + 8a98980 commit 45ee6a0
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 13 deletions.
57 changes: 48 additions & 9 deletions cookbook_download.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package chef

import (
"crypto/md5"
"fmt"
"io"
"os"
"path"
Expand All @@ -17,11 +19,11 @@ func (c *CookbookService) Download(name, version string) error {
return err
}

return c.DownloadAt(name, version, cwd)
return c.DownloadTo(name, version, cwd)
}

// DownloadAt downloads a cookbook to the specified local directory on disk
func (c *CookbookService) DownloadAt(name, version, localDir string) error {
// DownloadTo downloads a cookbook to the specified local directory on disk
func (c *CookbookService) DownloadTo(name, version, localDir string) error {
// If the version is set to 'latest' or it is empty ("") then,
// we will set the version to '_latest' which is the default endpoint
if version == "" || version == "latest" {
Expand Down Expand Up @@ -60,6 +62,12 @@ func (c *CookbookService) DownloadAt(name, version, localDir string) error {
return nil
}

// DownloadAt is a deprecated alias for DownloadTo
func (c *CookbookService) DownloadAt(name, version, localDir string) error {
err := c.DownloadTo(name, version, localDir)
return err
}

// downloadCookbookItems downloads all the provided cookbook items into the provided
// local path, it also ensures that the provided directory exists by creating it
func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, localPath string) error {
Expand All @@ -73,8 +81,7 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType,
}

for _, item := range items {
itemPath := path.Join(localPath, item.Name)
if err := c.downloadCookbookFile(item.Url, itemPath); err != nil {
if err := c.downloadCookbookFile(item, localPath); err != nil {
return err
}
}
Expand All @@ -83,11 +90,14 @@ func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType,
}

// downloadCookbookFile downloads a single cookbook file to disk
func (c *CookbookService) downloadCookbookFile(url, file string) error {
request, err := c.client.NewRequest("GET", url, nil)
func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath string) error {
filePath := path.Join(localPath, item.Name)

request, err := c.client.NewRequest("GET", item.Url, nil)
if err != nil {
return err
}

response, err := c.client.Do(request, nil)
if response != nil {
defer response.Body.Close()
Expand All @@ -96,13 +106,42 @@ func (c *CookbookService) downloadCookbookFile(url, file string) error {
return err
}

f, err := os.Create(file)
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()

if _, err := io.Copy(f, response.Body); err != nil {
return err
}
return nil

if verifyMD5Checksum(filePath, item.Checksum) {
return nil
}

return fmt.Errorf(
"cookbook file '%s' checksum mismatch. (expected:%s)",
filePath,
item.Checksum,
)
}

func verifyMD5Checksum(filePath, checksum string) bool {
file, err := os.Open(filePath)
if err != nil {
return false
}
defer file.Close()

hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return false
}

md5String := fmt.Sprintf("%x", hash.Sum(nil))
if md5String == checksum {
return true
}
return false
}
107 changes: 103 additions & 4 deletions cookbook_download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestCookbooksDownloadEmptyWithVersion(t *testing.T) {
assert.Nil(t, err)
}

func TestCookbooksDownloadAt(t *testing.T) {
func TestCookbooksDownloadTo(t *testing.T) {
setup()
defer teardown()

Expand All @@ -93,7 +93,7 @@ func TestCookbooksDownloadAt(t *testing.T) {
{
"name": "default.rb",
"path": "recipes/default.rb",
"checksum": "320sdk2w38020827kdlsdkasbd5454b6",
"checksum": "8e751ed8663cb9b97499956b6a20b0de",
"specificity": "default",
"url": "` + server.URL + `/bookshelf/foo/default_rb"
}
Expand All @@ -103,7 +103,7 @@ func TestCookbooksDownloadAt(t *testing.T) {
{
"name": "metadata.rb",
"path": "metadata.rb",
"checksum": "14963c5b685f3a15ea90ae51bd5454b6",
"checksum": "6607f3131919e82dc4ba4c026fcfee9f",
"specificity": "default",
"url": "` + server.URL + `/bookshelf/foo/metadata_rb"
}
Expand All @@ -130,7 +130,7 @@ func TestCookbooksDownloadAt(t *testing.T) {
fmt.Fprintf(w, "log 'this is a resource'")
})

err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir)
err = client.Cookbooks.DownloadTo("foo", "0.2.1", tempDir)
assert.Nil(t, err)

var (
Expand All @@ -152,3 +152,102 @@ func TestCookbooksDownloadAt(t *testing.T) {
assert.Equal(t, "log 'this is a resource'", string(recipeBytes))
}
}

func TestCookbooksDownloadAt(t *testing.T) {
setup()
defer teardown()

mockedCookbookResponseFile := `
{
"version": "0.2.1",
"name": "foo-0.2.1",
"cookbook_name": "foo",
"frozen?": false,
"chef_type": "cookbook_version",
"json_class": "Chef::CookbookVersion",
"attributes": [],
"definitions": [],
"files": [],
"libraries": [],
"providers": [],
"recipes": [
{
"name": "default.rb",
"path": "recipes/default.rb",
"checksum": "8e751ed8663cb9b97499956b6a20b0de",
"specificity": "default",
"url": "` + server.URL + `/bookshelf/foo/default_rb"
}
],
"resources": [],
"root_files": [
{
"name": "metadata.rb",
"path": "metadata.rb",
"checksum": "6607f3131919e82dc4ba4c026fcfee9f",
"specificity": "default",
"url": "` + server.URL + `/bookshelf/foo/metadata_rb"
}
],
"templates": [],
"metadata": {},
"access": {}
}
`

tempDir, err := ioutil.TempDir("", "foo-cookbook")
if err != nil {
t.Error(err)
}
defer os.RemoveAll(tempDir) // clean up

mux.HandleFunc("/cookbooks/foo/0.2.1", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, string(mockedCookbookResponseFile))
})
mux.HandleFunc("/bookshelf/foo/metadata_rb", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "name 'foo'")
})
mux.HandleFunc("/bookshelf/foo/default_rb", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "log 'this is a resource'")
})

err = client.Cookbooks.DownloadAt("foo", "0.2.1", tempDir)
assert.Nil(t, err)

var (
cookbookPath = path.Join(tempDir, "foo-0.2.1")
metadataPath = path.Join(cookbookPath, "metadata.rb")
recipesPath = path.Join(cookbookPath, "recipes")
defaultPath = path.Join(recipesPath, "default.rb")
)
assert.DirExistsf(t, cookbookPath, "the cookbook directory should exist")
assert.DirExistsf(t, recipesPath, "the recipes directory should exist")
if assert.FileExistsf(t, metadataPath, "a metadata.rb file should exist") {
metadataBytes, err := ioutil.ReadFile(metadataPath)
assert.Nil(t, err)
assert.Equal(t, "name 'foo'", string(metadataBytes))
}
if assert.FileExistsf(t, defaultPath, "the default.rb recipes should exist") {
recipeBytes, err := ioutil.ReadFile(defaultPath)
assert.Nil(t, err)
assert.Equal(t, "log 'this is a resource'", string(recipeBytes))
}
}

func TestVerifyMD5Checksum(t *testing.T) {
tempDir, err := ioutil.TempDir("", "md5-test")
if err != nil {
t.Error(err)
}
defer os.RemoveAll(tempDir) // clean up

var (
// if someone changes the test data,
// you have to also update the below md5 sum
testData = []byte("hello\nchef\n")
filePath = path.Join(tempDir, "dat")
)
err = ioutil.WriteFile(filePath, testData, 0644)
assert.Nil(t, err)
assert.True(t, verifyMD5Checksum(filePath, "70bda176ac4db06f1f66f96ae0693be1"))
}

0 comments on commit 45ee6a0

Please sign in to comment.