~juju-qa/ubuntu/xenial/juju/2.0-rc2

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/downloader/download.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
 
12
12
        "github.com/juju/errors"
13
13
        "github.com/juju/utils"
14
 
        "gopkg.in/tomb.v1"
15
14
)
16
15
 
17
16
// Request holds a single download request.
27
26
        // the download is invalid then the func must return errors.NotValid.
28
27
        // If no func is provided then no verification happens.
29
28
        Verify func(*os.File) error
 
29
 
 
30
        // Abort is a channel that will cancel the download when it is closed.
 
31
        Abort <-chan struct{}
30
32
}
31
33
 
32
34
// Status represents the status of a completed download.
33
35
type Status struct {
34
 
        // File holds the downloaded data on success.
35
 
        File *os.File
 
36
        // Filename is the name of the file which holds the downloaded
 
37
        // data on success.
 
38
        Filename string
36
39
 
37
40
        // Err describes any error encountered while downloading.
38
41
        Err error
39
42
}
40
43
 
 
44
// StartDownload starts a new download as specified by `req` using
 
45
// `openBlob` to actually pull the remote data.
 
46
func StartDownload(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
 
47
        if openBlob == nil {
 
48
                openBlob = NewHTTPBlobOpener(utils.NoVerifySSLHostnames)
 
49
        }
 
50
        dl := &Download{
 
51
                done:     make(chan Status, 1),
 
52
                openBlob: openBlob,
 
53
        }
 
54
        go dl.run(req)
 
55
        return dl
 
56
}
 
57
 
41
58
// Download can download a file from the network.
42
59
type Download struct {
43
 
        tomb     tomb.Tomb
44
60
        done     chan Status
45
61
        openBlob func(*url.URL) (io.ReadCloser, error)
46
62
}
47
63
 
48
 
// StartDownload returns a new Download instance based on the provided
49
 
// request. openBlob is used to gain access to the blob, whether through
50
 
// an HTTP request or some other means.
51
 
func StartDownload(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
52
 
        dl := newDownload(openBlob)
53
 
        go dl.run(req)
54
 
        return dl
55
 
}
56
 
 
57
 
func newDownload(openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
58
 
        if openBlob == nil {
59
 
                openBlob = NewHTTPBlobOpener(utils.NoVerifySSLHostnames)
60
 
        }
61
 
        return &Download{
62
 
                done:     make(chan Status),
63
 
                openBlob: openBlob,
64
 
        }
65
 
}
66
 
 
67
 
// Stop stops any download that's in progress.
68
 
func (dl *Download) Stop() {
69
 
        dl.tomb.Kill(nil)
70
 
        dl.tomb.Wait()
71
 
}
72
 
 
73
64
// Done returns a channel that receives a status when the download has
74
 
// completed.  It is the receiver's responsibility to close and remove
75
 
// the received file.
 
65
// completed or is aborted. Exactly one Status value will be sent for
 
66
// each download once it finishes (successfully or otherwise) or is
 
67
// aborted.
 
68
//
 
69
// It is the receiver's responsibility to handle and remove the
 
70
// downloaded file.
76
71
func (dl *Download) Done() <-chan Status {
77
72
        return dl.done
78
73
}
79
74
 
80
 
// Wait blocks until the download completes or the abort channel receives.
81
 
