github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/common/step_download.go (about) 1 package common 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/mitchellh/multistep" 10 "github.com/mitchellh/packer/packer" 11 ) 12 13 // StepDownload downloads a remote file using the download client within 14 // this package. This step handles setting up the download configuration, 15 // progress reporting, interrupt handling, etc. 16 // 17 // Uses: 18 // cache packer.Cache 19 // ui packer.Ui 20 type StepDownload struct { 21 // The checksum and the type of the checksum for the download 22 Checksum string 23 ChecksumType string 24 25 // A short description of the type of download being done. Example: 26 // "ISO" or "Guest Additions" 27 Description string 28 29 // The name of the key where the final path of the ISO will be put 30 // into the state. 31 ResultKey string 32 33 // The path where the result should go, otherwise it goes to the 34 // cache directory. 35 TargetPath string 36 37 // A list of URLs to attempt to download this thing. 38 Url []string 39 } 40 41 func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction { 42 cache := state.Get("cache").(packer.Cache) 43 ui := state.Get("ui").(packer.Ui) 44 45 var checksum []byte 46 if s.Checksum != "" { 47 var err error 48 checksum, err = hex.DecodeString(s.Checksum) 49 if err != nil { 50 state.Put("error", fmt.Errorf("Error parsing checksum: %s", err)) 51 return multistep.ActionHalt 52 } 53 } 54 55 ui.Say(fmt.Sprintf("Downloading or copying %s", s.Description)) 56 57 var finalPath string 58 for _, url := range s.Url { 59 ui.Message(fmt.Sprintf("Downloading or copying: %s", url)) 60 61 targetPath := s.TargetPath 62 if targetPath == "" { 63 log.Printf("Acquiring lock to download: %s", url) 64 targetPath = cache.Lock(url) 65 defer cache.Unlock(url) 66 } 67 68 config := &DownloadConfig{ 69 Url: url, 70 TargetPath: targetPath, 71 CopyFile: false, 72 Hash: HashForType(s.ChecksumType), 73 Checksum: checksum, 74 UserAgent: "Packer", 75 } 76 77 path, err, retry := s.download(config, state) 78 if err != nil { 79 ui.Message(fmt.Sprintf("Error downloading: %s", err)) 80 } 81 82 if !retry { 83 return multistep.ActionHalt 84 } 85 86 if err == nil { 87 finalPath = path 88 break 89 } 90 } 91 92 if finalPath == "" { 93 err := fmt.Errorf("%s download failed.", s.Description) 94 state.Put("error", err) 95 ui.Error(err.Error()) 96 return multistep.ActionHalt 97 } 98 99 state.Put(s.ResultKey, finalPath) 100 return multistep.ActionContinue 101 } 102 103 func (s *StepDownload) Cleanup(multistep.StateBag) {} 104 105 func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) { 106 var path string 107 ui := state.Get("ui").(packer.Ui) 108 download := NewDownloadClient(config) 109 110 downloadCompleteCh := make(chan error, 1) 111 go func() { 112 var err error 113 path, err = download.Get() 114 downloadCompleteCh <- err 115 }() 116 117 progressTicker := time.NewTicker(5 * time.Second) 118 defer progressTicker.Stop() 119 120 for { 121 select { 122 case err := <-downloadCompleteCh: 123 if err != nil { 124 return "", err, true 125 } 126 127 return path, nil, true 128 case <-progressTicker.C: 129 progress := download.PercentProgress() 130 if progress >= 0 { 131 ui.Message(fmt.Sprintf("Download progress: %d%%", progress)) 132 } 133 case <-time.After(1 * time.Second): 134 if _, ok := state.GetOk(multistep.StateCancelled); ok { 135 ui.Say("Interrupt received. Cancelling download...") 136 return "", nil, false 137 } 138 } 139 } 140 }