github.com/homburg/packer@v0.6.1-0.20140528012651-1dcaf1716848/common/step_download.go (about)

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