func (dl *Download) Wait(abort <-chan struct{}) (*os.File, error) {
82
 
        defer dl.Stop()
83
 
 
84
 
        select {
85
 
        case <-abort:
86
 
                logger.Infof("download aborted")
87
 
                return nil, errors.New("aborted")
88
 
        case status := <-dl.Done():
89
 
                if status.Err != nil {
90
 
                        if status.File != nil {
91
 
                                if err := status.File.Close(); err != nil {
92
 
                                        logger.Errorf("failed to close file: %v", err)
93
 
                                }
94
 
                        }
95
 
                        return nil, errors.Trace(status.Err)
96
 
                }
97
 
                return status.File, nil
98
 
        }
 
75
// Wait blocks until the download finishes (successfully or
 
76
// otherwise), or the download is aborted. There will only be a
 
77
// filename if err is nil.
 
78
func (dl *Download) Wait() (string, error) {
 
79
        // No select required here because each download will always
 
80
        // return a value once it completes. Downloads can be aborted via
 
81
        // the Abort channel provided a creation time.
 
82
        status := <-dl.Done()
 
83
        return status.Filename, errors.Trace(status.Err)
99
84
}
100
85
 
101
86
func (dl *Download) run(req Request) {
102
 
        defer dl.tomb.Done()
103
 
 
104
87
        // TODO(dimitern) 2013-10-03 bug #1234715
105
88
        // Add a testing HTTPS storage to verify the
106
89
        // disableSSLHostnameVerification behavior here.
107
 
        file, err := download(req, dl.openBlob)
 
90
        filename, err := dl.download(req)
108
91
        if err != nil {
109
 
                err = errors.Annotatef(err, "cannot download %q", req.URL)
110
 
        }
111
 
 
112
 
        if err == nil {
 
92
                err = errors.Trace(err)
 
93
        } else {
113
94
                logger.Infof("download complete (%q)", req.URL)
114
 
                if req.Verify != nil {
115
 
                        err = verifyDownload(file, req)
116
 
                }
117
 
        }
118
 
 
119
 
        status := Status{
120
 
                File: file,
121
 
                Err:  err,
122
 
        }
123
 
        select {
124
 
        case dl.done <- status:
125
 
                // no-op
126
 
        case <-dl.tomb.Dying():
127
 
                cleanTempFile(file)
128
 
        }
129
 
}
130
 
 
131
 
func verifyDownload(file *os.File, req Request) error {
132
 
        err := req.Verify(file)
133
 
        if err != nil {
134
 
                if errors.IsNotValid(err) {
135
 
                        logger.Errorf("download of %s invalid: %v", req.URL, err)
136
 
                }
137
 
                return errors.Trace(err)
138
 
        }
139
 
        logger.Infof("download verified (%q)", req.URL)
140
 
 
141
 
        if _, err := file.Seek(0, os.SEEK_SET); err != nil {
142
 
                logger.Errorf("failed to seek to beginning of file: %v", err)
143
 
                return errors.Trace(err)
144
 
        }
145
 
        return nil
146
 
}
147
 
 
148
 
func download(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) (file *os.File, err error) {
 
95
                err = verifyDownload(filename, req)
 
96
                if err != nil {
 
97
                        os.Remove(filename)
 
98
                        filename = ""
 
99
                }
 
100
        }
 
101
 
 
102
        // No select needed here because the channel has a size of 1 and
 
103
        // will only be written to once.
 
104
        dl.done <- Status{
 
105
                Filename: filename,
 
106
                Err:      err,
 
107
        }
 
108
}
 
109
 
 
110
func (dl *Download) download(req Request) (filename string, err error) {
149
111
        logger.Infof("downloading from %s", req.URL)
150
112
 
151
113
        dir := req.TargetDir
154
116
        }
155
117
        tempFile, err := ioutil.TempFile(dir, "inprogress-")
156
118
        if err != nil {
157
 
                return nil, errors.Trace(err)
 
119
                return "", errors.Trace(err)
158
120
        }
159
121
        defer func() {
 
122
                tempFile.Close()
160
123
                if err != nil {
161
 
                        cleanTempFile(tempFile)
 
124
                        os.Remove(tempFile.Name())
162
125
                }
163
126
        }()
164
127
 
165
 
        reader, err := openBlob(req.URL)
 
128
        blobReader, err := dl.openBlob(req.URL)
166
129
        if err != nil {
167
 
                return nil, errors.Trace(err)
 
130
                return "", errors.Trace(err)
168
131
        }
169
 
        defer reader.Close()
 
132
        defer blobReader.Close()
170
133
 
 
134
        reader := &abortableReader{blobReader, req.Abort}
171
135
        _, err = io.Copy(tempFile, reader)
172
136
        if err != nil {
173
 
                return nil, errors.Trace(err)
174
 
        }
175
 
        if _, err := tempFile.Seek(0, 0); err != nil {
176
 
                return nil, errors.Trace(err)
177
 
        }
178
 
        return tempFile, nil
179
 
}
180
 
 
181
 
func cleanTempFile(f *os.File) {
182
 
        if f == nil {
183
 
                return
184
 
        }
185
 
 
186
 
        f.Close()
187
 
        if err := os.Remove(f.Name()); err != nil {
188
 
                logger.Errorf("cannot remove temp file %q: %v", f.Name(), err)
189
 
        }
 
137
                return "", errors.Trace(err)
 
138
        }
 
139
 
 
140
        return tempFile.Name(), nil
 
141
}
 
142
 
 
143
// abortableReader wraps a Reader, returning an error from Read calls
 
144
// if the abort channel provided is closed.
 
145
type abortableReader struct {
 
146
        r     io.Reader
 
147
        abort <-chan struct{}
 
148
}
 
149
 
 
150
// Read implements io.Reader.
 
151
func (ar *abortableReader) Read(p []byte) (int, error) {
 
152
        select {
 
153
        case <-ar.abort:
 
154
                return 0, errors.New("download aborted")
 
155
        default:
 
156
        }
 
157
        return ar.r.Read(p)
 
158
}
 
159
 
 
160
func verifyDownload(filename string, req Request) error {
 
161
        if req.Verify == nil {
 
162
                return nil
 
163
        }
 
164
 
 
165
        file, err := os.Open(filename)
 
166
        if err != nil {
 
167
                return errors.Annotate(err, "opening for verify")
 
168
        }
 
169
        defer file.Close()
 
170
 
 
171
        if err := req.Verify(file); err != nil {
 
172
                return errors.Trace(err)
 
173
        }
 
174
        logger.Infof("download verified (%q)", req.URL)
 
175
        return nil
190
176
}