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 }