github.com/go-chef/chef@v0.30.1/cookbook_download.go (about) 1 // 2 // Author:: Salim Afiune <afiune@chef.io> 3 // 4 5 package chef 6 7 import ( 8 "crypto/md5" 9 "fmt" 10 "io" 11 "os" 12 "path" 13 ) 14 15 // Download downloads a cookbook to the current directory on disk 16 func (c *CookbookService) Download(name, version string) error { 17 cwd, err := os.Getwd() 18 if err != nil { 19 return err 20 } 21 22 return c.DownloadTo(name, version, cwd) 23 } 24 25 // DownloadTo downloads a cookbook to the specified local directory on disk 26 func (c *CookbookService) DownloadTo(name, version, localDir string) error { 27 // If the version is set to 'latest' or it is empty ("") then, 28 // we will set the version to '_latest' which is the default endpoint 29 if version == "" || version == "latest" { 30 version = "_latest" 31 } 32 33 cookbook, err := c.GetVersion(name, version) 34 if err != nil { 35 return err 36 } 37 38 debug("Downloading %s cookbook version %s\n", cookbook.CookbookName, cookbook.Version) 39 40 // We use 'cookbook.Name' since it returns the string '{NAME}-{VERSION}'. Ex: 'apache-0.1.0' 41 cookbookPath := path.Join(localDir, cookbook.Name) 42 43 downloadErrs := []error{ 44 c.downloadCookbookItems(cookbook.RootFiles, "root_files", cookbookPath), 45 c.downloadCookbookItems(cookbook.Files, "files", path.Join(cookbookPath, "files")), 46 c.downloadCookbookItems(cookbook.Templates, "templates", path.Join(cookbookPath, "templates")), 47 c.downloadCookbookItems(cookbook.Attributes, "attributes", path.Join(cookbookPath, "attributes")), 48 c.downloadCookbookItems(cookbook.Recipes, "recipes", path.Join(cookbookPath, "recipes")), 49 c.downloadCookbookItems(cookbook.Definitions, "definitions", path.Join(cookbookPath, "definitions")), 50 c.downloadCookbookItems(cookbook.Libraries, "libraries", path.Join(cookbookPath, "libraries")), 51 c.downloadCookbookItems(cookbook.Providers, "providers", path.Join(cookbookPath, "providers")), 52 c.downloadCookbookItems(cookbook.Resources, "resources", path.Join(cookbookPath, "resources")), 53 } 54 55 for _, err := range downloadErrs { 56 if err != nil { 57 return err 58 } 59 } 60 61 debug("Cookbook downloaded to %s\n", cookbookPath) 62 return nil 63 } 64 65 // DownloadAt is a deprecated alias for DownloadTo 66 func (c *CookbookService) DownloadAt(name, version, localDir string) error { 67 err := c.DownloadTo(name, version, localDir) 68 return err 69 } 70 71 // downloadCookbookItems downloads all the provided cookbook items into the provided 72 // local path, it also ensures that the provided directory exists by creating it 73 func (c *CookbookService) downloadCookbookItems(items []CookbookItem, itemType, localPath string) error { 74 if len(items) == 0 { 75 return nil 76 } 77 78 debug("Downloading %s\n", itemType) 79 if err := os.MkdirAll(localPath, 0755); err != nil { 80 return err 81 } 82 83 for _, item := range items { 84 if err := c.downloadCookbookFile(item, localPath); err != nil { 85 return err 86 } 87 } 88 89 return nil 90 } 91 92 // downloadCookbookFile downloads a single cookbook file to disk 93 func (c *CookbookService) downloadCookbookFile(item CookbookItem, localPath string) error { 94 filePath := path.Join(localPath, item.Name) 95 96 // First check and see if the file is already there - if it is and the checksum 97 // matches, there's no need to redownload it. 98 if verifyMD5Checksum(filePath, item.Checksum) { 99 return nil 100 } 101 102 request, err := c.client.NewRequest("GET", item.Url, nil) 103 if err != nil { 104 return err 105 } 106 107 response, err := c.client.Do(request, nil) 108 if response != nil { 109 defer response.Body.Close() 110 } 111 if err != nil { 112 return err 113 } 114 115 f, err := os.Create(filePath) 116 if err != nil { 117 return err 118 } 119 defer f.Close() 120 121 if _, err := io.Copy(f, response.Body); err != nil { 122 return err 123 } 124 125 if verifyMD5Checksum(filePath, item.Checksum) { 126 return nil 127 } 128 129 return fmt.Errorf( 130 "cookbook file '%s' checksum mismatch. (expected:%s)", 131 filePath, 132 item.Checksum, 133 ) 134 } 135 136 func verifyMD5Checksum(filePath, checksum string) bool { 137 file, err := os.Open(filePath) 138 if err != nil { 139 return false 140 } 141 defer file.Close() 142 143 hash := md5.New() 144 if _, err := io.Copy(hash, file); err != nil { 145 return false 146 } 147 148 md5String := fmt.Sprintf("%x", hash.Sum(nil)) 149 if md5String == checksum { 150 return true 151 } 152 return false 153 }