github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/common/step_download.go (about)

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