github.com/ttysteale/packer@v0.8.2-0.20150708160520-e5f8ea386ed8/common/step_download.go (about)

     1  package common
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"log"
     8  	"time"
     9  
    10  	"github.com/mitchellh/multistep"
    11  	"github.com/mitchellh/packer/packer"
    12  )
    13  
    14  // StepDownload downloads a remote file using the download client within
    15  // this package. This step handles setting up the download configuration,
    16  // progress reporting, interrupt handling, etc.
    17  //
    18  // Uses:
    19  //   cache packer.Cache
    20  //   ui    packer.Ui
    21  type StepDownload struct {
    22  	// The checksum and the type of the checksum for the download
    23  	Checksum     string
    24  	ChecksumType string
    25  
    26  	// A short description of the type of download being done. Example:
    27  	// "ISO" or "Guest Additions"
    28  	Description string
    29  
    30  	// The name of the key where the final path of the ISO will be put
    31  	// into the state.
    32  	ResultKey string
    33  
    34  	// The path where the result should go, otherwise it goes to the
    35  	// cache directory.
    36  	TargetPath string
    37  
    38  	// A list of URLs to attempt to download this thing.
    39  	Url []string
    40  
    41  	// Extension is the extension to force for the file that is downloaded.
    42  	// Some systems require a certain extension. If this isn't set, the
    43  	// extension on the URL is used. Otherwise, this will be forced
    44  	// on the downloaded file for every URL.
    45  	Extension string
    46  }
    47  
    48  func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
    49  	cache := state.Get("cache").(packer.Cache)
    50  	ui := state.Get("ui").(packer.Ui)
    51  
    52  	var checksum []byte
    53  	if s.Checksum != "" {
    54  		var err error
    55  		checksum, err = hex.DecodeString(s.Checksum)
    56  		if err != nil {
    57  			state.Put("error", fmt.Errorf("Error parsing checksum: %s", err))
    58  			return multistep.ActionHalt
    59  		}
    60  	}
    61  
    62  	ui.Say(fmt.Sprintf("Downloading or copying %s", s.Description))
    63  
    64  	var finalPath string
    65  	for _, url := range s.Url {
    66  		ui.Message(fmt.Sprintf("Downloading or copying: %s", url))
    67  
    68  		targetPath := s.TargetPath
    69  		if targetPath == "" {
    70  			// Determine a cache key. This is normally just the URL but
    71  			// if we force a certain extension we hash the URL and add
    72  			// the extension to force it.
    73  			cacheKey := url
    74  			if s.Extension != "" {
    75  				hash := sha1.Sum([]byte(url))
    76  				cacheKey = fmt.Sprintf(
    77  					"%s.%s", hex.EncodeToString(hash[:]), s.Extension)
    78  			}
    79  
    80  			log.Printf("Acquiring lock to download: %s", url)
    81  			targetPath = cache.Lock(cacheKey)
    82  			defer cache.Unlock(cacheKey)
    83  		}
    84  
    85  		config := &DownloadConfig{
    86  			Url:        url,
    87  			TargetPath: targetPath,
    88  			CopyFile:   false,
    89  			Hash:       HashForType(s.ChecksumType),
    90  			Checksum:   checksum,
    91  			UserAgent:  "Packer",
    92  		}
    93  
    94  		path, err, retry := s.download(config, state)
    95  		if err != nil {
    96  			ui.Message(fmt.Sprintf("Error downloading: %s", err))
    97  		}
    98  
    99  		if !retry {
   100  			return multistep.ActionHalt
   101  		}
   102  
   103  		if err == nil {
   104  			finalPath = path
   105  			break
   106  		}
   107  	}
   108  
   109  	if finalPath == "" {
   110  		err := fmt.Errorf("%s download failed.", s.Description)
   111  		state.Put("error", err)
   112  		ui.Error(err.Error())
   113  		return multistep.ActionHalt
   114  	}
   115  
   116  	state.Put(s.ResultKey, finalPath)
   117  	return multistep.ActionContinue
   118  }
   119  
   120  func (s *StepDownload) Cleanup(multistep.StateBag) {}
   121  
   122  func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) {
   123  	var path string
   124  	ui := state.Get("ui").(packer.Ui)
   125  	download := NewDownloadClient(config)
   126  
   127  	downloadCompleteCh := make(chan error, 1)
   128  	go func() {
   129  		var err error
   130  		path, err = download.Get()
   131  		downloadCompleteCh <- err
   132  	}()
   133  
   134  	progressTicker := time.NewTicker(5 * time.Second)
   135  	defer progressTicker.Stop()
   136  
   137  	for {
   138  		select {
   139  		case err := <-downloadCompleteCh:
   140  			if err != nil {
   141  				return "", err, true
   142  			}
   143  
   144  			return path, nil, true
   145  		case <-progressTicker.C:
   146  			progress := download.PercentProgress()
   147  			if progress >= 0 {
   148  				ui.Message(fmt.Sprintf("Download progress: %d%%", progress))
   149  			}
   150  		case <-time.After(1 * time.Second):
   151  			if _, ok := state.GetOk(multistep.StateCancelled); ok {
   152  				ui.Say("Interrupt received. Cancelling download...")
   153  				return "", nil, false
   154  			}
   155  		}
   156  	}
   157  }