github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/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/coreos/rkt/rkt/config" 25 "github.com/coreos/rkt/store" 26 "github.com/hashicorp/errwrap" 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 *store.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 log.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 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 ensureLogger(o.Debug) 90 aciFile, err := getTmpROC(o.S, u.String()) 91 if err != nil { 92 return nil, nil, err 93 } 94 defer func() { maybeClose(aciFile) }() 95 96 session := o.getSession(u, aciFile.File, "ACI", etag) 97 dl := o.getDownloader(session) 98 if err := dl.Download(u, aciFile.File); err != nil { 99 return nil, nil, errwrap.Wrap(errors.New("error downloading ACI"), err) 100 } 101 if session.Cd.UseCached { 102 return nil, session.Cd, nil 103 } 104 retAciFile := aciFile 105 aciFile = nil 106 return retAciFile, session.Cd, nil 107 } 108 109 // GetAscRemoteFetcher provides a remoteAscFetcher for asc. 110 func (o *httpOps) GetAscRemoteFetcher() *remoteAscFetcher { 111 ensureLogger(o.Debug) 112 f := func(u *url.URL, file *os.File) error { 113 switch u.Scheme { 114 case "http", "https": 115 default: 116 return fmt.Errorf("invalid signature location: expected %q scheme, got %q", "http(s)", u.Scheme) 117 } 118 session := o.getSession(u, file, "signature", "") 119 dl := o.getDownloader(session) 120 err := dl.Download(u, file) 121 if err != nil { 122 return err 123 } 124 if session.Cd.UseCached { 125 return fmt.Errorf("unexpected cache reuse request for signature %q", u.String()) 126 } 127 return nil 128 } 129 return &remoteAscFetcher{ 130 F: f, 131 S: o.S, 132 } 133 } 134 135 func (o *httpOps) getSession(u *url.URL, file *os.File, label, etag string) *resumableSession { 136 eTagFilePath := fmt.Sprintf("%s.etag", file.Name()) 137 return &resumableSession{ 138 InsecureSkipTLSVerify: o.InsecureSkipTLSVerify, 139 Headers: o.getHeaders(u, etag), 140 File: file, 141 ETagFilePath: eTagFilePath, 142 Label: label, 143 } 144 } 145 146 func (o *httpOps) getDownloader(session downloadSession) *downloader { 147 return &downloader{ 148 Session: session, 149 } 150 } 151 152 func (o *httpOps) getHeaders(u *url.URL, etag string) http.Header { 153 options := o.getHeadersForURL(u) 154 if etag != "" { 155 options.Add("If-None-Match", etag) 156 } 157 return options 158 } 159 160 func (o *httpOps) getHeadersForURL(u *url.URL) http.Header { 161 // Send credentials only over secure channel 162 // TODO(krnowak): This could be controlled with another 163 // insecure flag. 164 if u.Scheme == "https" { 165 if hostOpts, ok := o.Headers[u.Host]; ok { 166 return hostOpts.Header() 167 } 168 } 169 170 return make(http.Header) 171 }