/
step_download.go
220 lines (195 loc) · 6.27 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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package common
import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"
getter "github.com/hashicorp/go-getter/v2"
urlhelper "github.com/hashicorp/go-getter/v2/helper/url"
"github.com/hashicorp/packer/common/filelock"
"github.com/hashicorp/packer/helper/multistep"
"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
}
var defaultGetterClient = getter.Client{}
func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if len(s.Url) == 0 {
log.Printf("No URLs were provided to Step Download. Continuing...")
return multistep.ActionContinue
}
defer log.Printf("Leaving retrieve loop for %s", s.Description)
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Retrieving %s", s.Description))
var errs []error
for _, source := range s.Url {
if ctx.Err() != nil {
state.Put("error", fmt.Errorf("Download cancelled: %v", errs))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Trying %s", source))
var err error
var dst string
if s.Description == "OVF/OVA" && strings.HasSuffix(source, ".ovf") {
// TODO(adrien): make go-getter allow using files in place.
// ovf files usually point to a file in the same directory, so
// using them in place is the only way.
ui.Say(fmt.Sprintf("Using ovf inplace"))
dst = source
} else {
dst, err = s.download(ctx, ui, source)
}
if err == nil {
state.Put(s.ResultKey, dst)
return multistep.ActionContinue
}
// may be another url will work
errs = append(errs, err)
}
err := fmt.Errorf("error downloading %s: %v", s.Description, errs)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string) (string, error) {
if runtime.GOOS == "windows" {
// Check that the user specified a UNC path, and promote it to an smb:// uri.
if strings.HasPrefix(source, "\\\\") && len(source) > 2 && source[2] != '?' {
source = filepath.ToSlash(source[2:])
source = fmt.Sprintf("smb://%s", source)
}
}
u, err := urlhelper.Parse(source)
if err != nil {
return "", fmt.Errorf("url parse: %s", err)
}
if checksum := u.Query().Get("checksum"); checksum != "" {
s.Checksum = checksum
}
if s.ChecksumType != "" && s.ChecksumType != "none" {
// add checksum to url query params as go getter will checksum for us
q := u.Query()
q.Set("checksum", s.ChecksumType+":"+s.Checksum)
u.RawQuery = q.Encode()
} else if s.Checksum != "" {
q := u.Query()
q.Set("checksum", s.Checksum)
u.RawQuery = q.Encode()
}
// store file under sha1(hash) if set
// hash can sometimes be a checksum url
// otherwise, use sha1(source_url)
var shaSum [20]byte
if s.Checksum != "" {
shaSum = sha1.Sum([]byte(s.Checksum))
} else {
shaSum = sha1.Sum([]byte(u.String()))
}
shaSumString := hex.EncodeToString(shaSum[:])
targetPath := s.TargetPath
if targetPath == "" {
targetPath = shaSumString
if s.Extension != "" {
targetPath += "." + s.Extension
}
targetPath, err = packer.CachePath(targetPath)
if err != nil {
return "", fmt.Errorf("CachePath: %s", err)
}
} else if filepath.Ext(targetPath) == "" {
// When an absolute path is provided
// this adds the file to the targetPath
if !strings.HasSuffix(targetPath, "/") {
targetPath += "/"
}
targetPath += shaSumString
if s.Extension != "" {
targetPath += "." + s.Extension
} else {
targetPath += ".iso"
}
}
lockFile := targetPath + ".lock"
log.Printf("Acquiring lock for: %s (%s)", u.String(), lockFile)
lock := filelock.New(lockFile)
lock.Lock()
defer lock.Unlock()
wd, err := os.Getwd()
if err != nil {
log.Printf("get working directory: %v", err)
// here we ignore the error in case the
// working directory is not needed.
// It would be better if the go-getter
// could guess it only in cases it is
// necessary.
}
src := u.String()
if u.Scheme == "" || strings.ToLower(u.Scheme) == "file" {
// If a local filepath, then we need to preprocess to make sure the
// path doens't have any multiple successive path separators; if it
// does, go-getter will read this as a specialized go-getter-specific
// subdirectory command, which it most likely isn't.
src = filepath.Clean(u.String())
if _, err := os.Stat(filepath.Clean(u.Path)); err != nil {
// Cleaned path isn't present on system so it must be some other
// scheme. Don't error right away; see if go-getter can figure it
// out.
src = u.String()
}
}
ui.Say(fmt.Sprintf("Trying %s", u.String()))
req := &getter.Request{
Dst: targetPath,
Src: src,
ProgressListener: ui,
Pwd: wd,
Mode: getter.ModeFile,
Inplace: true,
}
switch op, err := defaultGetterClient.Get(ctx, req); err.(type) {
case nil: // success !
ui.Say(fmt.Sprintf("%s => %s", u.String(), op.Dst))
return op.Dst, nil
case *getter.ChecksumError:
ui.Say(fmt.Sprintf("Checksum did not match, removing %s", targetPath))
if err := os.Remove(targetPath); err != nil {
ui.Error(fmt.Sprintf("Failed to remove cache file. Please remove manually: %s", targetPath))
}
return "", err
default:
ui.Say(fmt.Sprintf("Download failed %s", err))
return "", err
}
}
func (s *StepDownload) Cleanup(multistep.StateBag) {}