github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/image/httpops.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package image 16 17 import ( 18 "errors" 19 "fmt" 20 "net/http" 21 "net/url" 22 "os" 23 24 "github.com/hashicorp/errwrap" 25 "github.com/rkt/rkt/rkt/config" 26 "github.com/rkt/rkt/store/imagestore" 27 ) 28 29 // httpOps is a kind of facade around a downloader and a 30 // resumableSession. It provides some higher-level functions for 31 // fetching images and signature keys. It also is a provider of a 32 // remote fetcher for asc. 33 type httpOps struct { 34 InsecureSkipTLSVerify bool 35 S *imagestore.Store 36 Headers map[string]config.Headerer 37 Debug bool 38 } 39 40 // DownloadSignature takes an asc instance and tries to get the 41 // signature. If the remote server asked to to defer the download, 42 // this function will return true and no error and no file. 43 func (o *httpOps) DownloadSignature(a *asc) (readSeekCloser, bool, error) { 44 ensureLogger(o.Debug) 45 diag.Printf("downloading signature from %v", a.Location) 46 ascFile, err := a.Get() 47 if err == nil { 48 return ascFile, false, nil 49 } 50 if _, ok := err.(*statusAcceptedError); ok { 51 log.Printf("server requested deferring the signature download") 52 return NopReadSeekCloser(nil), true, nil 53 } 54 return nil, false, errwrap.Wrap(errors.New("error downloading the signature file"), err) 55 } 56 57 // DownloadSignatureAgain does a similar thing to DownloadSignature, 58 // but it expects the signature to be actually provided, that is - no 59 // deferring this time. 60 func (o *httpOps) DownloadSignatureAgain(a *asc) (readSeekCloser, error) { 61 ensureLogger(o.Debug) 62 ascFile, retry, err := o.DownloadSignature(a) 63 if err != nil { 64 return nil, err 65 } 66 if retry { 67 return nil, fmt.Errorf("error downloading the signature file: server asked to defer the download again") 68 } 69 return ascFile, nil 70 } 71 72 // DownloadImage download the image, duh. It expects to actually 73 // receive the file, instead of being asked to use the cached version. 74 func (o *httpOps) DownloadImage(u *url.URL) (readSeekCloser, *cacheData, error) { 75 ensureLogger(o.Debug) 76 image, cd, err := o.DownloadImageWithETag(u, "") 77 if err != nil { 78 return nil, nil, err 79 } 80 if cd.UseCached { 81 return nil, nil, fmt.Errorf("asked to use cached image even if not asked for that") 82 } 83 return image, cd, nil 84 } 85 86 // DownloadImageWithETag might download an image or tell you to use 87 // the cached image. In the latter case the returned file will be nil. 88 func (o *httpOps) DownloadImageWithETag(u *url.URL, etag string) (readSeekCloser, *cacheData, error) { 89 var aciFile *removeOnClose // closed on error 90 var errClose error // error signaling to close aciFile 91 92 ensureLogger(o.Debug) 93 aciFile, err := getTmpROC(o.S, u.String()) 94 if err != nil { 95 return nil, nil, err 96 } 97 98 defer func() { 99 if errClose != nil { 100 aciFile.Close() 101 } 102 }() 103 104 session := o.getSession(u, aciFile.File, "ACI", etag) 105 dl := o.getDownloader(session) 106 errClose = dl.Download(u, aciFile.File) 107 if errClose != nil { 108 return nil, nil, errwrap.Wrap(errors.New("error downloading ACI"), errClose) 109 } 110 111 if session.Cd.UseCached { 112 aciFile.Close() 113 return NopReadSeekCloser(nil), session.Cd, nil 114 } 115 116 return aciFile, session.Cd, nil 117 } 118 119 // AscRemoteFetcher provides a remoteAscFetcher for asc. 120 func (o *httpOps) AscRemoteFetcher() *remoteAscFetcher { 121 ensureLogger(o.Debug) 122 f := func(u *url.URL, file *os.File) error { 123 switch u.Scheme { 124 case "http", "https": 125 default: 126 return fmt.Errorf("invalid signature location: expected %q scheme, got %q", "http(s)", u.Scheme) 127 } 128 session := o.getSession(u, file, "signature", "") 129 dl := o.getDownloader(session) 130 err := dl.Download(u, file) 131 if err != nil { 132 return err 133 } 134 if session.Cd.UseCached { 135 return fmt.Errorf("unexpected cache reuse request for signature %q", u.String()) 136 } 137 return nil 138 } 139 return &remoteAscFetcher{ 140 F: f, 141 S: o.S, 142 } 143 } 144 145 func (o *httpOps) getSession(u *url.URL, file *os.File, label, etag string) *resumableSession { 146 eTagFilePath := fmt.Sprintf("%s.etag", file.Name()) 147 return &resumableSession{ 148 InsecureSkipTLSVerify: o.InsecureSkipTLSVerify, 149 Headers: o.getHeaders(u, etag), 150 Headerers: o.Headers, 151 File: file, 152 ETagFilePath: eTagFilePath, 153 Label: label, 154 } 155 } 156 157 func (o *httpOps) getDownloader(session downloadSession) *downloader { 158 return &downloader{ 159 Session: session, 160 } 161 } 162 163 func (o *httpOps) getHeaders(u *url.URL, etag string) http.Header { 164 options := o.getHeadersForURL(u, etag) 165 if etag != "" { 166 options.Add("If-None-Match", etag) 167 } 168 return options 169 } 170 171 func (o *httpOps) getHeadersForURL(u *url.URL, etag string) http.Header { 172 return make(http.Header) 173 }