github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/rkt/image/fetcher.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 "container/list" 19 "fmt" 20 "net/url" 21 "runtime" 22 23 "github.com/coreos/rkt/common/apps" 24 "github.com/coreos/rkt/stage0" 25 "github.com/coreos/rkt/store" 26 "github.com/hashicorp/errwrap" 27 28 "github.com/appc/spec/discovery" 29 "github.com/appc/spec/schema/types" 30 ) 31 32 // Fetcher will try to fetch images into the store. 33 type Fetcher action 34 35 // FetchImage will take an image as either a path, a URL or a name 36 // string and import it into the store if found. If ascPath is not "", 37 // it must exist as a local file and will be used as the signature 38 // file for verification, unless verification is disabled. If 39 // f.WithDeps is true also image dependencies are fetched. 40 func (f *Fetcher) FetchImage(img string, ascPath string, imgType apps.AppImageType) (string, error) { 41 ensureLogger(f.Debug) 42 a := f.getAsc(ascPath) 43 hash, err := f.fetchSingleImage(img, a, imgType) 44 if err != nil { 45 return "", err 46 } 47 if f.WithDeps { 48 err = f.fetchImageDeps(hash) 49 if err != nil { 50 return "", err 51 } 52 } 53 return hash, nil 54 } 55 56 func (f *Fetcher) getAsc(ascPath string) *asc { 57 if ascPath != "" { 58 return &asc{ 59 Location: ascPath, 60 Fetcher: &localAscFetcher{}, 61 } 62 } 63 return &asc{} 64 } 65 66 // fetchImageDeps will recursively fetch all the image dependencies 67 func (f *Fetcher) fetchImageDeps(hash string) error { 68 imgsl := list.New() 69 seen := map[string]struct{}{} 70 f.addImageDeps(hash, imgsl, seen) 71 for el := imgsl.Front(); el != nil; el = el.Next() { 72 a := &asc{} 73 img := el.Value.(string) 74 hash, err := f.fetchSingleImage(img, a, apps.AppImageName) 75 if err != nil { 76 return err 77 } 78 f.addImageDeps(hash, imgsl, seen) 79 } 80 return nil 81 } 82 83 func (f *Fetcher) addImageDeps(hash string, imgsl *list.List, seen map[string]struct{}) error { 84 dependencies, err := f.getImageDeps(hash) 85 if err != nil { 86 return errwrap.Wrap(fmt.Errorf("failed to get dependencies for image ID %q", hash), err) 87 } 88 for _, d := range dependencies { 89 imgName := d.ImageName.String() 90 app, err := discovery.NewApp(imgName, d.Labels.ToMap()) 91 if err != nil { 92 return errwrap.Wrap(fmt.Errorf("one of image ID's %q dependencies (image %q) is invalid", hash, imgName), err) 93 } 94 appStr := app.String() 95 if _, ok := seen[appStr]; ok { 96 continue 97 } 98 imgsl.PushBack(app.String()) 99 seen[appStr] = struct{}{} 100 } 101 return nil 102 } 103 104 func (f *Fetcher) getImageDeps(hash string) (types.Dependencies, error) { 105 key, err := f.S.ResolveKey(hash) 106 if err != nil { 107 return nil, err 108 } 109 im, err := f.S.GetImageManifest(key) 110 if err != nil { 111 return nil, err 112 } 113 return im.Dependencies, nil 114 } 115 116 func (f *Fetcher) fetchSingleImage(img string, a *asc, imgType apps.AppImageType) (string, error) { 117 if imgType == apps.AppImageGuess { 118 imgType = guessImageType(img) 119 } 120 if imgType == apps.AppImageHash { 121 return "", fmt.Errorf("cannot fetch a hash '%q', expected either a URL, a path or an image name", img) 122 } 123 124 switch imgType { 125 case apps.AppImageURL: 126 return f.fetchSingleImageByURL(img, a) 127 case apps.AppImagePath: 128 return f.fetchSingleImageByPath(img, a) 129 case apps.AppImageName: 130 return f.fetchSingleImageByName(img, a) 131 default: 132 return "", fmt.Errorf("unknown image type %d", imgType) 133 } 134 } 135 136 type remoteCheck int 137 138 const ( 139 remoteCheckLax remoteCheck = iota 140 remoteCheckStrict 141 ) 142 143 func (f *Fetcher) fetchSingleImageByURL(urlStr string, a *asc) (string, error) { 144 u, err := url.Parse(urlStr) 145 if err != nil { 146 return "", errwrap.Wrap(fmt.Errorf("invalid image URL %q", urlStr), err) 147 } 148 149 switch u.Scheme { 150 case "http", "https": 151 return f.fetchSingleImageByHTTPURL(u, a) 152 case "docker": 153 return f.fetchSingleImageByDockerURL(u) 154 case "file": 155 return f.fetchSingleImageByPath(u.Path, a) 156 case "": 157 return "", fmt.Errorf("expected image URL %q to contain a scheme", urlStr) 158 default: 159 return "", fmt.Errorf("an unsupported URL scheme %q - the only URL schemes supported by rkt are docker, http, https and file", u.Scheme) 160 } 161 } 162 163 func (f *Fetcher) fetchSingleImageByHTTPURL(u *url.URL, a *asc) (string, error) { 164 rem, err := f.getRemoteForURL(u) 165 if err != nil { 166 return "", err 167 } 168 if h := f.maybeCheckRemoteFromStore(rem, remoteCheckStrict); h != "" { 169 return h, nil 170 } 171 if h, err := f.maybeFetchHTTPURLFromRemote(rem, u, a); h != "" || err != nil { 172 return h, err 173 } 174 return "", fmt.Errorf("unable to fetch image from URL %q: either image was not found in the store or store was disabled and fetching from remote yielded nothing or it was disabled", u.String()) 175 } 176 177 func (f *Fetcher) fetchSingleImageByDockerURL(u *url.URL) (string, error) { 178 rem, err := f.getRemoteForURL(u) 179 if err != nil { 180 return "", err 181 } 182 // TODO(krnowak): use strict checking when we implement 183 // setting CacheMaxAge in store.Remote for docker images 184 if h := f.maybeCheckRemoteFromStore(rem, remoteCheckLax); h != "" { 185 return h, nil 186 } 187 if h, err := f.maybeFetchDockerURLFromRemote(u); h != "" || err != nil { 188 return h, err 189 } 190 return "", fmt.Errorf("unable to fetch docker image from URL %q: either image was not found in the store or store was disabled and fetching from remote yielded nothing or it was disabled", u.String()) 191 } 192 193 func (f *Fetcher) getRemoteForURL(u *url.URL) (*store.Remote, error) { 194 if f.NoCache { 195 return nil, nil 196 } 197 urlStr := u.String() 198 if rem, ok, err := f.S.GetRemote(urlStr); err != nil { 199 return nil, errwrap.Wrap(fmt.Errorf("failed to fetch URL %q", urlStr), err) 200 } else if ok { 201 return rem, nil 202 } 203 return nil, nil 204 } 205 206 func (f *Fetcher) maybeCheckRemoteFromStore(rem *store.Remote, check remoteCheck) string { 207 if f.NoStore || rem == nil { 208 return "" 209 } 210 useBlobKey := false 211 switch check { 212 case remoteCheckLax: 213 useBlobKey = true 214 case remoteCheckStrict: 215 useBlobKey = useCached(rem.DownloadTime, rem.CacheMaxAge) 216 } 217 if useBlobKey { 218 log.Printf("using image from local store for url %s", rem.ACIURL) 219 return rem.BlobKey 220 } 221 return "" 222 } 223 224 func (f *Fetcher) maybeFetchHTTPURLFromRemote(rem *store.Remote, u *url.URL, a *asc) (string, error) { 225 if !f.StoreOnly { 226 log.Printf("remote fetching from URL %q", u.String()) 227 hf := &httpFetcher{ 228 InsecureFlags: f.InsecureFlags, 229 S: f.S, 230 Ks: f.Ks, 231 Rem: rem, 232 Debug: f.Debug, 233 Headers: f.Headers, 234 } 235 return hf.GetHash(u, a) 236 } 237 return "", nil 238 } 239 240 func (f *Fetcher) maybeFetchDockerURLFromRemote(u *url.URL) (string, error) { 241 if !f.StoreOnly { 242 log.Printf("remote fetching from URL %q", u.String()) 243 df := &dockerFetcher{ 244 InsecureFlags: f.InsecureFlags, 245 DockerAuth: f.DockerAuth, 246 S: f.S, 247 Debug: f.Debug, 248 } 249 return df.GetHash(u) 250 } 251 return "", nil 252 } 253 254 func (f *Fetcher) fetchSingleImageByPath(path string, a *asc) (string, error) { 255 log.Printf("using image from file %s", path) 256 ff := &fileFetcher{ 257 InsecureFlags: f.InsecureFlags, 258 S: f.S, 259 Ks: f.Ks, 260 Debug: f.Debug, 261 } 262 return ff.GetHash(path, a) 263 } 264 265 type appBundle struct { 266 App *discovery.App 267 Str string 268 } 269 270 func newAppBundle(name string) (*appBundle, error) { 271 app, err := discovery.NewAppFromString(name) 272 if err != nil { 273 return nil, errwrap.Wrap(fmt.Errorf("invalid image name %q", name), err) 274 } 275 if _, ok := app.Labels["arch"]; !ok { 276 app.Labels["arch"] = runtime.GOARCH 277 } 278 if _, ok := app.Labels["os"]; !ok { 279 app.Labels["os"] = runtime.GOOS 280 } 281 if err := types.IsValidOSArch(app.Labels, stage0.ValidOSArch); err != nil { 282 return nil, errwrap.Wrap(fmt.Errorf("invalid image name %q", name), err) 283 } 284 bundle := &appBundle{ 285 App: app, 286 Str: name, 287 } 288 return bundle, nil 289 } 290 291 func (f *Fetcher) fetchSingleImageByName(name string, a *asc) (string, error) { 292 app, err := newAppBundle(name) 293 if err != nil { 294 return "", err 295 } 296 if h, err := f.maybeCheckStoreForApp(app); h != "" || err != nil { 297 return h, err 298 } 299 if h, err := f.maybeFetchImageFromRemote(app, a); h != "" || err != nil { 300 return h, err 301 } 302 return "", fmt.Errorf("unable to fetch image from image name %q: either image was not found in the store or store was disabled and fetching from remote yielded nothing or it was disabled", name) 303 } 304 305 func (f *Fetcher) maybeCheckStoreForApp(app *appBundle) (string, error) { 306 if !f.NoStore { 307 key, err := f.getStoreKeyFromApp(app) 308 if err == nil { 309 log.Printf("using image from local store for image name %s", app.Str) 310 return key, nil 311 } 312 switch err.(type) { 313 case store.ACINotFoundError: 314 // ignore the "not found" error 315 default: 316 return "", err 317 } 318 } 319 return "", nil 320 } 321 322 func (f *Fetcher) getStoreKeyFromApp(app *appBundle) (string, error) { 323 labels, err := types.LabelsFromMap(app.App.Labels) 324 if err != nil { 325 return "", errwrap.Wrap(fmt.Errorf("invalid labels in the name %q", app.Str), err) 326 } 327 key, err := f.S.GetACI(app.App.Name, labels) 328 if err != nil { 329 switch err.(type) { 330 case store.ACINotFoundError: 331 return "", err 332 default: 333 return "", errwrap.Wrap(fmt.Errorf("cannot find image %q", app.Str), err) 334 } 335 } 336 return key, nil 337 } 338 339 func (f *Fetcher) maybeFetchImageFromRemote(app *appBundle, a *asc) (string, error) { 340 if !f.StoreOnly { 341 nf := &nameFetcher{ 342 InsecureFlags: f.InsecureFlags, 343 S: f.S, 344 Ks: f.Ks, 345 Debug: f.Debug, 346 Headers: f.Headers, 347 TrustKeysFromHTTPS: f.TrustKeysFromHTTPS, 348 } 349 return nf.GetHash(app.App, a) 350 } 351 return "", nil 352 }