github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/image/pull.go (about) 1 package image 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/containers/common/pkg/retry" 12 cp "github.com/containers/image/v5/copy" 13 "github.com/containers/image/v5/directory" 14 "github.com/containers/image/v5/docker" 15 "github.com/containers/image/v5/docker/archive" 16 dockerarchive "github.com/containers/image/v5/docker/archive" 17 ociarchive "github.com/containers/image/v5/oci/archive" 18 oci "github.com/containers/image/v5/oci/layout" 19 "github.com/containers/image/v5/pkg/shortnames" 20 is "github.com/containers/image/v5/storage" 21 "github.com/containers/image/v5/transports" 22 "github.com/containers/image/v5/transports/alltransports" 23 "github.com/containers/image/v5/types" 24 "github.com/containers/podman/v2/libpod/events" 25 "github.com/containers/podman/v2/pkg/errorhandling" 26 "github.com/containers/podman/v2/pkg/registries" 27 "github.com/opentracing/opentracing-go" 28 "github.com/pkg/errors" 29 "github.com/sirupsen/logrus" 30 ) 31 32 var ( 33 // DockerArchive is the transport we prepend to an image name 34 // when saving to docker-archive 35 DockerArchive = dockerarchive.Transport.Name() 36 // OCIArchive is the transport we prepend to an image name 37 // when saving to oci-archive 38 OCIArchive = ociarchive.Transport.Name() 39 // DirTransport is the transport for pushing and pulling 40 // images to and from a directory 41 DirTransport = directory.Transport.Name() 42 // DockerTransport is the transport for docker registries 43 DockerTransport = docker.Transport.Name() 44 // OCIDirTransport is the transport for pushing and pulling 45 // images to and from a directory containing an OCI image 46 OCIDirTransport = oci.Transport.Name() 47 // AtomicTransport is the transport for atomic registries 48 AtomicTransport = "atomic" 49 // DefaultTransport is a prefix that we apply to an image name 50 // NOTE: This is a string prefix, not actually a transport name usable for transports.Get(); 51 // and because syntaxes of image names are transport-dependent, the prefix is not really interchangeable; 52 // each user implicitly assumes the appended string is a Docker-like reference. 53 DefaultTransport = DockerTransport + "://" 54 // DefaultLocalRegistry is the default local registry for local image operations 55 // Remote pulls will still use defined registries 56 DefaultLocalRegistry = "localhost" 57 ) 58 59 // pullRefPair records a pair of prepared image references to pull. 60 type pullRefPair struct { 61 image string 62 srcRef types.ImageReference 63 dstRef types.ImageReference 64 resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull 65 } 66 67 // cleanUpFunc is a function prototype for clean-up functions. 68 type cleanUpFunc func() error 69 70 // pullGoal represents the prepared image references and decided behavior to be executed by imagePull 71 type pullGoal struct { 72 refPairs []pullRefPair 73 pullAllPairs bool // Pull all refPairs instead of stopping on first success. 74 cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader) 75 shortName string // Set when pulling a short name 76 resolved *shortnames.Resolved // Set when pulling a short name 77 } 78 79 // cleanUp invokes all cleanUpFuncs. Certain resources may not be available 80 // anymore. Errors are logged. 81 func (p *pullGoal) cleanUp() { 82 for _, f := range p.cleanUpFuncs { 83 if err := f(); err != nil { 84 logrus.Error(err.Error()) 85 } 86 } 87 } 88 89 // singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair. 90 func singlePullRefPairGoal(rp pullRefPair) *pullGoal { 91 return &pullGoal{ 92 refPairs: []pullRefPair{rp}, 93 pullAllPairs: false, // Does not really make a difference. 94 } 95 } 96 97 func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) { 98 decomposedDest, err := decompose(destName) 99 if err == nil && !decomposedDest.hasRegistry { 100 // If the image doesn't have a registry, set it as the default repo 101 ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry) 102 if err != nil { 103 return pullRefPair{}, err 104 } 105 destName = ref.String() 106 } 107 108 reference := destName 109 if srcRef.DockerReference() != nil { 110 reference = srcRef.DockerReference().String() 111 } 112 destRef, err := is.Transport.ParseStoreReference(ir.store, reference) 113 if err != nil { 114 return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName) 115 } 116 return pullRefPair{ 117 image: destName, 118 srcRef: srcRef, 119 dstRef: destRef, 120 }, nil 121 } 122 123 // getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value. 124 func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) { 125 rp, err := ir.getPullRefPair(srcRef, destName) 126 if err != nil { 127 return nil, err 128 } 129 return singlePullRefPairGoal(rp), nil 130 } 131 132 // getPullRefPairsFromDockerArchiveReference returns a slice of pullRefPairs 133 // for the specified docker reference and the corresponding archive.Reader. 134 func (ir *Runtime) getPullRefPairsFromDockerArchiveReference(ctx context.Context, reader *archive.Reader, ref types.ImageReference, sc *types.SystemContext) ([]pullRefPair, error) { 135 destNames, err := reader.ManifestTagsForReference(ref) 136 if err != nil { 137 return nil, err 138 } 139 140 if len(destNames) == 0 { 141 destName, err := getImageDigest(ctx, ref, sc) 142 if err != nil { 143 return nil, err 144 } 145 destNames = append(destNames, destName) 146 } else { 147 for i := range destNames { 148 ref, err := NormalizedTag(destNames[i]) 149 if err != nil { 150 return nil, err 151 } 152 destNames[i] = ref.String() 153 } 154 } 155 156 refPairs := []pullRefPair{} 157 for _, destName := range destNames { 158 destRef, err := is.Transport.ParseStoreReference(ir.store, destName) 159 if err != nil { 160 return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName) 161 } 162 pair := pullRefPair{ 163 image: destName, 164 srcRef: ref, 165 dstRef: destRef, 166 } 167 refPairs = append(refPairs, pair) 168 } 169 170 return refPairs, nil 171 } 172 173 // pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport. 174 // Note that callers are responsible for invoking (*pullGoal).cleanUp() to clean up possibly open resources. 175 func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) { 176 span, _ := opentracing.StartSpanFromContext(ctx, "pullGoalFromImageReference") 177 defer span.Finish() 178 179 // supports pulling from docker-archive, oci, and registries 180 switch srcRef.Transport().Name() { 181 case DockerArchive: 182 reader, readerRef, err := archive.NewReaderForReference(sc, srcRef) 183 if err != nil { 184 return nil, err 185 } 186 187 pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, readerRef, sc) 188 if err != nil { 189 // No need to defer for a single error path. 190 if err := reader.Close(); err != nil { 191 logrus.Error(err.Error()) 192 } 193 return nil, err 194 } 195 196 return &pullGoal{ 197 pullAllPairs: true, 198 refPairs: pairs, 199 cleanUpFuncs: []cleanUpFunc{reader.Close}, 200 }, nil 201 202 case OCIArchive: 203 // retrieve the manifest from index.json to access the image name 204 manifest, err := ociarchive.LoadManifestDescriptor(srcRef) 205 if err != nil { 206 return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef) 207 } 208 var dest string 209 if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { 210 // If the input image has no image.ref.name, we need to feed it a dest anyways 211 // use the hex of the digest 212 dest, err = getImageDigest(ctx, srcRef, sc) 213 if err != nil { 214 return nil, errors.Wrapf(err, "error getting image digest; image reference not found") 215 } 216 } else { 217 dest = manifest.Annotations["org.opencontainers.image.ref.name"] 218 } 219 return ir.getSinglePullRefPairGoal(srcRef, dest) 220 221 case DirTransport: 222 image := toLocalImageName(srcRef.StringWithinTransport()) 223 return ir.getSinglePullRefPairGoal(srcRef, image) 224 225 case OCIDirTransport: 226 split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2) 227 image := toLocalImageName(split[0]) 228 return ir.getSinglePullRefPairGoal(srcRef, image) 229 230 default: 231 return ir.getSinglePullRefPairGoal(srcRef, imgName) 232 } 233 } 234 235 // toLocalImageName converts an image name into a 'localhost/' prefixed one 236 func toLocalImageName(imageName string) string { 237 return fmt.Sprintf( 238 "%s/%s", 239 DefaultLocalRegistry, 240 strings.TrimLeft(imageName, "/"), 241 ) 242 } 243 244 // pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries. 245 // Use pullImageFromReference if the source is known precisely. 246 func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) { 247 span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromHeuristicSource") 248 defer span.Finish() 249 250 var goal *pullGoal 251 sc := GetSystemContext(signaturePolicyPath, authfile, false) 252 if dockerOptions != nil { 253 sc.OSChoice = dockerOptions.OSChoice 254 sc.ArchitectureChoice = dockerOptions.ArchitectureChoice 255 sc.VariantChoice = dockerOptions.VariantChoice 256 } 257 if signaturePolicyPath == "" { 258 sc.SignaturePolicyPath = ir.SignaturePolicyPath 259 } 260 sc.BlobInfoCacheDir = filepath.Join(ir.store.GraphRoot(), "cache") 261 srcRef, err := alltransports.ParseImageName(inputName) 262 if err != nil { 263 // We might be pulling with an unqualified image reference in which case 264 // we need to make sure that we're not using any other transport. 265 srcTransport := alltransports.TransportFromImageName(inputName) 266 if srcTransport != nil && srcTransport.Name() != DockerTransport { 267 return nil, err 268 } 269 goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName) 270 if err != nil { 271 return nil, errors.Wrap(err, "error getting default registries to try") 272 } 273 } else { 274 goal, err = ir.pullGoalFromImageReference(ctx, srcRef, inputName, sc) 275 if err != nil { 276 return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName) 277 } 278 } 279 defer goal.cleanUp() 280 return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label) 281 } 282 283 // pullImageFromReference pulls an image from a types.imageReference. 284 func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions) ([]string, error) { 285 span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromReference") 286 defer span.Finish() 287 288 sc := GetSystemContext(signaturePolicyPath, authfile, false) 289 if dockerOptions != nil { 290 sc.OSChoice = dockerOptions.OSChoice 291 sc.ArchitectureChoice = dockerOptions.ArchitectureChoice 292 sc.VariantChoice = dockerOptions.VariantChoice 293 } 294 goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc) 295 if err != nil { 296 return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) 297 } 298 defer goal.cleanUp() 299 return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil) 300 } 301 302 func cleanErrorMessage(err error) string { 303 errMessage := strings.TrimPrefix(errors.Cause(err).Error(), "errors:\n") 304 errMessage = strings.Split(errMessage, "\n")[0] 305 return fmt.Sprintf(" %s\n", errMessage) 306 } 307 308 // doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead. 309 func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) { 310 span, _ := opentracing.StartSpanFromContext(ctx, "doPullImage") 311 defer span.Finish() 312 313 policyContext, err := getPolicyContext(sc) 314 if err != nil { 315 return nil, err 316 } 317 defer func() { 318 if err := policyContext.Destroy(); err != nil { 319 logrus.Errorf("failed to destroy policy context: %q", err) 320 } 321 }() 322 323 systemRegistriesConfPath := registries.SystemRegistriesConfPath() 324 325 var ( 326 images []string 327 pullErrors []error 328 ) 329 330 for _, imageInfo := range goal.refPairs { 331 copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil) 332 copyOptions.SourceCtx.SystemRegistriesConfPath = systemRegistriesConfPath // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place. 333 // Print the following statement only when pulling from a docker or atomic registry 334 if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) { 335 if _, err := io.WriteString(writer, fmt.Sprintf("Trying to pull %s...\n", imageInfo.image)); err != nil { 336 return nil, err 337 } 338 } 339 // If the label is not nil, check if the label exists and if not, return err 340 if label != nil { 341 if err := checkRemoteImageForLabel(ctx, *label, imageInfo, sc); err != nil { 342 return nil, err 343 } 344 } 345 imageInfo := imageInfo 346 if err = retry.RetryIfNecessary(ctx, func() error { 347 _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) 348 return err 349 }, retryOptions); err != nil { 350 pullErrors = append(pullErrors, err) 351 logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) 352 if writer != nil { 353 _, _ = io.WriteString(writer, cleanErrorMessage(err)) 354 } 355 } else { 356 if imageInfo.resolvedShortname != nil { 357 if err := imageInfo.resolvedShortname.Record(); err != nil { 358 logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err) 359 } 360 } 361 if !goal.pullAllPairs { 362 ir.newImageEvent(events.Pull, "") 363 return []string{imageInfo.image}, nil 364 } 365 images = append(images, imageInfo.image) 366 } 367 } 368 // If no image was found, we should handle. Lets be nicer to the user 369 // and see if we can figure out why. 370 if len(images) == 0 { 371 if goal.resolved != nil { 372 return nil, goal.resolved.FormatPullErrors(pullErrors) 373 } 374 return nil, errorhandling.JoinErrors(pullErrors) 375 } 376 377 ir.newImageEvent(events.Pull, images[0]) 378 return images, nil 379 } 380 381 // getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment 382 // variable. If it's "on", return `nil` to use the defaults from 383 // containers/image and the registries.conf files on the system. If it's 384 // "off", empty or unset, return types.ShortNameModeDisabled to turn off 385 // short-name aliasing by default. 386 // 387 // TODO: remove this function once we want to default to short-name aliasing. 388 func getShortNameMode() *types.ShortNameMode { 389 env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING") 390 if strings.ToLower(env) == "on" { 391 return nil // default to whatever registries.conf and c/image decide 392 } 393 mode := types.ShortNameModeDisabled 394 return &mode 395 } 396 397 // pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible 398 // image references to try pulling in combination with the registries.conf file as well 399 func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) { 400 if sys == nil { 401 sys = &types.SystemContext{} 402 } 403 sys.ShortNameMode = getShortNameMode() 404 405 resolved, err := shortnames.Resolve(sys, inputName) 406 if err != nil { 407 return nil, err 408 } 409 410 if desc := resolved.Description(); len(desc) > 0 { 411 logrus.Debug(desc) 412 if writer != nil { 413 if _, err := writer.Write([]byte(desc + "\n")); err != nil { 414 return nil, err 415 } 416 } 417 } 418 419 refPairs := []pullRefPair{} 420 for i, candidate := range resolved.PullCandidates { 421 srcRef, err := docker.NewReference(candidate.Value) 422 if err != nil { 423 return nil, err 424 } 425 ps, err := ir.getPullRefPair(srcRef, candidate.Value.String()) 426 if err != nil { 427 return nil, err 428 } 429 ps.resolvedShortname = &resolved.PullCandidates[i] 430 refPairs = append(refPairs, ps) 431 } 432 return &pullGoal{ 433 refPairs: refPairs, 434 pullAllPairs: false, 435 shortName: inputName, 436 resolved: resolved, 437 }, nil 438 } 439 440 // checkRemoteImageForLabel checks if the remote image has a specific label. if the label exists, we 441 // return nil, else we return an error 442 func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullRefPair, sc *types.SystemContext) error { 443 labelImage, err := imageInfo.srcRef.NewImage(ctx, sc) 444 if err != nil { 445 return err 446 } 447 remoteInspect, err := labelImage.Inspect(ctx) 448 if err != nil { 449 return err 450 } 451 // Labels are case insensitive; so we iterate instead of simple lookup 452 for k := range remoteInspect.Labels { 453 if strings.ToLower(label) == strings.ToLower(k) { 454 return nil 455 } 456 } 457 return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels) 458 }