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
}