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  }