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  }