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 © 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 }