github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/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 "errors" 20 "fmt" 21 "net/url" 22 "os" 23 24 "github.com/hashicorp/errwrap" 25 "github.com/rkt/rkt/common" 26 "github.com/rkt/rkt/common/apps" 27 dist "github.com/rkt/rkt/pkg/distribution" 28 "github.com/rkt/rkt/stage0" 29 "github.com/rkt/rkt/store/imagestore" 30 31 "github.com/appc/spec/discovery" 32 "github.com/appc/spec/schema/types" 33 ) 34 35 // distBundle contains the distribution and the original image string 36 // (primarily used for logging) 37 type distBundle struct { 38 dist dist.Distribution 39 image string 40 } 41 42 // Fetcher will try to fetch images into the store. 43 type Fetcher action 44 45 // FetchImages uses FetchImage to attain a list of image hashes 46 func (f *Fetcher) FetchImages(al *apps.Apps) error { 47 return al.Walk(func(app *apps.App) error { 48 d, err := DistFromImageString(app.Image) 49 if err != nil { 50 return err 51 } 52 h, err := f.FetchImage(d, app.Image, app.Asc) 53 if err != nil { 54 return err 55 } 56 app.ImageID = *h 57 return nil 58 }) 59 } 60 61 // FetchImage will take an image as either a path, a URL or a name 62 // string and import it into the store if found. If ascPath is not "", 63 // it must exist as a local file and will be used as the signature 64 // file for verification, unless verification is disabled. If 65 // f.WithDeps is true also image dependencies are fetched. 66 func (f *Fetcher) FetchImage(d dist.Distribution, image, ascPath string) (*types.Hash, error) { 67 ensureLogger(f.Debug) 68 db := &distBundle{ 69 dist: d, 70 image: image, 71 } 72 a := f.getAsc(ascPath) 73 hash, err := f.fetchSingleImage(db, a) 74 if err != nil { 75 return nil, err 76 } 77 if f.WithDeps { 78 err = f.fetchImageDeps(hash) 79 if err != nil { 80 return nil, err 81 } 82 } 83 84 // we need to be able to do a chroot and access to the tree store 85 // directories, we need to 86 // 1) check if the system supports OverlayFS 87 // 2) check if we're root 88 if common.SupportsOverlay() == nil && os.Geteuid() == 0 { 89 if _, _, err := f.Ts.Render(hash, false); err != nil { 90 return nil, errwrap.Wrap(errors.New("error rendering tree store"), err) 91 } 92 } 93 h, err := types.NewHash(hash) 94 if err != nil { 95 // should never happen 96 log.PanicE("invalid hash", err) 97 } 98 return h, nil 99 } 100 101 func (f *Fetcher) getAsc(ascPath string) *asc { 102 if ascPath != "" { 103 return &asc{ 104 Location: ascPath, 105 Fetcher: &localAscFetcher{}, 106 } 107 } 108 return &asc{} 109 } 110 111 // fetchImageDeps will recursively fetch all the image dependencies 112 func (f *Fetcher) fetchImageDeps(hash string) error { 113 imgsl := list.New() 114 seen := map[string]dist.Distribution{} 115 f.addImageDeps(hash, imgsl, seen) 116 for el := imgsl.Front(); el != nil; el = el.Next() { 117 a := &asc{} 118 d := el.Value.(*dist.Appc) 119 str := d.String() 120 db := &distBundle{ 121 dist: d, 122 image: str, 123 } 124 hash, err := f.fetchSingleImage(db, a) 125 if err != nil { 126 return err 127 } 128 f.addImageDeps(hash, imgsl, seen) 129 } 130 return nil 131 } 132 133 func (f *Fetcher) addImageDeps(hash string, imgsl *list.List, seen map[string]dist.Distribution) error { 134 dependencies, err := f.getImageDeps(hash) 135 if err != nil { 136 return errwrap.Wrap(fmt.Errorf("failed to get dependencies for image ID %q", hash), err) 137 } 138 for _, d := range dependencies { 139 imgName := d.ImageName.String() 140 app, err := discovery.NewApp(imgName, d.Labels.ToMap()) 141 if err != nil { 142 return errwrap.Wrap(fmt.Errorf("one of image ID's %q dependencies (image %q) is invalid", hash, imgName), err) 143 } 144 d := dist.NewAppcFromApp(app) 145 // To really catch already seen deps the saved string must be a 146 // reproducible string keeping the labels order 147 for _, sd := range seen { 148 if d.Equals(sd) { 149 continue 150 } 151 } 152 imgsl.PushBack(d) 153 seen[d.CIMD().String()] = d 154 } 155 return nil 156 } 157 158 func (f *Fetcher) getImageDeps(hash string) (types.Dependencies, error) { 159 key, err := f.S.ResolveKey(hash) 160 if err != nil { 161 return nil, err 162 } 163 im, err := f.S.GetImageManifest(key) 164 if err != nil { 165 return nil, err 166 } 167 return im.Dependencies, nil 168 } 169 170 func (f *Fetcher) fetchSingleImage(db *distBundle, a *asc) (string, error) { 171 switch v := db.dist.(type) { 172 case *dist.ACIArchive: 173 return f.fetchACIArchive(db, a) 174 case *dist.Appc: 175 return f.fetchSingleImageByName(db, a) 176 case *dist.Docker: 177 return f.fetchSingleImageByDockerURL(v) 178 default: 179 return "", fmt.Errorf("unknown distribution type %T", v) 180 } 181 } 182 183 func (f *Fetcher) fetchACIArchive(db *distBundle, a *asc) (string, error) { 184 u := db.dist.(*dist.ACIArchive).TransportURL() 185 186 switch u.Scheme { 187 case "http", "https": 188 return f.fetchSingleImageByHTTPURL(u, a) 189 case "file": 190 return f.fetchSingleImageByPath(u.Path, a) 191 case "": 192 return "", fmt.Errorf("expected image URL %q to contain a scheme", u.String()) 193 default: 194 return "", fmt.Errorf("an unsupported URL scheme %q - the only URL schemes supported by rkt for an archive are http, https and file", u.Scheme) 195 } 196 } 197 198 func (f *Fetcher) fetchSingleImageByHTTPURL(u *url.URL, a *asc) (string, error) { 199 rem, err := remoteForURL(f.S, u) 200 if err != nil { 201 return "", err 202 } 203 if h := f.maybeCheckRemoteFromStore(rem); h != "" { 204 return h, nil 205 } 206 if h, err := f.maybeFetchHTTPURLFromRemote(rem, u, a); h != "" || err != nil { 207 return h, err 208 } 209 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()) 210 } 211 212 func (f *Fetcher) fetchSingleImageByDockerURL(d *dist.Docker) (string, error) { 213 ds := d.ReferenceURL() 214 // Convert to the docker2aci URL format 215 urlStr := "docker://" + ds 216 u, err := url.Parse(urlStr) 217 if err != nil { 218 return "", err 219 } 220 221 rem, err := remoteForURL(f.S, u) 222 if err != nil { 223 return "", err 224 } 225 if h := f.maybeCheckRemoteFromStore(rem); h != "" { 226 return h, nil 227 } 228 if h, err := f.maybeFetchDockerURLFromRemote(u); h != "" || err != nil { 229 return h, err 230 } 231 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()) 232 } 233 234 func (f *Fetcher) maybeCheckRemoteFromStore(rem *imagestore.Remote) string { 235 if f.PullPolicy == PullPolicyUpdate || rem == nil { 236 return "" 237 } 238 diag.Printf("using image from local store for url %s", rem.ACIURL) 239 return rem.BlobKey 240 } 241 242 func (f *Fetcher) maybeFetchHTTPURLFromRemote(rem *imagestore.Remote, u *url.URL, a *asc) (string, error) { 243 if f.PullPolicy != PullPolicyNever { 244 diag.Printf("remote fetching from URL %q", u.String()) 245 hf := &httpFetcher{ 246 InsecureFlags: f.InsecureFlags, 247 S: f.S, 248 Ks: f.Ks, 249 Rem: rem, 250 Debug: f.Debug, 251 Headers: f.Headers, 252 } 253 return hf.Hash(u, a) 254 } 255 return "", nil 256 } 257 258 func (f *Fetcher) maybeFetchDockerURLFromRemote(u *url.URL) (string, error) { 259 if f.PullPolicy != PullPolicyNever { 260 diag.Printf("remote fetching from URL %q", u.String()) 261 df := &dockerFetcher{ 262 InsecureFlags: f.InsecureFlags, 263 DockerAuth: f.DockerAuth, 264 S: f.S, 265 Debug: f.Debug, 266 } 267 return df.Hash(u) 268 } 269 return "", nil 270 } 271 272 func (f *Fetcher) fetchSingleImageByPath(path string, a *asc) (string, error) { 273 diag.Printf("using image from file %s", path) 274 ff := &fileFetcher{ 275 InsecureFlags: f.InsecureFlags, 276 S: f.S, 277 Ks: f.Ks, 278 Debug: f.Debug, 279 } 280 return ff.Hash(path, a) 281 } 282 283 // TODO(sgotti) I'm not sure setting default os and arch also for image 284 // dependencies is correct since it may break noarch dependencies. Reference: 285 // https://github.com/rkt/rkt/pull/2317 286 func (db *distBundle) setAppDefaults() error { 287 app := db.dist.(*dist.Appc).App() 288 if _, ok := app.Labels["arch"]; !ok { 289 app.Labels["arch"] = common.GetArch() 290 } 291 if _, ok := app.Labels["os"]; !ok { 292 app.Labels["os"] = common.GetOS() 293 } 294 if err := types.IsValidOSArch(app.Labels, stage0.ValidOSArch); err != nil { 295 return errwrap.Wrap(fmt.Errorf("invalid Appc distribution %q", db.image), err) 296 } 297 db.dist = dist.NewAppcFromApp(app) 298 return nil 299 } 300 301 func (f *Fetcher) fetchSingleImageByName(db *distBundle, a *asc) (string, error) { 302 if err := db.setAppDefaults(); err != nil { 303 return "", err 304 } 305 if h, err := f.maybeCheckStoreForApp(db); h != "" || err != nil { 306 return h, err 307 } 308 if h, err := f.maybeFetchImageFromRemote(db, a); h != "" || err != nil { 309 return h, err 310 } 311 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", db.image) 312 } 313 314 func (f *Fetcher) maybeCheckStoreForApp(db *distBundle) (string, error) { 315 if f.PullPolicy != PullPolicyUpdate { 316 key, err := f.getStoreKeyFromApp(db) 317 if err == nil { 318 diag.Printf("using image from local store for image name %s", db.image) 319 return key, nil 320 } 321 switch err.(type) { 322 case imagestore.ACINotFoundError: 323 // ignore the "not found" error 324 default: 325 return "", err 326 } 327 } 328 return "", nil 329 } 330 331 func (f *Fetcher) getStoreKeyFromApp(db *distBundle) (string, error) { 332 app := db.dist.(*dist.Appc).App() 333 labels, err := types.LabelsFromMap(app.Labels) 334 if err != nil { 335 return "", errwrap.Wrap(fmt.Errorf("invalid labels in the name %q", db.image), err) 336 } 337 key, err := f.S.GetACI(app.Name, labels) 338 if err != nil { 339 switch err.(type) { 340 case imagestore.ACINotFoundError: 341 return "", err 342 default: 343 return "", errwrap.Wrap(fmt.Errorf("cannot find image %q", db.image), err) 344 } 345 } 346 return key, nil 347 } 348 349 func (f *Fetcher) maybeFetchImageFromRemote(db *distBundle, a *asc) (string, error) { 350 if f.PullPolicy != PullPolicyNever { 351 app := db.dist.(*dist.Appc).App() 352 nf := &nameFetcher{ 353 InsecureFlags: f.InsecureFlags, 354 S: f.S, 355 Ks: f.Ks, 356 NoCache: f.NoCache, 357 Debug: f.Debug, 358 Headers: f.Headers, 359 TrustKeysFromHTTPS: f.TrustKeysFromHTTPS, 360 } 361 return nf.Hash(app, a) 362 } 363 return "", nil 364 }