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  }