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