func()

in agent/engine/docker_task_engine.go [1566:1703]


func (engine *DockerTaskEngine) pullAndUpdateContainerReference(task *apitask.Task, container *apicontainer.Container) dockerapi.DockerContainerMetadata {
	// If a task is blocked here for some time, and before it starts pulling image,
	// the task's desired status is set to stopped, then don't pull the image
	if task.GetDesiredStatus() == apitaskstatus.TaskStopped {
		logger.Warn("Task's desired status is stopped, skipping image pull for container", logger.Fields{
			field.TaskID:    task.GetID(),
			field.Container: container.Name,
			field.Image:     container.Image,
		})
		container.SetDesiredStatus(apicontainerstatus.ContainerStopped)
		return dockerapi.DockerContainerMetadata{Error: TaskStoppedBeforePullBeginError{task.Arn}}
	}

	// Set registry auth credentials if required and clearCreds them when no longer needed
	clearCreds, err := engine.setRegistryCredentials(container, task)
	if err != nil {
		return dockerapi.DockerContainerMetadata{Error: err}
	}
	if clearCreds != nil {
		defer clearCreds()
	}

	imageRef := container.Image
	imageDigest := container.GetImageDigest()
	if imageDigest != "" {
		// If image digest is available then prepare a canonical reference to be used for image pull.
		// This ensures that the image version referenced by the digest is pulled.
		canonicalRef, err := referenceutil.GetCanonicalRef(imageRef, imageDigest)
		if err != nil {
			logger.Error("Failed to prepare a canonical reference. Cannot pull image.", logger.Fields{
				field.TaskID:      task.GetID(),
				field.Container:   container.Name,
				field.Image:       imageRef,
				field.ImageDigest: imageDigest,
				field.Error:       err,
			})
			return dockerapi.DockerContainerMetadata{
				Error: dockerapi.CannotPullContainerError{
					FromError: fmt.Errorf(
						"failed to prepare a canonical reference with image '%s' and digest '%s': %w",
						imageRef, imageDigest, err),
				},
			}
		}
		imageRef = canonicalRef.String()
		logger.Info("Prepared a canonical reference for image pull", logger.Fields{
			field.TaskID:      task.GetID(),
			field.Container:   container.Name,
			field.Image:       container.Image,
			field.ImageDigest: imageDigest,
			field.ImageRef:    imageRef,
		})
	}

	metadata := engine.client.PullImage(engine.ctx, imageRef, container.RegistryAuthentication, engine.cfg.ImagePullTimeout)

	// Don't add internal images(created by ecs-agent) into image manager state
	if container.IsInternal() {
		return metadata
	}
	pullSucceeded := metadata.Error == nil

	if pullSucceeded && imageRef != container.Image && !referenceutil.DigestExists(container.Image) {
		// Resolved image manifest digest was used to pull the image.
		// Tag the pulled image so that it can be found using the image reference in the task.
		ctx, cancel := context.WithTimeout(engine.ctx, tagImageTimeout)
		defer cancel()
		err := engine.client.TagImage(ctx, imageRef, container.Image)
		if err != nil {
			logger.Error("Failed to tag image after pull", logger.Fields{
				field.TaskID:    task.GetID(),
				field.Container: container.Name,
				field.Image:     container.Image,
				field.ImageRef:  imageRef,
				field.Error:     err,
			})
			if errors.Is(err, context.DeadlineExceeded) {
				metadata.Error = &dockerapi.DockerTimeoutError{
					Duration:   tagImageTimeout,
					Transition: "pulled",
				}
			} else {
				metadata.Error = &dockerapi.CannotPullContainerError{FromError: err}
			}
			pullSucceeded = false
		} else {
			logger.Info("Successfully tagged image", logger.Fields{
				field.TaskID:    task.GetID(),
				field.Container: container.Name,
				field.Image:     container.Image,
				field.ImageRef:  imageRef,
			})
		}
	}

	findCachedImage := false
	if !pullSucceeded {
		// Extend error message
		metadata.Error = apierrormsgs.AugmentNamedErrMsg(metadata.Error)
		// If Agent failed to pull an image when
		// 1. DependentContainersPullUpfront is enabled
		// 2. ImagePullBehavior is not set to always
		// search the image in local cached images
		if engine.cfg.DependentContainersPullUpfront.Enabled() && engine.cfg.ImagePullBehavior != config.ImagePullAlwaysBehavior {
			if _, err := engine.client.InspectImage(imageRef); err != nil {
				logger.Error("Failed to find cached image for container", logger.Fields{
					field.TaskID:    task.GetID(),
					field.Container: container.Name,
					field.Image:     imageRef,
					field.Error:     err,
				})
				// Stop the task if the container is an essential container,
				// and the image is not available in both remote and local caches
				if container.IsEssential() {
					task.SetDesiredStatus(apitaskstatus.TaskStopped)
					engine.EmitTaskEvent(task, fmt.Sprintf("%s: %s", metadata.Error.ErrorName(), metadata.Error.Error()))
				}
				return dockerapi.DockerContainerMetadata{Error: metadata.Error}
			}
			logger.Info("Found cached image, use it directly for container", logger.Fields{
				field.TaskID:    task.GetID(),
				field.Container: container.Name,
				field.Image:     imageRef,
			})
			findCachedImage = true
		}
	}

	if pullSucceeded || findCachedImage {
		dockerContainer := &apicontainer.DockerContainer{
			Container: container,
		}
		engine.state.AddPulledContainer(dockerContainer, task)
	}

	engine.updateContainerReference(pullSucceeded, container, task.GetID())
	return metadata
}