github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/image/common.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  	"fmt"
    19  	"io/ioutil"
    20  	"net/url"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/hashicorp/errwrap"
    27  	dist "github.com/rkt/rkt/pkg/distribution"
    28  	"github.com/rkt/rkt/pkg/keystore"
    29  	rktlog "github.com/rkt/rkt/pkg/log"
    30  	"github.com/rkt/rkt/rkt/config"
    31  	rktflag "github.com/rkt/rkt/rkt/flag"
    32  	"github.com/rkt/rkt/store/imagestore"
    33  	"github.com/rkt/rkt/store/treestore"
    34  
    35  	"github.com/appc/spec/discovery"
    36  	"github.com/appc/spec/schema"
    37  	"golang.org/x/crypto/openpgp"
    38  )
    39  
    40  type imageStringType int
    41  
    42  const (
    43  	imageStringName imageStringType = iota // image type to be guessed
    44  	imageStringPath                        // absolute or relative path
    45  
    46  	PullPolicyNever  = "never"
    47  	PullPolicyNew    = "new"
    48  	PullPolicyUpdate = "update"
    49  )
    50  
    51  // action is a common type for Finder and Fetcher
    52  type action struct {
    53  	// S is an aci store where images will be looked for or stored.
    54  	S *imagestore.Store
    55  	// Ts is an aci tree store.
    56  	Ts *treestore.Store
    57  	// Ks is a keystore used for verification of the image
    58  	Ks *keystore.Keystore
    59  	// Headers is a map of headers which might be used for
    60  	// downloading via https protocol.
    61  	Headers map[string]config.Headerer
    62  	// DockerAuth is used for authenticating when fetching docker
    63  	// images.
    64  	DockerAuth map[string]config.BasicCredentials
    65  	// InsecureFlags is a set of flags for enabling some insecure
    66  	// functionality. For now it is mostly skipping image
    67  	// signature verification and TLS certificate verification.
    68  	InsecureFlags *rktflag.SecFlags
    69  	// Debug tells whether additional debug messages should be
    70  	// printed.
    71  	Debug bool
    72  	// TrustKeysFromHTTPS tells whether discovered keys downloaded
    73  	// via the https protocol can be trusted
    74  	TrustKeysFromHTTPS bool
    75  
    76  	// PullPolicy controls when to pull images from remote, versus using a copy
    77  	// on the local filesystem, versus checking for updates to local images
    78  	PullPolicy string
    79  	// NoCache tells to ignore transport caching.
    80  	NoCache bool
    81  	// WithDeps tells whether image dependencies should be
    82  	// downloaded too.
    83  	WithDeps bool
    84  }
    85  
    86  var (
    87  	log    *rktlog.Logger
    88  	diag   *rktlog.Logger
    89  	stdout *rktlog.Logger
    90  )
    91  
    92  func ensureLogger(debug bool) {
    93  	if log == nil || diag == nil || stdout == nil {
    94  		log, diag, stdout = rktlog.NewLogSet("image", debug)
    95  	}
    96  	if !debug {
    97  		diag.SetOutput(ioutil.Discard)
    98  	}
    99  }
   100  
   101  // useCached checks if downloadTime plus maxAge is before/after the current time.
   102  // return true if the cached image should be used, false otherwise.
   103  func useCached(downloadTime time.Time, maxAge int) bool {
   104  	freshnessLifetime := int(time.Now().Sub(downloadTime).Seconds())
   105  	if maxAge > 0 && freshnessLifetime < maxAge {
   106  		return true
   107  	}
   108  	return false
   109  }
   110  
   111  // ascURLFromImgURL creates a URL to a signature file from passed URL
   112  // to an image.
   113  func ascURLFromImgURL(u *url.URL) *url.URL {
   114  	copy := *u
   115  	copy.Path = ascPathFromImgPath(copy.Path)
   116  	return &copy
   117  }
   118  
   119  // ascPathFromImgPath creates a path to a signature file from passed
   120  // path to an image.
   121  func ascPathFromImgPath(path string) string {
   122  	return fmt.Sprintf("%s.aci.asc", strings.TrimSuffix(path, ".aci"))
   123  }
   124  
   125  // printIdentities prints a message that signature was verified.
   126  func printIdentities(entity *openpgp.Entity) {
   127  	lines := []string{"signature verified:"}
   128  	for _, v := range entity.Identities {
   129  		lines = append(lines, fmt.Sprintf("  %s", v.Name))
   130  	}
   131  	log.Print(strings.Join(lines, "\n"))
   132  }
   133  
   134  // DistFromImageString return the distribution for the given input image string
   135  func DistFromImageString(is string) (dist.Distribution, error) {
   136  	u, err := url.Parse(is)
   137  	if err != nil {
   138  		return nil, errwrap.Wrap(fmt.Errorf("failed to parse image url %q", is), err)
   139  	}
   140  
   141  	// Convert user friendly image string names to internal distribution URIs
   142  	// file:///full/path/to/aci/file.aci -> archive:aci:file%3A%2F%2F%2Ffull%2Fpath%2Fto%2Faci%2Ffile.aci
   143  	switch u.Scheme {
   144  	case "":
   145  		// no scheme given, hence it is an appc image name or path
   146  		appImageType := guessAppcOrPath(is, []string{schema.ACIExtension})
   147  
   148  		switch appImageType {
   149  		case imageStringName:
   150  			app, err := discovery.NewAppFromString(is)
   151  			if err != nil {
   152  				return nil, fmt.Errorf("invalid appc image string %q: %v", is, err)
   153  			}
   154  			return dist.NewAppcFromApp(app), nil
   155  		case imageStringPath:
   156  			absPath, err := filepath.Abs(is)
   157  			if err != nil {
   158  				return nil, errwrap.Wrap(fmt.Errorf("failed to get an absolute path for %q", is), err)
   159  			}
   160  			is = "file://" + absPath
   161  
   162  			// given a file:// image string, call this function again to return an ACI distribution
   163  			return DistFromImageString(is)
   164  		default:
   165  			return nil, fmt.Errorf("invalid image string type %q", appImageType)
   166  		}
   167  	case "file", "http", "https":
   168  		// An ACI archive with any transport type (file, http, s3 etc...) and final aci extension
   169  		if filepath.Ext(u.Path) == schema.ACIExtension {
   170  			dist, err := dist.NewACIArchiveFromTransportURL(u)
   171  			if err != nil {
   172  				return nil, fmt.Errorf("archive distribution creation error: %v", err)
   173  			}
   174  			return dist, nil
   175  		}
   176  	case "docker":
   177  		// Accept both docker: and docker:// uri
   178  		dockerStr := is
   179  		if strings.HasPrefix(dockerStr, "docker://") {
   180  			dockerStr = strings.TrimPrefix(dockerStr, "docker://")
   181  		} else if strings.HasPrefix(dockerStr, "docker:") {
   182  			dockerStr = strings.TrimPrefix(dockerStr, "docker:")
   183  		}
   184  
   185  		dist, err := dist.NewDockerFromString(dockerStr)
   186  		if err != nil {
   187  			return nil, fmt.Errorf("docker distribution creation error: %v", err)
   188  		}
   189  		return dist, nil
   190  	case dist.Scheme: // cimd
   191  		return dist.Parse(is)
   192  	default:
   193  		// any other scheme is a an appc image name, i.e. "my-app:v1.0"
   194  		app, err := discovery.NewAppFromString(is)
   195  		if err != nil {
   196  			return nil, fmt.Errorf("invalid appc image string %q: %v", is, err)
   197  		}
   198  
   199  		return dist.NewAppcFromApp(app), nil
   200  	}
   201  
   202  	return nil, fmt.Errorf("invalid image string %q", is)
   203  }
   204  
   205  func guessAppcOrPath(is string, extensions []string) imageStringType {
   206  	if filepath.IsAbs(is) {
   207  		return imageStringPath
   208  	}
   209  
   210  	// Well, at this point is basically heuristics time. The image
   211  	// parameter can be either a relative path or an image name.
   212  
   213  	// First, let's try to stat whatever file the URL would specify. If it
   214  	// exists, that's probably what the user wanted.
   215  	f, err := os.Stat(is)
   216  	if err == nil && f.Mode().IsRegular() {
   217  		return imageStringPath
   218  	}
   219  
   220  	// Second, let's check if there is a colon in the image
   221  	// parameter. Colon often serves as a paths separator (like in
   222  	// the PATH environment variable), so if it exists, then it is
   223  	// highly unlikely that the image parameter is a path. Colon
   224  	// in this context is often used for specifying a version of
   225  	// an image, like in "example.com/reduce-worker:1.0.0".
   226  	if strings.ContainsRune(is, ':') {
   227  		return imageStringName
   228  	}
   229  
   230  	// Third, let's check if there is a dot followed by a slash
   231  	// (./) - if so, it is likely that the image parameter is path
   232  	// like ./aci-in-this-dir or ../aci-in-parent-dir
   233  	if strings.Contains(is, "./") {
   234  		return imageStringPath
   235  	}
   236  
   237  	// Fourth, let's check if the image parameter has an .aci
   238  	// extension. If so, likely a path like "stage1-coreos.aci".
   239  	for _, e := range extensions {
   240  		if filepath.Ext(is) == e {
   241  			return imageStringPath
   242  		}
   243  	}
   244  
   245  	// At this point, if the image parameter is something like
   246  	// "coreos.com/rkt/stage1-coreos" and you have a directory
   247  	// tree "coreos.com/rkt" in the current working directory and
   248  	// you meant the image parameter to point to the file
   249  	// "stage1-coreos" in this directory tree, then you better be
   250  	// off prepending the parameter with "./", because I'm gonna
   251  	// treat this as an image name otherwise.
   252  	return imageStringName
   253  }
   254  
   255  func eTag(rem *imagestore.Remote) string {
   256  	if rem != nil {
   257  		return rem.ETag
   258  	}
   259  	return ""
   260  }
   261  
   262  func maybeUseCached(rem *imagestore.Remote, cd *cacheData) string {
   263  	if rem == nil || cd == nil {
   264  		return ""
   265  	}
   266  	if cd.UseCached {
   267  		return rem.BlobKey
   268  	}
   269  	return ""
   270  }
   271  
   272  func remoteForURL(s *imagestore.Store, u *url.URL) (*imagestore.Remote, error) {
   273  	urlStr := u.String()
   274  	rem, err := s.GetRemote(urlStr)
   275  	if err != nil {
   276  		if err == imagestore.ErrRemoteNotFound {
   277  			return nil, nil
   278  		}
   279  
   280  		return nil, errwrap.Wrap(fmt.Errorf("failed to fetch remote for URL %q", urlStr), err)
   281  	}
   282  
   283  	return rem, nil
   284  }