github.com/emate/packer@v0.8.1-0.20150625195101-fe0fde195dc6/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/mitchellh/multistep" 11 "github.com/mitchellh/packer/packer" 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 var finalPath string 65 for _, url := range s.Url { 66 ui.Message(fmt.Sprintf("Downloading or copying: %s", url)) 67 68 targetPath := s.TargetPath 69 if targetPath == "" { 70 // Determine a cache key. This is normally just the URL but 71 // if we force a certain extension we hash the URL and add 72 // the extension to force it. 73 cacheKey := url 74 if s.Extension != "" { 75 hash := sha1.Sum([]byte(url)) 76 cacheKey = fmt.Sprintf( 77 "%s.%s", hex.EncodeToString(hash[:]), s.Extension) 78 } 79 80 log.Printf("Acquiring lock to download: %s", url) 81 targetPath = cache.Lock(cacheKey) 82 defer cache.Unlock(cacheKey) 83 } 84 85 config := &DownloadConfig{ 86 Url: url, 87 TargetPath: targetPath, 88 CopyFile: false, 89 Hash: HashForType(s.ChecksumType), 90 Checksum: checksum, 91 UserAgent: "Packer", 92 } 93 94 path, err, retry := s.download(config, state) 95 if err != nil { 96 ui.Message(fmt.Sprintf("Error downloading: %s", err)) 97 } 98 99 if !retry { 100 return multistep.ActionHalt 101 } 102 103 if err == nil { 104 finalPath = path 105 break 106 } 107 } 108 109 if finalPath == "" { 110 err := fmt.Errorf("%s download failed.", s.Description) 111 state.Put("error", err) 112 ui.Error(err.Error()) 113 return multistep.ActionHalt 114 } 115 116 state.Put(s.ResultKey, finalPath) 117 return multistep.ActionContinue 118 } 119 120 func (s *StepDownload) Cleanup(multistep.StateBag) {} 121 122 func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) { 123 var path string 124 ui := state.Get("ui").(packer.Ui) 125 download := NewDownloadClient(config) 126 127 downloadCompleteCh := make(chan error, 1) 128 go func() { 129 var err error 130 path, err = download.Get() 131 downloadCompleteCh <- err 132 }() 133 134 progressTicker := time.NewTicker(5 * time.Second) 135 defer progressTicker.Stop() 136 137 for { 138 select { 139 case err := <-downloadCompleteCh: 140 if err != nil { 141 return "", err, true 142 } 143 144 return path, nil, true 145 case <-progressTicker.C: 146 progress := download.PercentProgress() 147 if progress >= 0 { 148 ui.Message(fmt.Sprintf("Download progress: %d%%", progress)) 149 } 150 case <-time.After(1 * time.Second): 151 if _, ok := state.GetOk(multistep.StateCancelled); ok { 152 ui.Say("Interrupt received. Cancelling download...") 153 return "", nil, false 154 } 155 } 156 } 157 }