forked from hashicorp/packer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
step_download.go
175 lines (145 loc) · 4.35 KB
/
step_download.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package common
import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"log"
"time"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/helper/useragent"
"github.com/hashicorp/packer/packer"
)
// StepDownload downloads a remote file using the download client within
// this package. This step handles setting up the download configuration,
// progress reporting, interrupt handling, etc.
//
// Uses:
// cache packer.Cache
// ui packer.Ui
type StepDownload struct {
// The checksum and the type of the checksum for the download
Checksum string
ChecksumType string
// A short description of the type of download being done. Example:
// "ISO" or "Guest Additions"
Description string
// The name of the key where the final path of the ISO will be put
// into the state.
ResultKey string
// The path where the result should go, otherwise it goes to the
// cache directory.
TargetPath string
// A list of URLs to attempt to download this thing.
Url []string
// Extension is the extension to force for the file that is downloaded.
// Some systems require a certain extension. If this isn't set, the
// extension on the URL is used. Otherwise, this will be forced
// on the downloaded file for every URL.
Extension string
}
func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
cache := state.Get("cache").(packer.Cache)
ui := state.Get("ui").(packer.Ui)
var checksum []byte
if s.Checksum != "" {
var err error
checksum, err = hex.DecodeString(s.Checksum)
if err != nil {
state.Put("error", fmt.Errorf("Error parsing checksum: %s", err))
return multistep.ActionHalt
}
}
ui.Say(fmt.Sprintf("Retrieving %s", s.Description))
// First try to use any already downloaded file
// If it fails, proceed to regular download logic
var downloadConfigs = make([]*DownloadConfig, len(s.Url))
var finalPath string
for i, url := range s.Url {
targetPath := s.TargetPath
if targetPath == "" {
// Determine a cache key. This is normally just the URL but
// if we force a certain extension we hash the URL and add
// the extension to force it.
cacheKey := url
if s.Extension != "" {
hash := sha1.Sum([]byte(url))
cacheKey = fmt.Sprintf(
"%s.%s", hex.EncodeToString(hash[:]), s.Extension)
}
log.Printf("Acquiring lock to download: %s", url)
targetPath = cache.Lock(cacheKey)
defer cache.Unlock(cacheKey)
}
config := &DownloadConfig{
Url: url,
TargetPath: targetPath,
CopyFile: false,
Hash: HashForType(s.ChecksumType),
Checksum: checksum,
UserAgent: useragent.String(),
}
downloadConfigs[i] = config
if match, _ := NewDownloadClient(config, ui).VerifyChecksum(config.TargetPath); match {
ui.Message(fmt.Sprintf("Found already downloaded, initial checksum matched, no download needed: %s", url))
finalPath = config.TargetPath
break
}
}
if finalPath == "" {
for i := range s.Url {
config := downloadConfigs[i]
path, err, retry := s.download(config, state)
if err != nil {
ui.Message(fmt.Sprintf("Error downloading: %s", err))
}
if !retry {
return multistep.ActionHalt
}
if err == nil {
finalPath = path
break
}
}
}
if finalPath == "" {
err := fmt.Errorf("%s download failed.", s.Description)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put(s.ResultKey, finalPath)
return multistep.ActionContinue
}
func (s *StepDownload) Cleanup(multistep.StateBag) {}
func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) {
var path string
ui := state.Get("ui").(packer.Ui)
// Create download client with config
download := NewDownloadClient(config, ui)
downloadCompleteCh := make(chan error, 1)
go func() {
var err error
path, err = download.Get()
downloadCompleteCh <- err
}()
for {
select {
case err := <-downloadCompleteCh:
if err != nil {
return "", err, true
}
if download.config.CopyFile {
ui.Message(fmt.Sprintf("Transferred: %s", config.Url))
} else {
ui.Message(fmt.Sprintf("Using file in-place: %s", config.Url))
}
return path, nil, true
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
ui.Say("Interrupt received. Cancelling download...")
return "", nil, false
}
}
}
}