github.com/raghuse92/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 }