github.com/HashDataInc/packer@v1.3.2/common/step_download.go (about)

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