github.com/jerryclinesmith/packer@v0.3.7/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  		}
    74  
    75  		path, err, retry := s.download(config, state)
    76  		if err != nil {
    77  			ui.Message(fmt.Sprintf("Error downloading: %s", err))
    78  		}
    79  
    80  		if !retry {
    81  			return multistep.ActionHalt
    82  		}
    83  
    84  		if err == nil {
    85  			finalPath = path
    86  			break
    87  		}
    88  	}
    89  
    90  	if finalPath == "" {
    91  		err := fmt.Errorf("%s download failed.", s.Description)
    92  		state.Put("error", err)
    93  		ui.Error(err.Error())
    94  		return multistep.ActionHalt
    95  	}
    96  
    97  	state.Put(s.ResultKey, finalPath)
    98  	return multistep.ActionContinue
    99  }
   100  
   101  func (s *StepDownload) Cleanup(multistep.StateBag) {}
   102  
   103  func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) {
   104  	var path string
   105  	ui := state.Get("ui").(packer.Ui)
   106  	download := NewDownloadClient(config)
   107  
   108  	downloadCompleteCh := make(chan error, 1)
   109  	go func() {
   110  		var err error
   111  		path, err = download.Get()
   112  		downloadCompleteCh <- err
   113  	}()
   114  
   115  	progressTicker := time.NewTicker(5 * time.Second)
   116  	defer progressTicker.Stop()
   117  
   118  	for {
   119  		select {
   120  		case err := <-downloadCompleteCh:
   121  			if err != nil {
   122  				return "", err, true
   123  			}
   124  
   125  			return path, nil, true
   126  		case <-progressTicker.C:
   127  			progress := download.PercentProgress()
   128  			if progress >= 0 {
   129  				ui.Message(fmt.Sprintf("Download progress: %d%%", progress))
   130  			}
   131  		case <-time.After(1 * time.Second):
   132  			if _, ok := state.GetOk(multistep.StateCancelled); ok {
   133  				ui.Say("Interrupt received. Cancelling download...")
   134  				return "", nil, false
   135  			}
   136  		}
   137  	}
   138  }