func (task *Task) initializeDockerVolumes(sharedVolumeMatchFullConfig bool, dockerClient dockerapi.DockerClient, ctx context.Context) error {
	for i, vol := range task.Volumes {
		// No need to do this for non-docker volume, eg: host bind/empty volume
		if vol.Type != DockerVolumeType {
			continue
		}

		dockerVolume, ok := vol.Volume.(*taskresourcevolume.DockerVolumeConfig)
		if !ok {
			return errors.New("task volume: volume configuration does not match the type 'docker'")
		}
		// Agent needs to create task-scoped volume
		if dockerVolume.Scope == taskresourcevolume.TaskScope {
			err := task.addTaskScopedVolumes(ctx, dockerClient, &task.Volumes[i])
			if err != nil {
				return err
			}
		} else {
			// Agent needs to create shared volume if that's auto provisioned
			err := task.addSharedVolumes(sharedVolumeMatchFullConfig, ctx, dockerClient, &task.Volumes[i])
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// initializeDockerVolumes checks the volume resource in the task to determine if the agent
// should create the volume before creating the container

initializeDockerVolumes checks the volume resource in the task to determine if the agent should create the volume before creating the container


func (task *Task) addTaskScopedVolumes(ctx context.Context, dockerClient dockerapi.DockerClient,
	vol *TaskVolume) error {

	volumeConfig := vol.Volume.(*taskresourcevolume.DockerVolumeConfig)
	volumeResource, err := taskresourcevolume.NewVolumeResource(
		ctx,
		vol.Name,
		task.volumeName(vol.Name),
		volumeConfig.Scope, volumeConfig.Autoprovision,
		volumeConfig.Driver, volumeConfig.DriverOpts,
		volumeConfig.Labels, dockerClient)
	if err != nil {
		return err
	}

	vol.Volume = &volumeResource.VolumeConfig
	task.AddResource(resourcetype.DockerVolumeKey, volumeResource)
	task.updateContainerVolumeDependency(vol.Name)
	return nil
}

func ( task * Task ) addTaskScopedVolumes ( ctx context . Context , dockerClient dockerapi . DockerClient , vol * TaskVolume ) error { volumeConfig := vol . Volume . ( * taskresourcevolume . DockerVolumeConfig ) volumeResource , err := taskresourcevolume . NewVolumeResource ( ctx , vol . Name , task . volumeName ( vol . Name ) , volumeConfig . Scope , volumeConfig . Autoprovision , volumeConfig . Driver , volumeConfig . DriverOpts , volumeConfig . Labels , dockerClient ) if err != nil { return err } vol . Volume = & volumeResource . VolumeConfig task . AddResource ( resourcetype . DockerVolumeKey , volumeResource ) task . updateContainerVolumeDependency ( vol . Name ) return nil }

// addTaskScopedVolumes adds the task scoped volume into task resources and updates container dependency


func (task *Task) updateContainerVolumeDependency(name string) {
	// Find all the container that depends on the volume
	for _, container := range task.Containers {
		for _, mountpoint := range container.MountPoints {
			if mountpoint.SourceVolume == name {
				container.BuildResourceDependency(name,
					resourcestatus.ResourceCreated,
					apicontainerstatus.ContainerPulled)
			}
		}
	}
}

// updateContainerVolumeDependency adds the volume resource to container dependency


func (task *Task) initializeCredentialsEndpoint(credentialsManager credentials.Manager) {
	id := task.GetCredentialsID()
	if id == "" {
		// No credentials set for the task. Do not inject the endpoint environment variable.
		return
	}
	taskCredentials, ok := credentialsManager.GetTaskCredentials(id)
	if !ok {
		// Task has credentials id set, but credentials manager is unaware of
		// the id. This should never happen as the payload handler sets
		// credentialsId for the task after adding credentials to the
		// credentials manager
		seelog.Errorf("Unable to get credentials for task: %s", task.Arn)
		return
	}

	credentialsEndpointRelativeURI := taskCredentials.IAMRoleCredentials.GenerateCredentialsEndpointRelativeURI()
	for _, container := range task.Containers {
		// container.Environment map would not be initialized if there are
		// no environment variables to be set or overridden in the container
		// config. Check if that's the case and initilialize if needed
		if container.Environment == nil {
			container.Environment = make(map[string]string)
		}
		container.Environment[awsSDKCredentialsRelativeURIPathEnvironmentVariableName] = credentialsEndpointRelativeURI
	}

}

// initializeCredentialsEndpoint sets the credentials endpoint for all containers in a task if needed.

initializeCredentialsEndpoint sets the credentials endpoint for all containers in a task if needed .


func (task *Task) initializeContainersV3MetadataEndpoint(uuidProvider utils.UUIDProvider) {
	for _, container := range task.Containers {
		v3EndpointID := container.GetV3EndpointID()
		if v3EndpointID == "" { // if container's v3 endpoint has not been set
			container.SetV3EndpointID(uuidProvider.New())
		}

		container.InjectV3MetadataEndpoint()
	}
}

// initializeContainersV3MetadataEndpoint generates an v3 endpoint id for each container, constructs the
// v3 metadata endpoint, and injects it as an environment variable

initializeContainersV3MetadataEndpoint generates an v3 endpoint id for each container constructs the v3 metadata endpoint and injects it as an environment variable


func (task *Task) requiresASMDockerAuthData() bool {
	for _, container := range task.Containers {
		if container.ShouldPullWithASMAuth() {
			return true
		}
	}
	return false
}

func ( task * Task ) requiresASMDockerAuthData ( ) bool { for _ , container := range task . Containers { if container . ShouldPullWithASMAuth ( ) { return true } } return false }

// requiresASMDockerAuthData returns true if atleast one container in the task
// needs to retrieve private registry authentication data from ASM

requiresASMDockerAuthData returns true if atleast one container in the task needs to retrieve private registry authentication data from ASM


func (task *Task) initializeASMAuthResource(credentialsManager credentials.Manager,
	resourceFields *taskresource.ResourceFields) {
	asmAuthResource := asmauth.NewASMAuthResource(task.Arn, task.getAllASMAuthDataRequirements(),
		task.ExecutionCredentialsID, credentialsManager, resourceFields.ASMClientCreator)
	task.AddResource(asmauth.ResourceName, asmAuthResource)
	for _, container := range task.Containers {
		if container.ShouldPullWithASMAuth() {
			container.BuildResourceDependency(asmAuthResource.GetName(),
				resourcestatus.ResourceStatus(asmauth.ASMAuthStatusCreated),
				apicontainerstatus.ContainerPulled)
		}
	}
}

func ( task * Task ) initializeASMAuthResource ( credentialsManager credentials . Manager , resourceFields * taskresource . ResourceFields ) { asmAuthResource := asmauth . NewASMAuthResource ( task . Arn , task . getAllASMAuthDataRequirements ( ) , task . ExecutionCredentialsID , credentialsManager , resourceFields . ASMClientCreator ) task . AddResource ( asmauth . ResourceName , asmAuthResource ) for _ , container := range task . Containers { if container . ShouldPullWithASMAuth ( ) { container . BuildResourceDependency ( asmAuthResource . GetName ( ) , resourcestatus . ResourceStatus ( asmauth . ASMAuthStatusCreated ) , apicontainerstatus . ContainerPulled ) } } }

// initializeASMAuthResource builds the resource dependency map for the ASM auth resource


func (task *Task) requiresSSMSecret() bool {
	for _, container := range task.Containers {
		if container.ShouldCreateWithSSMSecret() {
			return true
		}
	}
	return false
}

func ( task * Task ) requiresSSMSecret ( ) bool { for _ , container := range task . Containers { if container . ShouldCreateWithSSMSecret ( ) { return true } } return false }

// requiresSSMSecret returns true if at least one container in the task
// needs to retrieve secret from SSM parameter

requiresSSMSecret returns true if at least one container in the task needs to retrieve secret from SSM parameter


func (task *Task) initializeSSMSecretResource(credentialsManager credentials.Manager,
	resourceFields *taskresource.ResourceFields) {
	ssmSecretResource := ssmsecret.NewSSMSecretResource(task.Arn, task.getAllSSMSecretRequirements(),
		task.ExecutionCredentialsID, credentialsManager, resourceFields.SSMClientCreator)
	task.AddResource(ssmsecret.ResourceName, ssmSecretResource)

	// for every container that needs ssm secret vending as env, it needs to wait all secrets got retrieved
	for _, container := range task.Containers {
		if container.ShouldCreateWithSSMSecret() {
			container.BuildResourceDependency(ssmSecretResource.GetName(),
				resourcestatus.ResourceStatus(ssmsecret.SSMSecretCreated),
				apicontainerstatus.ContainerCreated)
		}
	}
}

// initializeSSMSecretResource builds the resource dependency map for the SSM ssmsecret resource


func (task *Task) getAllSSMSecretRequirements() map[string][]apicontainer.Secret {
	reqs := make(map[string][]apicontainer.Secret)

	for _, container := range task.Containers {
		for _, secret := range container.Secrets {
			if secret.Provider == apicontainer.SecretProviderSSM {
				if _, ok := reqs[secret.Region]; !ok {
					reqs[secret.Region] = []apicontainer.Secret{}
				}

				reqs[secret.Region] = append(reqs[secret.Region], secret)
			}
		}
	}
	return reqs
}

func ( task * Task ) getAllSSMSecretRequirements ( ) map [ string ] [ ] apicontainer . Secret { reqs := make ( map [ string ] [ ] apicontainer . Secret ) for _ , container := range task . Containers { for _ , secret := range container . Secrets { if secret . Provider == apicontainer . SecretProviderSSM { if _ , ok := reqs [ secret . Region ] ; ! ok { reqs [ secret . Region ] = [ ] apicontainer . Secret { } } reqs [ secret . Region ] = append ( reqs [ secret . Region ] , secret ) } } } return reqs }

// getAllSSMSecretRequirements stores all secrets in a map whose key is region and value is all
// secrets in that region

getAllSSMSecretRequirements stores all secrets in a map whose key is region and value is all secrets in that region


func (task *Task) requiresASMSecret() bool {
	for _, container := range task.Containers {
		if container.ShouldCreateWithASMSecret() {
			return true
		}
	}
	return false
}

func ( task * Task ) requiresASMSecret ( ) bool { for _ , container := range task . Containers { if container . ShouldCreateWithASMSecret ( ) { return true } } return false }

// requiresASMSecret returns true if at least one container in the task
// needs to retrieve secret from AWS Secrets Manager

requiresASMSecret returns true if at least one container in the task needs to retrieve secret from AWS Secrets Manager


func (task *Task) initializeASMSecretResource(credentialsManager credentials.Manager,
	resourceFields *taskresource.ResourceFields) {
	asmSecretResource := asmsecret.NewASMSecretResource(task.Arn, task.getAllASMSecretRequirements(),
		task.ExecutionCredentialsID, credentialsManager, resourceFields.ASMClientCreator)
	task.AddResource(asmsecret.ResourceName, asmSecretResource)

	// for every container that needs asm secret vending as envvar, it needs to wait all secrets got retrieved
	for _, container := range task.Containers {
		if container.ShouldCreateWithASMSecret() {
			container.BuildResourceDependency(asmSecretResource.GetName(),
				resourcestatus.ResourceStatus(asmsecret.ASMSecretCreated),
				apicontainerstatus.ContainerCreated)
		}
	}
}

// initializeASMSecretResource builds the resource dependency map for the asmsecret resource


func (task *Task) getAllASMSecretRequirements() map[string]apicontainer.Secret {
	reqs := make(map[string]apicontainer.Secret)

	for _, container := range task.Containers {
		for _, secret := range container.Secrets {
			if secret.Provider == apicontainer.SecretProviderASM {
				secretKey := secret.GetSecretResourceCacheKey()
				if _, ok := reqs[secretKey]; !ok {
					reqs[secretKey] = secret
				}
			}
		}
	}
	return reqs
}

func ( task * Task ) getAllASMSecretRequirements ( ) map [ string ] apicontainer . Secret { reqs := make ( map [ string ] apicontainer . Secret ) for _ , container := range task . Containers { for _ , secret := range container . Secrets { if secret . Provider == apicontainer . SecretProviderASM { secretKey := secret . GetSecretResourceCacheKey ( ) if _ , ok := reqs [ secretKey ] ; ! ok { reqs [ secretKey ] = secret } } } } return reqs }

// getAllASMSecretRequirements stores secrets in a task in a map


func (task *Task) BuildCNIConfig() (*ecscni.Config, error) {
	if !task.isNetworkModeVPC() {
		return nil, errors.New("task config: task has no ENIs associated with it, unable to generate cni config")
	}

	cfg := &ecscni.Config{}
	convertENIToCNIConfig(task.GetTaskENI(), cfg)
	if task.GetAppMesh() != nil {
		convertAppMeshToCNIConfig(task.GetAppMesh(), cfg)
	}

	return cfg, nil
}

func ( task * Task ) BuildCNIConfig ( ) ( * ecscni . Config , error ) { if ! task . isNetworkModeVPC ( ) { return nil , errors . New ( " " ) } cfg := & ecscni . Config { } convertENIToCNIConfig ( task . GetTaskENI ( ) , cfg ) if task . GetAppMesh ( ) != nil { convertAppMeshToCNIConfig ( task . GetAppMesh ( ) , cfg ) } return cfg , nil }

// BuildCNIConfig constructs the cni configuration from eni


func convertENIToCNIConfig(eni *apieni.ENI, cfg *ecscni.Config) {
	cfg.ENIID = eni.ID
	cfg.ID = eni.MacAddress
	cfg.ENIMACAddress = eni.MacAddress
	cfg.SubnetGatewayIPV4Address = eni.GetSubnetGatewayIPV4Address()
	for _, ipv4 := range eni.IPV4Addresses {
		if ipv4.Primary {
			cfg.ENIIPV4Address = ipv4.Address
			break
		}
	}
	// If there is ipv6 assigned to eni then set it
	if len(eni.IPV6Addresses) > 0 {
		cfg.ENIIPV6Address = eni.IPV6Addresses[0].Address
	}
}

// convertENIToCNIConfig converts input eni config into cni config


func convertAppMeshToCNIConfig(appMesh *apiappmesh.AppMesh, cfg *ecscni.Config) {
	cfg.AppMeshCNIEnabled = true
	cfg.IgnoredUID = appMesh.IgnoredUID
	cfg.IgnoredGID = appMesh.IgnoredGID
	cfg.ProxyIngressPort = appMesh.ProxyIngressPort
	cfg.ProxyEgressPort = appMesh.ProxyEgressPort
	cfg.AppPorts = appMesh.AppPorts
	cfg.EgressIgnoredIPs = appMesh.EgressIgnoredIPs
	cfg.EgressIgnoredPorts = appMesh.EgressIgnoredPorts

}

func convertAppMeshToCNIConfig ( appMesh * apiappmesh . AppMesh , cfg * ecscni . Config ) { cfg . AppMeshCNIEnabled = true cfg . IgnoredUID = appMesh . IgnoredUID cfg . IgnoredGID = appMesh . IgnoredGID cfg . ProxyIngressPort = appMesh . ProxyIngressPort cfg . ProxyEgressPort = appMesh . ProxyEgressPort cfg . AppPorts = appMesh . AppPorts cfg . EgressIgnoredIPs = appMesh . EgressIgnoredIPs cfg . EgressIgnoredPorts = appMesh . EgressIgnoredPorts }

// convertAppMeshToCNIConfig converts input app mesh config into cni config


func (task *Task) HostVolumeByName(name string) (taskresourcevolume.Volume, bool) {
	for _, v := range task.Volumes {
		if v.Name == name {
			return v.Volume, true
		}
	}
	return nil, false
}

func ( task * Task ) HostVolumeByName ( name string ) ( taskresourcevolume . Volume , bool ) { for _ , v := range task . Volumes { if v . Name == name { return v . Volume , true } } return nil , false }

// HostVolumeByName returns the task Volume for the given a volume name in that
// task. The second return value indicates the presence of that volume

HostVolumeByName returns the task Volume for the given a volume name in that task . The second return value indicates the presence of that volume


func (task *Task) UpdateMountPoints(cont *apicontainer.Container, vols []types.MountPoint) {
	for _, mountPoint := range cont.MountPoints {
		containerPath := getCanonicalPath(mountPoint.ContainerPath)
		for _, vol := range vols {
			if strings.Compare(vol.Destination, containerPath) == 0 ||
				// /path/ -> /path or \path\ -> \path
				strings.Compare(vol.Destination, strings.TrimRight(containerPath, string(filepath.Separator))) == 0 {
				if hostVolume, exists := task.HostVolumeByName(mountPoint.SourceVolume); exists {
					if empty, ok := hostVolume.(*taskresourcevolume.LocalDockerVolume); ok {
						empty.HostPath = vol.Source
					}
				}
			}
		}
	}
}

func
(
task
*
Task
)
UpdateMountPoints
(
cont
*
apicontainer
.
Container
,
vols
[
]
types
.
MountPoint
)
{
for
_
,
mountPoint
:=
range
cont
.
MountPoints
{
containerPath
:=
getCanonicalPath
(
mountPoint
.
ContainerPath
)
for
_
,
vol
:=
range
vols
{
if
strings
.
Compare
(
vol
.
Destination
,
containerPath
)
==
0
||
// /path/ -> /path or \path\ -> \path
strings
.
Compare
(
vol
.
Destination
,
strings
.
TrimRight
(
containerPath
,
string
(
filepath
.
Separator
)
)
)
==
0
{
if
hostVolume
,
exists
:=
task
.
HostVolumeByName
(
mountPoint
.
SourceVolume
)
;
exists
{
if
empty
,
ok
:=
hostVolume
.
(
*
taskresourcevolume
.
LocalDockerVolume
)
;
ok
{
empty
.
HostPath
=
vol
.
Source
}
}
}
}
}
}

// UpdateMountPoints updates the mount points of volumes that were created
// without specifying a host path.  This is used as part of the empty host
// volume feature.

UpdateMountPoints updates the mount points of volumes that were created without specifying a host path . This is used as part of the empty host volume feature .


func (task *Task) DockerConfig(container *apicontainer.Container, apiVersion dockerclient.DockerVersion) (*dockercontainer.Config, *apierrors.DockerClientConfigError) {
	return task.dockerConfig(container, apiVersion)
}

func ( task * Task ) DockerConfig ( container * apicontainer . Container , apiVersion dockerclient . DockerVersion ) ( * dockercontainer . Config , * apierrors . DockerClientConfigError ) { return task . dockerConfig ( container , apiVersion ) }

// DockerConfig converts the given container in this task to the format of
// the Docker SDK 'Config' struct

DockerConfig converts the given container in this task to the format of the Docker SDK Config struct


func (task *Task) DockerHostConfig(container *apicontainer.Container, dockerContainerMap map[string]*apicontainer.DockerContainer, apiVersion dockerclient.DockerVersion) (*dockercontainer.HostConfig, *apierrors.HostConfigError) {
	return task.dockerHostConfig(container, dockerContainerMap, apiVersion)
}

func ( task * Task ) DockerHostConfig ( container * apicontainer . Container , dockerContainerMap map [ string ] * apicontainer . DockerContainer , apiVersion dockerclient . DockerVersion ) ( * dockercontainer . HostConfig , * apierrors . HostConfigError ) { return task . dockerHostConfig ( container , dockerContainerMap , apiVersion ) }

// DockerHostConfig construct the configuration recognized by docker


func (task *Task) ApplyExecutionRoleLogsAuth(hostConfig *dockercontainer.HostConfig, credentialsManager credentials.Manager) *apierrors.HostConfigError {
	id := task.GetExecutionCredentialsID()
	if id == "" {
		// No execution credentials set for the task. Do not inject the endpoint environment variable.
		return &apierrors.HostConfigError{Msg: "No execution credentials set for the task"}
	}

	executionRoleCredentials, ok := credentialsManager.GetTaskCredentials(id)
	if !ok {
		// Task has credentials id set, but credentials manager is unaware of
		// the id. This should never happen as the payload handler sets
		// credentialsId for the task after adding credentials to the
		// credentials manager
		return &apierrors.HostConfigError{Msg: "Unable to get execution role credentials for task"}
	}
	credentialsEndpointRelativeURI := executionRoleCredentials.IAMRoleCredentials.GenerateCredentialsEndpointRelativeURI()
	hostConfig.LogConfig.Config[awslogsCredsEndpointOpt] = credentialsEndpointRelativeURI
	return nil
}

// ApplyExecutionRoleLogsAuth will check whether the task has execution role
// credentials, and add the genereated credentials endpoint to the associated HostConfig

ApplyExecutionRoleLogsAuth will check whether the task has execution role credentials and add the genereated credentials endpoint to the associated HostConfig


func (task *Task) shouldOverrideNetworkMode(container *apicontainer.Container, dockerContainerMap map[string]*apicontainer.DockerContainer) (bool, string) {
	// TODO. We can do an early return here by determining which kind of task it is
	// Example: Does this task have ENIs in its payload, what is its networking mode etc
	if container.IsInternal() {
		// If it's an internal container, set the network mode to none.
		// Currently, internal containers are either for creating empty host
		// volumes or for creating the 'pause' container. Both of these
		// only need the network mode to be set to "none"
		return true, networkModeNone
	}

	// For other types of containers, determine if the container map contains
	// a pause container. Since a pause container is only added to the task
	// when using non docker daemon supported network modes, its existence
	// indicates the need to configure the network mode outside of supported
	// network drivers
	if task.GetTaskENI() == nil {
		return false, ""
	}

	pauseContName := ""
	for _, cont := range task.Containers {
		if cont.Type == apicontainer.ContainerCNIPause {
			pauseContName = cont.Name
			break
		}
	}
	if pauseContName == "" {
		seelog.Critical("Pause container required, but not found in the task: %s", task.String())
		return false, ""
	}
	pauseContainer, ok := dockerContainerMap[pauseContName]
	if !ok || pauseContainer == nil {
		// This should never be the case and implies a code-bug.
		seelog.Criticalf("Pause container required, but not found in container map for container: [%s] in task: %s",
			container.String(), task.String())
		return false, ""
	}
	return true, dockerMappingContainerPrefix + pauseContainer.DockerID
}

// shouldOverrideNetworkMode returns true if the network mode of the container needs
// to be overridden. It also returns the override string in this case. It returns
// false otherwise

shouldOverrideNetworkMode returns true if the network mode of the container needs to be overridden . It also returns the override string in this case . It returns false otherwise


func (task *Task) applyENIHostname(dockerConfig *dockercontainer.Config) *dockercontainer.Config {
	eni := task.GetTaskENI()
	if eni == nil {
		return dockerConfig
	}

	hostname := eni.GetHostname()
	if hostname == "" {
		return dockerConfig
	}

	dockerConfig.Hostname = hostname
	return dockerConfig
}

func ( task * Task ) applyENIHostname ( dockerConfig * dockercontainer . Config ) * dockercontainer . Config { eni := task . GetTaskENI ( ) if eni == nil { return dockerConfig } hostname := eni . GetHostname ( ) if hostname == " " { return dockerConfig } dockerConfig . Hostname = hostname return dockerConfig }

// applyENIHostname adds the hostname provided by the ENI message to the
// container's docker config. At the time of implmentation, we are only using it
// to configure the pause container for awsvpc tasks

applyENIHostname adds the hostname provided by the ENI message to the container s docker config . At the time of implmentation we are only using it to configure the pause container for awsvpc tasks


func (task *Task) shouldOverridePIDMode(container *apicontainer.Container, dockerContainerMap map[string]*apicontainer.DockerContainer) (bool, string) {
	// If the container is an internal container (ContainerEmptyHostVolume,
	// ContainerCNIPause, or ContainerNamespacePause), then PID namespace for
	// the container itself should be private (default Docker option)
	if container.IsInternal() {
		return false, ""
	}

	switch task.getPIDMode() {
	case pidModeHost:
		return true, pidModeHost

	case pidModeTask:
		pauseCont, ok := task.ContainerByName(NamespacePauseContainerName)
		if !ok {
			seelog.Criticalf("Namespace Pause container not found in the task: %s; Setting Task's Desired Status to Stopped", task.Arn)
			task.SetDesiredStatus(apitaskstatus.TaskStopped)
			return false, ""
		}
		pauseDockerID, ok := dockerContainerMap[pauseCont.Name]
		if !ok || pauseDockerID == nil {
			// Docker container shouldn't be nil or not exist if the Container definition within task exists; implies code-bug
			seelog.Criticalf("Namespace Pause docker container not found in the task: %s; Setting Task's Desired Status to Stopped", task.Arn)
			task.SetDesiredStatus(apitaskstatus.TaskStopped)
			return false, ""
		}
		return true, dockerMappingContainerPrefix + pauseDockerID.DockerID

		// If PIDMode is not Host or Task, then no need to override
	default:
		return false, ""
	}
}

// shouldOverridePIDMode returns true if the PIDMode of the container needs
// to be overridden. It also returns the override string in this case. It returns
// false otherwise

shouldOverridePIDMode returns true if the PIDMode of the container needs to be overridden . It also returns the override string in this case . It returns false otherwise


func (task *Task) shouldOverrideIPCMode(container *apicontainer.Container, dockerContainerMap map[string]*apicontainer.DockerContainer) (bool, string) {
	// All internal containers do not need the same IPCMode. The NamespaceContainerPause
	// needs to be "shareable" if ipcMode is "task". All other internal containers should
	// defer to the Docker daemon default option (either shareable or private depending on
	// version and configuration)
	if container.IsInternal() {
		if container.Type == apicontainer.ContainerNamespacePause {
			// Setting NamespaceContainerPause to be sharable with other containers
			if task.getIPCMode() == ipcModeTask {
				return true, ipcModeSharable
			}
		}
		// Defaulting to Docker daemon default option
		return false, ""
	}

	switch task.getIPCMode() {
	// No IPCMode provided in Task Definition, no need to override
	case "":
		return false, ""

		// IPCMode is none - container will have own private namespace with /dev/shm not mounted
	case ipcModeNone:
		return true, ipcModeNone

	case ipcModeHost:
		return true, ipcModeHost

	case ipcModeTask:
		pauseCont, ok := task.ContainerByName(NamespacePauseContainerName)
		if !ok {
			seelog.Criticalf("Namespace Pause container not found in the task: %s; Setting Task's Desired Status to Stopped", task.Arn)
			task.SetDesiredStatus(apitaskstatus.TaskStopped)
			return false, ""
		}
		pauseDockerID, ok := dockerContainerMap[pauseCont.Name]
		if !ok || pauseDockerID == nil {
			// Docker container shouldn't be nill or not exist if the Container definition within task exists; implies code-bug
			seelog.Criticalf("Namespace Pause container not found in the task: %s; Setting Task's Desired Status to Stopped", task.Arn)
			task.SetDesiredStatus(apitaskstatus.TaskStopped)
			return false, ""
		}
		return true, dockerMappingContainerPrefix + pauseDockerID.DockerID

	default:
		return false, ""
	}
}

// shouldOverrideIPCMode returns true if the IPCMode of the container needs
// to be overridden. It also returns the override string in this case. It returns
// false otherwise

shouldOverrideIPCMode returns true if the IPCMode of the container needs to be overridden . It also returns the override string in this case . It returns false otherwise


func (task *Task) UpdateStatus() bool {
	change := task.updateTaskKnownStatus()
	// DesiredStatus can change based on a new known status
	task.UpdateDesiredStatus()
	return change != apitaskstatus.TaskStatusNone
}

// UpdateStatus updates a task's known and desired statuses to be compatible
// with all of its containers
// It will return a bool indicating if there was a change

UpdateStatus updates a task s known and desired statuses to be compatible with all of its containers It will return a bool indicating if there was a change


func (task *Task) UpdateDesiredStatus() {
	task.lock.Lock()
	defer task.lock.Unlock()
	task.updateTaskDesiredStatusUnsafe()
	task.updateContainerDesiredStatusUnsafe(task.DesiredStatusUnsafe)
	task.updateResourceDesiredStatusUnsafe(task.DesiredStatusUnsafe)
}

func ( task * Task ) UpdateDesiredStatus ( ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . updateTaskDesiredStatusUnsafe ( ) task . updateContainerDesiredStatusUnsafe ( task . DesiredStatusUnsafe ) task . updateResourceDesiredStatusUnsafe ( task . DesiredStatusUnsafe ) }

// UpdateDesiredStatus sets the known status of the task


func (task *Task) SetKnownStatus(status apitaskstatus.TaskStatus) {
	task.setKnownStatus(status)
	task.updateKnownStatusTime()
}

func ( task * Task ) SetKnownStatus ( status apitaskstatus . TaskStatus ) { task . setKnownStatus ( status ) task . updateKnownStatusTime ( ) }

// SetKnownStatus sets the known status of the task


func (task *Task) GetKnownStatus() apitaskstatus.TaskStatus {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.KnownStatusUnsafe
}

func ( task * Task ) GetKnownStatus ( ) apitaskstatus . TaskStatus { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . KnownStatusUnsafe }

// GetKnownStatus gets the KnownStatus of the task


func (task *Task) GetKnownStatusTime() time.Time {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.KnownStatusTimeUnsafe
}

func ( task * Task ) GetKnownStatusTime ( ) time . Time { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . KnownStatusTimeUnsafe }

// GetKnownStatusTime gets the KnownStatusTime of the task


func (task *Task) SetCredentialsID(id string) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.credentialsID = id
}

func ( task * Task ) SetCredentialsID ( id string ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . credentialsID = id }

// SetCredentialsID sets the credentials ID for the task


func (task *Task) GetCredentialsID() string {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.credentialsID
}

func ( task * Task ) GetCredentialsID ( ) string { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . credentialsID }

// GetCredentialsID gets the credentials ID for the task


func (task *Task) SetExecutionRoleCredentialsID(id string) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.ExecutionCredentialsID = id
}

func ( task * Task ) SetExecutionRoleCredentialsID ( id string ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . ExecutionCredentialsID = id }

// SetExecutionRoleCredentialsID sets the ID for the task execution role credentials


func (task *Task) GetExecutionCredentialsID() string {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.ExecutionCredentialsID
}

func ( task * Task ) GetExecutionCredentialsID ( ) string { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . ExecutionCredentialsID }

// GetExecutionCredentialsID gets the credentials ID for the task


func (task *Task) GetDesiredStatus() apitaskstatus.TaskStatus {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.DesiredStatusUnsafe
}

func ( task * Task ) GetDesiredStatus ( ) apitaskstatus . TaskStatus { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . DesiredStatusUnsafe }

// GetDesiredStatus gets the desired status of the task


func (task *Task) SetDesiredStatus(status apitaskstatus.TaskStatus) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.DesiredStatusUnsafe = status
}

func ( task * Task ) SetDesiredStatus ( status apitaskstatus . TaskStatus ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . DesiredStatusUnsafe = status }

// SetDesiredStatus sets the desired status of the task


func (task *Task) GetSentStatus() apitaskstatus.TaskStatus {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.SentStatusUnsafe
}

func ( task * Task ) GetSentStatus ( ) apitaskstatus . TaskStatus { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . SentStatusUnsafe }

// GetSentStatus safely returns the SentStatus of the task


func (task *Task) SetSentStatus(status apitaskstatus.TaskStatus) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.SentStatusUnsafe = status
}

func ( task * Task ) SetSentStatus ( status apitaskstatus . TaskStatus ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . SentStatusUnsafe = status }

// SetSentStatus safely sets the SentStatus of the task


func (task *Task) SetTaskENI(eni *apieni.ENI) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.ENI = eni
}

func ( task * Task ) SetTaskENI ( eni * apieni . ENI ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . ENI = eni }

// SetTaskENI sets the eni information of the task


func (task *Task) GetTaskENI() *apieni.ENI {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.ENI
}

func ( task * Task ) GetTaskENI ( ) * apieni . ENI { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . ENI }

// GetTaskENI returns the eni of task, for now task can only have one enis

GetTaskENI returns the eni of task for now task can only have one enis


func (task *Task) SetAppMesh(appMesh *apiappmesh.AppMesh) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.AppMesh = appMesh
}

func ( task * Task ) SetAppMesh ( appMesh * apiappmesh . AppMesh ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . AppMesh = appMesh }

// SetAppMesh sets the app mesh config of the task


func (task *Task) GetAppMesh() *apiappmesh.AppMesh {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.AppMesh
}

func ( task * Task ) GetAppMesh ( ) * apiappmesh . AppMesh { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . AppMesh }

// GetAppMesh returns the app mesh config of the task


func (task *Task) GetStopSequenceNumber() int64 {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.StopSequenceNumber
}

func ( task * Task ) GetStopSequenceNumber ( ) int64 { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . StopSequenceNumber }

// GetStopSequenceNumber returns the stop sequence number of a task


func (task *Task) SetStopSequenceNumber(seqnum int64) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.StopSequenceNumber = seqnum
}

func ( task * Task ) SetStopSequenceNumber ( seqnum int64 ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . StopSequenceNumber = seqnum }

// SetStopSequenceNumber sets the stop seqence number of a task


func (task *Task) SetPullStartedAt(timestamp time.Time) bool {
	task.lock.Lock()
	defer task.lock.Unlock()

	// Only set this field if it is not set
	if task.PullStartedAtUnsafe.IsZero() {
		task.PullStartedAtUnsafe = timestamp
		return true
	}
	return false
}

func
(
task
*
Task
)
SetPullStartedAt
(
timestamp
time
.
Time
)
bool
{
task
.
lock
.
Lock
(
)
defer
task
.
lock
.
Unlock
(
)
// Only set this field if it is not set
if
task
.
PullStartedAtUnsafe
.
IsZero
(
)
{
task
.
PullStartedAtUnsafe
=
timestamp
return
true
}
return
false
}

// SetPullStartedAt sets the task pullstartedat timestamp and returns whether
// this field was updated or not

SetPullStartedAt sets the task pullstartedat timestamp and returns whether this field was updated or not


func (task *Task) GetPullStartedAt() time.Time {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.PullStartedAtUnsafe
}

func ( task * Task ) GetPullStartedAt ( ) time . Time { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . PullStartedAtUnsafe }

// GetPullStartedAt returns the PullStartedAt timestamp


func (task *Task) SetPullStoppedAt(timestamp time.Time) {
	task.lock.Lock()
	defer task.lock.Unlock()

	task.PullStoppedAtUnsafe = timestamp
}

func ( task * Task ) SetPullStoppedAt ( timestamp time . Time ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . PullStoppedAtUnsafe = timestamp }

// SetPullStoppedAt sets the task pullstoppedat timestamp


func (task *Task) GetPullStoppedAt() time.Time {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.PullStoppedAtUnsafe
}

func ( task * Task ) GetPullStoppedAt ( ) time . Time { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . PullStoppedAtUnsafe }

// GetPullStoppedAt returns the PullStoppedAt timestamp


func (task *Task) SetExecutionStoppedAt(timestamp time.Time) bool {
	task.lock.Lock()
	defer task.lock.Unlock()

	if task.ExecutionStoppedAtUnsafe.IsZero() {
		task.ExecutionStoppedAtUnsafe = timestamp
		return true
	}
	return false
}

func ( task * Task ) SetExecutionStoppedAt ( timestamp time . Time ) bool { task . lock . Lock ( ) defer task . lock . Unlock ( ) if task . ExecutionStoppedAtUnsafe . IsZero ( ) { task . ExecutionStoppedAtUnsafe = timestamp return true } return false }

// SetExecutionStoppedAt sets the ExecutionStoppedAt timestamp of the task


func (task *Task) GetExecutionStoppedAt() time.Time {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.ExecutionStoppedAtUnsafe
}

func ( task * Task ) GetExecutionStoppedAt ( ) time . Time { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . ExecutionStoppedAtUnsafe }

// GetExecutionStoppedAt returns the task executionStoppedAt timestamp


func (task *Task) stringUnsafe() string {
	res := fmt.Sprintf("%s:%s %s, TaskStatus: (%s->%s)",
		task.Family, task.Version, task.Arn,
		task.KnownStatusUnsafe.String(), task.DesiredStatusUnsafe.String())
	res += " Containers: ["
	for _, container := range task.Containers {
		res += fmt.Sprintf("%s (%s->%s),", container.Name, container.GetKnownStatus().String(), container.GetDesiredStatus().String())
	}

	if task.ENI != nil {
		res += fmt.Sprintf(" ENI: [%s]", task.ENI.String())
	}
	return res + "]"
}

func ( task * Task ) stringUnsafe ( ) string { res := fmt . Sprintf ( " " , task . Family , task . Version , task . Arn , task . KnownStatusUnsafe . String ( ) , task . DesiredStatusUnsafe . String ( ) ) res += " " for _ , container := range task . Containers { res += fmt . Sprintf ( " " , container . Name , container . GetKnownStatus ( ) . String ( ) , container . GetDesiredStatus ( ) . String ( ) ) } if task . ENI != nil { res += fmt . Sprintf ( " " , task . ENI . String ( ) ) } return res + " " }

// stringUnsafe returns a human readable string representation of this object


func (task *Task) RecordExecutionStoppedAt(container *apicontainer.Container) {
	if !container.Essential {
		return
	}
	if container.GetKnownStatus() != apicontainerstatus.ContainerStopped {
		return
	}
	// If the essential container is stopped, set the ExecutionStoppedAt timestamp
	now := time.Now()
	ok := task.SetExecutionStoppedAt(now)
	if !ok {
		// ExecutionStoppedAt was already recorded. Nothing to left to do here
		return
	}
	seelog.Infof("Task [%s]: recording execution stopped time. Essential container [%s] stopped at: %s",
		task.Arn, container.Name, now.String())
}

// RecordExecutionStoppedAt checks if this is an essential container stopped
// and set the task executionStoppedAt timestamps

RecordExecutionStoppedAt checks if this is an essential container stopped and set the task executionStoppedAt timestamps


func (task *Task) GetResources() []taskresource.TaskResource {
	task.lock.RLock()
	defer task.lock.RUnlock()
	return task.getResourcesUnsafe()
}

func ( task * Task ) GetResources ( ) [ ] taskresource . TaskResource { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . getResourcesUnsafe ( ) }

// GetResources returns the list of task resources from ResourcesMap


func (task *Task) getResourcesUnsafe() []taskresource.TaskResource {
	var resourceList []taskresource.TaskResource
	for _, resources := range task.ResourcesMapUnsafe {
		resourceList = append(resourceList, resources...)
	}
	return resourceList
}

func ( task * Task ) getResourcesUnsafe ( ) [ ] taskresource . TaskResource { var resourceList [ ] taskresource . TaskResource for _ , resources := range task . ResourcesMapUnsafe { resourceList = append ( resourceList , resources ... ) } return resourceList }

// getResourcesUnsafe returns the list of task resources from ResourcesMap


func (task *Task) AddResource(resourceType string, resource taskresource.TaskResource) {
	task.lock.Lock()
	defer task.lock.Unlock()
	task.ResourcesMapUnsafe[resourceType] = append(task.ResourcesMapUnsafe[resourceType], resource)
}

func ( task * Task ) AddResource ( resourceType string , resource taskresource . TaskResource ) { task . lock . Lock ( ) defer task . lock . Unlock ( ) task . ResourcesMapUnsafe [ resourceType ] = append ( task . ResourcesMapUnsafe [ resourceType ] , resource ) }

// AddResource adds a resource to ResourcesMap


func (task *Task) SetTerminalReason(reason string) {
	seelog.Infof("Task [%s]: attempting to set terminal reason for task [%s]", task.Arn, reason)
	task.terminalReasonOnce.Do(func() {
		seelog.Infof("Task [%s]: setting terminal reason for task [%s]", task.Arn, reason)

		// Converts the first letter of terminal reason into capital letter
		words := strings.Fields(reason)
		words[0] = strings.Title(words[0])
		task.terminalReason = strings.Join(words, " ")
	})
}

// SetTerminalReason sets the terminalReason string and this can only be set
// once per the task's lifecycle. This field does not accept updates.

SetTerminalReason sets the terminalReason string and this can only be set once per the task s lifecycle . This field does not accept updates .


func (task *Task) GetTerminalReason() string {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.terminalReason
}

func ( task * Task ) GetTerminalReason ( ) string { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . terminalReason }

// GetTerminalReason retrieves the terminalReason string


func (task *Task) PopulateASMAuthData(container *apicontainer.Container) error {
	secretID := container.RegistryAuthentication.ASMAuthData.CredentialsParameter
	resource, ok := task.getASMAuthResource()
	if !ok {
		return errors.New("task auth data: unable to fetch ASM resource")
	}
	// This will cause a panic if the resource is not of ASMAuthResource type.
	// But, it's better to panic as we should have never reached condition
	// unless we released an agent without any testing around that code path
	asmResource := resource[0].(*asmauth.ASMAuthResource)
	dac, ok := asmResource.GetASMDockerAuthConfig(secretID)
	if !ok {
		return errors.Errorf("task auth data: unable to fetch docker auth config [%s]", secretID)
	}
	container.SetASMDockerAuthConfig(dac)
	return nil
}

// PopulateASMAuthData sets docker auth credentials for a container


func (task *Task) getSSMSecretsResource() ([]taskresource.TaskResource, bool) {
	task.lock.RLock()
	defer task.lock.RUnlock()

	res, ok := task.ResourcesMapUnsafe[ssmsecret.ResourceName]
	return res, ok
}

func ( task * Task ) getSSMSecretsResource ( ) ( [ ] taskresource . TaskResource , bool ) { task . lock . RLock ( ) defer task . lock . RUnlock ( ) res , ok := task . ResourcesMapUnsafe [ ssmsecret . ResourceName ] return res , ok }

// getSSMSecretsResource retrieves ssmsecret resource from resource map


func (task *Task) PopulateSecrets(hostConfig *dockercontainer.HostConfig, container *apicontainer.Container) *apierrors.DockerClientConfigError {
	var ssmRes *ssmsecret.SSMSecretResource
	var asmRes *asmsecret.ASMSecretResource

	if container.ShouldCreateWithSSMSecret() {
		resource, ok := task.getSSMSecretsResource()
		if !ok {
			return &apierrors.DockerClientConfigError{Msg: "task secret data: unable to fetch SSM Secrets resource"}
		}
		ssmRes = resource[0].(*ssmsecret.SSMSecretResource)
	}

	if container.ShouldCreateWithASMSecret() {
		resource, ok := task.getASMSecretsResource()
		if !ok {
			return &apierrors.DockerClientConfigError{Msg: "task secret data: unable to fetch ASM Secrets resource"}
		}
		asmRes = resource[0].(*asmsecret.ASMSecretResource)
	}

	envVars := make(map[string]string)

	logDriverTokenName := ""
	logDriverTokenSecretValue := ""

	for _, secret := range container.Secrets {
		secretVal := ""

		if secret.Provider == apicontainer.SecretProviderSSM {
			k := secret.GetSecretResourceCacheKey()
			if secretValue, ok := ssmRes.GetCachedSecretValue(k); ok {
				secretVal = secretValue
			}
		}

		if secret.Provider == apicontainer.SecretProviderASM {
			k := secret.GetSecretResourceCacheKey()
			if secretValue, ok := asmRes.GetCachedSecretValue(k); ok {
				secretVal = secretValue
			}
		}

		if secret.Type == apicontainer.SecretTypeEnv {
			envVars[secret.Name] = secretVal
			continue
		}
		if secret.Target == apicontainer.SecretTargetLogDriver {
			logDriverTokenName = secret.Name
			logDriverTokenSecretValue = secretVal

			// Check if all the name and secret value for the log driver do exist
			// And add the secret value for this log driver into container's HostConfig
			if hostConfig.LogConfig.Type != "" && logDriverTokenName != "" && logDriverTokenSecretValue != "" {
				hostConfig.LogConfig.Config[logDriverTokenName] = logDriverTokenSecretValue
			}
		}
	}

	container.MergeEnvironmentVariables(envVars)
	return nil
}

// PopulateSecrets appends secrets to container's env var map and hostconfig section

PopulateSecrets appends secrets to container s env var map and hostconfig section


func (task *Task) getASMSecretsResource() ([]taskresource.TaskResource, bool) {
	task.lock.RLock()
	defer task.lock.RUnlock()

	res, ok := task.ResourcesMapUnsafe[asmsecret.ResourceName]
	return res, ok
}

func ( task * Task ) getASMSecretsResource ( ) ( [ ] taskresource . TaskResource , bool ) { task . lock . RLock ( ) defer task . lock . RUnlock ( ) res , ok := task . ResourcesMapUnsafe [ asmsecret . ResourceName ] return res , ok }

// getASMSecretsResource retrieves asmsecret resource from resource map


func (task *Task) getPIDMode() string {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.PIDMode
}

func ( task * Task ) getPIDMode ( ) string { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . PIDMode }


func (task *Task) getIPCMode() string {
	task.lock.RLock()
	defer task.lock.RUnlock()

	return task.IPCMode
}

func ( task * Task ) getIPCMode ( ) string { task . lock . RLock ( ) defer task . lock . RUnlock ( ) return task . IPCMode }


func (task *Task) AssociationsByTypeAndContainer(associationType, containerName string) []string {
	task.lock.RLock()
	defer task.lock.RUnlock()

	var associationNames []string
	for _, association := range task.Associations {
		if association.Type == associationType {
			for _, associatedContainerName := range association.Containers {
				if associatedContainerName == containerName {
					associationNames = append(associationNames, association.Name)
				}
			}
		}
	}

	return associationNames
}

func ( task * Task ) AssociationsByTypeAndContainer ( associationType , containerName string ) [ ] string { task . lock . RLock ( ) defer task . lock . RUnlock ( ) var associationNames [ ] string for _ , association := range task . Associations { if association . Type == associationType { for _ , associatedContainerName := range association . Containers { if associatedContainerName == containerName { associationNames = append ( associationNames , association . Name ) } } } } return associationNames }

// AssociationByTypeAndContainer gets a list of names of all the associations associated with a container and of a
// certain type

AssociationByTypeAndContainer gets a list of names of all the associations associated with a container and of a certain type


func (task *Task) AssociationByTypeAndName(associationType, associationName string) (*Association, bool) {
	task.lock.RLock()
	defer task.lock.RUnlock()

	for _, association := range task.Associations {
		if association.Type == associationType && association.Name == associationName {
			return &association, true
		}
	}

	return nil, false
}

func ( task * Task ) AssociationByTypeAndName ( associationType , associationName string ) ( * Association , bool ) { task . lock . RLock ( ) defer task . lock . RUnlock ( ) for _ , association := range task . Associations { if association . Type == associationType && association . Name == associationName { return & association , true } } return nil , false }

// AssociationByTypeAndName gets an association of a certain type and name


func newAttachENIHandler(ctx context.Context,
	cluster string,
	containerInstanceArn string,
	acsClient wsclient.ClientServer,
	taskEngineState dockerstate.TaskEngineState,
	saver statemanager.Saver) attachENIHandler {

	// Create a cancelable context from the parent context
	derivedContext, cancel := context.WithCancel(ctx)
	return attachENIHandler{
		messageBuffer:     make(chan *ecsacs.AttachTaskNetworkInterfacesMessage),
		ctx:               derivedContext,
		cancel:            cancel,
		cluster:           aws.String(cluster),
		containerInstance: aws.String(containerInstanceArn),
		acsClient:         acsClient,
		state:             taskEngineState,
		saver:             saver,
	}
}

// newAttachENIHandler returns an instance of the attachENIHandler struct


func (attachENIHandler *attachENIHandler) handlerFunc() func(message *ecsacs.AttachTaskNetworkInterfacesMessage) {
	return func(message *ecsacs.AttachTaskNetworkInterfacesMessage) {
		attachENIHandler.messageBuffer <- message
	}
}

func ( attachENIHandler * attachENIHandler ) handlerFunc ( ) func ( message * ecsacs . AttachTaskNetworkInterfacesMessage ) { return func ( message * ecsacs . AttachTaskNetworkInterfacesMessage ) { attachENIHandler . messageBuffer <- message } }

// handlerFunc returns a function to enqueue requests onto attachENIHandler buffer


func (attachENIHandler *attachENIHandler) handleMessages() {
	for {
		select {
		case message := <-attachENIHandler.messageBuffer:
			if err := attachENIHandler.handleSingleMessage(message); err != nil {
				seelog.Warnf("Unable to handle ENI Attachment message [%s]: %v", message.String(), err)
			}
		case <-attachENIHandler.ctx.Done():
			return
		}
	}
}

func ( attachENIHandler * attachENIHandler ) handleMessages ( ) { for { select { case message := <- attachENIHandler . messageBuffer : if err := attachENIHandler . handleSingleMessage ( message ) ; err != nil { seelog . Warnf ( " " , message . String ( ) , err ) } case <- attachENIHandler . ctx . Done ( ) : return } } }

// handleMessages handles each message one at a time


func (handler *attachENIHandler) handleSingleMessage(message *ecsacs.AttachTaskNetworkInterfacesMessage) error {
	receivedAt := time.Now()
	// Validate fields in the message
	if err := validateAttachTaskNetworkInterfacesMessage(message); err != nil {
		return errors.Wrapf(err,
			"attach eni message handler: error validating AttachTaskNetworkInterface message received from ECS")
	}

	// Send ACK
	go func(clusterArn *string, containerInstanceArn *string, messageID *string) {
		if err := handler.acsClient.MakeRequest(&ecsacs.AckRequest{
			Cluster:           clusterArn,
			ContainerInstance: containerInstanceArn,
			MessageId:         messageID,
		}); err != nil {
			seelog.Warnf("Failed to ack request with messageId: %s, error: %v", aws.StringValue(messageID), err)
		}
	}(message.ClusterArn, message.ContainerInstanceArn, message.MessageId)

	// Check if this is a duplicate message
	mac := aws.StringValue(message.ElasticNetworkInterfaces[0].MacAddress)
	if eniAttachment, ok := handler.state.ENIByMac(mac); ok {
		seelog.Infof("Duplicate ENI attachment message for ENI with MAC address: %s", mac)
		eniAckTimeoutHandler := ackTimeoutHandler{mac: mac, state: handler.state}
		return eniAttachment.StartTimer(eniAckTimeoutHandler.handle)
	}
	if err := handler.addENIAttachmentToState(message, receivedAt); err != nil {
		return errors.Wrapf(err, "attach eni message handler: unable to add eni attachment to engine state")
	}
	if err := handler.saver.Save(); err != nil {
		return errors.Wrapf(err, "attach eni message handler: unable to save agent state")
	}
	return nil
}

// handleSingleMessage acks the message received


func (handler *attachENIHandler) addENIAttachmentToState(message *ecsacs.AttachTaskNetworkInterfacesMessage, receivedAt time.Time) error {
	attachmentARN := aws.StringValue(message.ElasticNetworkInterfaces[0].AttachmentArn)
	mac := aws.StringValue(message.ElasticNetworkInterfaces[0].MacAddress)
	taskARN := aws.StringValue(message.TaskArn)
	eniAttachment := &apieni.ENIAttachment{
		TaskARN:          taskARN,
		AttachmentARN:    attachmentARN,
		AttachStatusSent: false,
		MACAddress:       mac,
		// Stop tracking the eni attachment after timeout
		ExpiresAt: receivedAt.Add(time.Duration(aws.Int64Value(message.WaitTimeoutMs)) * time.Millisecond),
	}
	eniAckTimeoutHandler := ackTimeoutHandler{mac: mac, state: handler.state}
	if err := eniAttachment.StartTimer(eniAckTimeoutHandler.handle); err != nil {
		return err
	}
	seelog.Infof("Adding eni info for task '%s' to state, attachment=%s mac=%s",
		taskARN, attachmentARN, mac)
	handler.state.AddENIAttachment(eniAttachment)
	return nil
}

// addENIAttachmentToState adds the eni info to the state


func validateAttachTaskNetworkInterfacesMessage(message *ecsacs.AttachTaskNetworkInterfacesMessage) error {
	if message == nil {
		return errors.Errorf("attach eni handler validation: empty AttachTaskNetworkInterface message received from ECS")
	}

	messageId := aws.StringValue(message.MessageId)
	if messageId == "" {
		return errors.Errorf("attach eni handler validation: message id not set in AttachTaskNetworkInterface message received from ECS")
	}

	clusterArn := aws.StringValue(message.ClusterArn)
	if clusterArn == "" {
		return errors.Errorf("attach eni handler validation: clusterArn not set in AttachTaskNetworkInterface message received from ECS")
	}

	containerInstanceArn := aws.StringValue(message.ContainerInstanceArn)
	if containerInstanceArn == "" {
		return errors.Errorf("attach eni handler validation: containerInstanceArn not set in AttachTaskNetworkInterface message received from ECS")
	}

	enis := message.ElasticNetworkInterfaces
	if len(enis) != 1 {
		return errors.Errorf("attach eni handler validation: incorrect number of ENIs in AttachTaskNetworkInterface message received from ECS. Obtained %d", len(enis))
	}

	eni := enis[0]
	if aws.StringValue(eni.MacAddress) == "" {
		return errors.Errorf("attach eni handler validation: MACAddress not listed in AttachTaskNetworkInterface message received from ECS")
	}

	taskArn := aws.StringValue(message.TaskArn)
	if taskArn == "" {
		return errors.Errorf("attach eni handler validation: taskArn not set in AttachTaskNetworkInterface message received from ECS")
	}

	timeout := aws.Int64Value(message.WaitTimeoutMs)
	if timeout <= 0 {
		return errors.Errorf("attach eni handler validation: invalid timeout listed in AttachTaskNetworkInterface message received from ECS")

	}

	return nil
}

// validateAttachTaskNetworkInterfacesMessage performs validation checks on the
// AttachTaskNetworkInterfacesMessage


func (m *MockStateManager) ForceSave() error {
	ret := m.ctrl.Call(m, "ForceSave")
	ret0, _ := ret[0].(error)
	return ret0
}

func ( m * MockStateManager ) ForceSave ( ) error { ret := m . ctrl . Call ( m , " " ) ret0 , _ := ret [ 0 ] . ( error ) return ret0 }


func (mr *MockStateManagerMockRecorder) ForceSave() *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForceSave", reflect.TypeOf((*MockStateManager)(nil).ForceSave))
}

func ( mr * MockStateManagerMockRecorder ) ForceSave ( ) * gomock . Call { return mr . mock . ctrl . RecordCallWithMethodType ( mr . mock , " " , reflect . TypeOf ( ( * MockStateManager ) ( nil ) . ForceSave ) ) }

// ForceSave indicates an expected call of ForceSave


func NewMockClientServer(ctrl *gomock.Controller) *MockClientServer {
	mock := &MockClientServer{ctrl: ctrl}
	mock.recorder = &MockClientServerMockRecorder{mock}
	return mock
}

func NewMockClientServer ( ctrl * gomock . Controller ) * MockClientServer { mock := & MockClientServer { ctrl : ctrl } mock . recorder = & MockClientServerMockRecorder { mock } return mock }

// NewMockClientServer creates a new mock instance


func (m *MockClientServer) AddRequestHandler(arg0 wsclient.RequestHandler) {
	m.ctrl.Call(m, "AddRequestHandler", arg0)
}

func ( m * MockClientServer ) AddRequestHandler ( arg0 wsclient . RequestHandler ) { m . ctrl . Call ( m , " " , arg0 ) }


func (mr *MockClientServerMockRecorder) AddRequestHandler(arg0 interface{}) *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRequestHandler", reflect.TypeOf((*MockClientServer)(nil).AddRequestHandler), arg0)
}

func ( mr * MockClientServerMockRecorder ) AddRequestHandler ( arg0 interface { } ) * gomock . Call { return mr . mock . ctrl . RecordCallWithMethodType ( mr . mock , " " , reflect . TypeOf ( ( * MockClientServer ) ( nil ) . AddRequestHandler ) , arg0 ) }

// AddRequestHandler indicates an expected call of AddRequestHandler


func (mr *MockClientServerMockRecorder) Connect() *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockClientServer)(nil).Connect))
}

func ( mr * MockClientServerMockRecorder ) Connect ( ) * gomock . Call { return mr . mock . ctrl . RecordCallWithMethodType ( mr . mock , " " , reflect . TypeOf ( ( * MockClientServer ) ( nil ) . Connect ) ) }

// Connect indicates an expected call of Connect


func (m *MockClientServer) Disconnect(arg0 ...interface{}) error {
	varargs := []interface{}{}
	for _, a := range arg0 {
		varargs = append(varargs, a)
	}
	ret := m.ctrl.Call(m, "Disconnect", varargs...)
	ret0, _ := ret[0].(error)
	return ret0
}

func ( m * MockClientServer ) Disconnect ( arg0 ... interface { } ) error { varargs := [ ] interface { } { } for _ , a := range arg0 { varargs = append ( varargs , a ) } ret := m . ctrl . Call ( m , " " , varargs ... ) ret0 , _ := ret [ 0 ] . ( error ) return ret0 }


func (m *MockClientServer) IsConnected() bool {
	ret := m.ctrl.Call(m, "IsConnected")
	ret0, _ := ret[0].(bool)
	return ret0
}

func ( m * MockClientServer ) IsConnected ( ) bool { ret := m . ctrl . Call ( m , " " ) ret0 , _ := ret [ 0 ] . ( bool ) return ret0 }


func (m *MockClientServer) MakeRequest(arg0 interface{}) error {
	ret := m.ctrl.Call(m, "MakeRequest", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

func ( m * MockClientServer ) MakeRequest ( arg0 interface { } ) error { ret := m . ctrl . Call ( m , " " , arg0 ) ret0 , _ := ret [ 0 ] . ( error ) return ret0 }


func (m *MockClientServer) SetAnyRequestHandler(arg0 wsclient.RequestHandler) {
	m.ctrl.Call(m, "SetAnyRequestHandler", arg0)
}

func ( m * MockClientServer ) SetAnyRequestHandler ( arg0 wsclient . RequestHandler ) { m . ctrl . Call ( m , " " , arg0 ) }

// SetAnyRequestHandler mocks base method


func (m *MockClientServer) SetConnection(arg0 wsconn.WebsocketConn) {
	m.ctrl.Call(m, "SetConnection", arg0)
}

func ( m * MockClientServer ) SetConnection ( arg0 wsconn . WebsocketConn ) { m . ctrl . Call ( m , " " , arg0 ) }


func (m *MockClientServer) SetReadDeadline(arg0 time.Time) error {
	ret := m.ctrl.Call(m, "SetReadDeadline", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

func ( m * MockClientServer ) SetReadDeadline ( arg0 time . Time ) error { ret := m . ctrl . Call ( m , " " , arg0 ) ret0 , _ := ret [ 0 ] . ( error ) return ret0 }


func (cs *ContainerStatus) ShouldReportToBackend(steadyStateStatus ContainerStatus) bool {
	return *cs == steadyStateStatus || *cs == ContainerStopped
}

func ( cs * ContainerStatus ) ShouldReportToBackend ( steadyStateStatus ContainerStatus ) bool { return * cs == steadyStateStatus || * cs == ContainerStopped }

// ShouldReportToBackend returns true if the container status is recognized as a
// valid state by ECS. Note that not all container statuses are recognized by ECS
// or map to ECS states

ShouldReportToBackend returns true if the container status is recognized as a valid state by ECS . Note that not all container statuses are recognized by ECS or map to ECS states


func (cs *ContainerStatus) BackendStatus(steadyStateStatus ContainerStatus) ContainerStatus {
	if *cs == steadyStateStatus {
		return ContainerRunning
	}

	if *cs == ContainerStopped {
		return ContainerStopped
	}

	return ContainerStatusNone
}

func ( cs * ContainerStatus ) BackendStatus ( steadyStateStatus ContainerStatus ) ContainerStatus { if * cs == steadyStateStatus { return ContainerRunning } if * cs == ContainerStopped { return ContainerStopped } return ContainerStatusNone }

// BackendStatus maps the internal container status in the agent to that in the
// backend

BackendStatus maps the internal container status in the agent to that in the backend


func (cs *ContainerStatus) UnmarshalJSON(b []byte) error {
	if strings.ToLower(string(b)) == "null" {
		*cs = ContainerStatusNone
		return nil
	}
	if b[0] != '"' || b[len(b)-1] != '"' {
		*cs = ContainerStatusNone
		return errors.New("container status unmarshal: status must be a string or null; Got " + string(b))
	}
	strStatus := string(b[1 : len(b)-1])
	// 'UNKNOWN' and 'DEAD' for Compatibility with v1.0.0 state files
	if strStatus == "UNKNOWN" {
		*cs = ContainerStatusNone
		return nil
	}
	if strStatus == "DEAD" {
		*cs = ContainerStopped
		return nil
	}

	stat, ok := containerStatusMap[strStatus]
	if !ok {
		*cs = ContainerStatusNone
		return errors.New("container status unmarshal: unrecognized status")
	}
	*cs = stat
	return nil
}

// UnmarshalJSON overrides the logic for parsing the JSON-encoded ContainerStatus data

UnmarshalJSON overrides the logic for parsing the JSON - encoded ContainerStatus data


func (cs *ContainerStatus) MarshalJSON() ([]byte, error) {
	if cs == nil {
		return nil, nil
	}
	return []byte(`"` + cs.String() + `"`), nil
}

func ( cs * ContainerStatus ) MarshalJSON ( ) ( [ ] byte , error ) { if cs == nil { return nil , nil } return [ ] byte ( `"` + cs . String ( ) + `"` ) , nil }

// MarshalJSON overrides the logic for JSON-encoding the ContainerStatus type

MarshalJSON overrides the logic for JSON - encoding the ContainerStatus type


func (healthStatus *ContainerHealthStatus) UnmarshalJSON(b []byte) error {
	*healthStatus = ContainerHealthUnknown

	if strings.ToLower(string(b)) == "null" {
		return nil
	}
	if b[0] != '"' || b[len(b)-1] != '"' {
		return errors.New("container health status unmarshal: status must be a string or null; Got " + string(b))
	}

	strStatus := string(b[1 : len(b)-1])
	switch strStatus {
	case "UNKNOWN":
	// The health status is already set to ContainerHealthUnknown initially
	case "HEALTHY":
		*healthStatus = ContainerHealthy
	case "UNHEALTHY":
		*healthStatus = ContainerUnhealthy
	default:
		return errors.New("container health status unmarshal: unrecognized status: " + string(b))
	}
	return nil
}

// UnmarshalJSON overrides the logic for parsing the JSON-encoded container health data

UnmarshalJSON overrides the logic for parsing the JSON - encoded container health data


func (healthStatus *ContainerHealthStatus) MarshalJSON() ([]byte, error) {
	if healthStatus == nil {
		return nil, nil
	}
	return []byte(`"` + healthStatus.String() + `"`), nil
}

func ( healthStatus * ContainerHealthStatus ) MarshalJSON ( ) ( [ ] byte , error ) { if healthStatus == nil { return nil , nil } return [ ] byte ( `"` + healthStatus . String ( ) + `"` ) , nil }

// MarshalJSON overrides the logic for JSON-encoding the ContainerHealthStatus type

MarshalJSON overrides the logic for JSON - encoding the ContainerHealthStatus type


func (engine *MetricsEngine) publishMetrics() {
	go func() {
		// Because we are using the DefaultRegisterer in Prometheus, we can use
		// the promhttp.Handler() function. In future cases for custom registers,
		// we can use promhttp.HandlerFor(customRegisterer, promhttp.HandlerOpts{})
		http.Handle("/metrics", promhttp.Handler())
		err := http.ListenAndServe(fmt.Sprintf(":%d", config.AgentPrometheusExpositionPort), nil)
		if err != nil {
			seelog.Errorf("Error publishing metrics: %s", err.Error())
		}
	}()
}

// Function that exposes all Agent Metrics on a given port.

Function that exposes all Agent Metrics on a given port .


func NewMockDockerClient(ctrl *gomock.Controller) *MockDockerClient {
	mock := &MockDockerClient{ctrl: ctrl}
	mock.recorder = &MockDockerClientMockRecorder{mock}
	return mock
}

func NewMockDockerClient ( ctrl * gomock . Controller ) * MockDockerClient { mock := & MockDockerClient { ctrl : ctrl } mock . recorder = & MockDockerClientMockRecorder { mock } return mock }

// NewMockDockerClient creates a new mock instance


func (m *MockDockerClient) APIVersion() (dockerclient.DockerVersion, error) {
	ret := m.ctrl.Call(m, "APIVersion")
	ret0, _ := ret[0].(dockerclient.DockerVersion)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

func ( m * MockDockerClient ) APIVersion ( ) ( dockerclient . DockerVersion , error ) { ret := m . ctrl . Call ( m , " " ) ret0 , _ := ret [ 0 ] . ( dockerclient . DockerVersion ) ret1 , _ := ret [ 1 ] . ( error ) return ret0 , ret1 }


func (m *MockDockerClient) ContainerEvents(arg0 context.Context) (<-chan dockerapi.DockerContainerChangeEvent, error) {
	ret := m.ctrl.Call(m, "ContainerEvents", arg0)
	ret0, _ := ret[0].(<-chan dockerapi.DockerContainerChangeEvent)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

func ( m * MockDockerClient ) ContainerEvents ( arg0 context . Context ) ( <- chan dockerapi . DockerContainerChangeEvent , error ) { ret := m . ctrl . Call ( m , " " , arg0 ) ret0 , _ := ret [ 0 ] . ( <- chan dockerapi . DockerContainerChangeEvent ) ret1 , _ := ret [ 1 ] . ( error ) return ret0 , ret1 }


func (mr *MockDockerClientMockRecorder) ContainerEvents(arg0 interface{}) *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainerEvents", reflect.TypeOf((*MockDockerClient)(nil).ContainerEvents), arg0)
}

func ( mr * MockDockerClientMockRecorder ) ContainerEvents ( arg0 interface { } ) * gomock . Call { return mr . mock . ctrl . RecordCallWithMethodType ( mr . mock , " " , reflect . TypeOf ( ( * MockDockerClient ) ( nil ) . ContainerEvents ) , arg0 ) }

// ContainerEvents indicates an expected call of ContainerEvents


func (m *MockDockerClient) CreateContainer(arg0 context.Context, arg1 *container0.Config, arg2 *container0.HostConfig, arg3 string, arg4 time.Duration) dockerapi.DockerContainerMetadata {
	ret := m.ctrl.Call(m, "CreateContainer", arg0, arg1, arg2, arg3, arg4)
	ret0, _ := ret[0].(dockerapi.DockerContainerMetadata)
	return ret0
}

func ( m * MockDockerClient ) CreateContainer ( arg0 context . Context , arg1 * container0 . Config , arg2 * container0 . HostConfig , arg3 string , arg4 time . Duration ) dockerapi . DockerContainerMetadata { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 , arg3 , arg4 ) ret0 , _ := ret [ 0 ] . ( dockerapi . DockerContainerMetadata ) return ret0 }


func (m *MockDockerClient) CreateVolume(arg0 context.Context, arg1, arg2 string, arg3, arg4 map[string]string, arg5 time.Duration) dockerapi.SDKVolumeResponse {
	ret := m.ctrl.Call(m, "CreateVolume", arg0, arg1, arg2, arg3, arg4, arg5)
	ret0, _ := ret[0].(dockerapi.SDKVolumeResponse)
	return ret0
}

func ( m * MockDockerClient ) CreateVolume ( arg0 context . Context , arg1 , arg2 string , arg3 , arg4 map [ string ] string , arg5 time . Duration ) dockerapi . SDKVolumeResponse { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 , arg3 , arg4 , arg5 ) ret0 , _ := ret [ 0 ] . ( dockerapi . SDKVolumeResponse ) return ret0 }


func (m *MockDockerClient) DescribeContainer(arg0 context.Context, arg1 string) (status.ContainerStatus, dockerapi.DockerContainerMetadata) {
	ret := m.ctrl.Call(m, "DescribeContainer", arg0, arg1)
	ret0, _ := ret[0].(status.ContainerStatus)
	ret1, _ := ret[1].(dockerapi.DockerContainerMetadata)
	return ret0, ret1
}

func ( m * MockDockerClient ) DescribeContainer ( arg0 context . Context , arg1 string ) ( status . ContainerStatus , dockerapi . DockerContainerMetadata ) { ret := m . ctrl . Call ( m , " " , arg0 , arg1 ) ret0 , _ := ret [ 0 ] . ( status . ContainerStatus ) ret1 , _ := ret [ 1 ] . ( dockerapi . DockerContainerMetadata ) return ret0 , ret1 }


func (m *MockDockerClient) InspectContainer(arg0 context.Context, arg1 string, arg2 time.Duration) (*types.ContainerJSON, error) {
	ret := m.ctrl.Call(m, "InspectContainer", arg0, arg1, arg2)
	ret0, _ := ret[0].(*types.ContainerJSON)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

func ( m * MockDockerClient ) InspectContainer ( arg0 context . Context , arg1 string , arg2 time . Duration ) ( * types . ContainerJSON , error ) { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 ) ret0 , _ := ret [ 0 ] . ( * types . ContainerJSON ) ret1 , _ := ret [ 1 ] . ( error ) return ret0 , ret1 }


func (m *MockDockerClient) InspectImage(arg0 string) (*types.ImageInspect, error) {
	ret := m.ctrl.Call(m, "InspectImage", arg0)
	ret0, _ := ret[0].(*types.ImageInspect)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

func ( m * MockDockerClient ) InspectImage ( arg0 string ) ( * types . ImageInspect , error ) { ret := m . ctrl . Call ( m , " " , arg0 ) ret0 , _ := ret [ 0 ] . ( * types . ImageInspect ) ret1 , _ := ret [ 1 ] . ( error ) return ret0 , ret1 }


func (m *MockDockerClient) KnownVersions() []dockerclient.DockerVersion {
	ret := m.ctrl.Call(m, "KnownVersions")
	ret0, _ := ret[0].([]dockerclient.DockerVersion)
	return ret0
}

func ( m * MockDockerClient ) KnownVersions ( ) [ ] dockerclient . DockerVersion { ret := m . ctrl . Call ( m , " " ) ret0 , _ := ret [ 0 ] . ( [ ] dockerclient . DockerVersion ) return ret0 }


func (mr *MockDockerClientMockRecorder) KnownVersions() *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KnownVersions", reflect.TypeOf((*MockDockerClient)(nil).KnownVersions))
}

func ( mr * MockDockerClientMockRecorder ) KnownVersions ( ) * gomock . Call { return mr . mock . ctrl . RecordCallWithMethodType ( mr . mock , " " , reflect . TypeOf ( ( * MockDockerClient ) ( nil ) . KnownVersions ) ) }

// KnownVersions indicates an expected call of KnownVersions


func (m *MockDockerClient) ListContainers(arg0 context.Context, arg1 bool, arg2 time.Duration) dockerapi.ListContainersResponse {
	ret := m.ctrl.Call(m, "ListContainers", arg0, arg1, arg2)
	ret0, _ := ret[0].(dockerapi.ListContainersResponse)
	return ret0
}

func ( m * MockDockerClient ) ListContainers ( arg0 context . Context , arg1 bool , arg2 time . Duration ) dockerapi . ListContainersResponse { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 ) ret0 , _ := ret [ 0 ] . ( dockerapi . ListContainersResponse ) return ret0 }


func (m *MockDockerClient) ListImages(arg0 context.Context, arg1 time.Duration) dockerapi.ListImagesResponse {
	ret := m.ctrl.Call(m, "ListImages", arg0, arg1)
	ret0, _ := ret[0].(dockerapi.ListImagesResponse)
	return ret0
}

func ( m * MockDockerClient ) ListImages ( arg0 context . Context , arg1 time . Duration ) dockerapi . ListImagesResponse { ret := m . ctrl . Call ( m , " " , arg0 , arg1 ) ret0 , _ := ret [ 0 ] . ( dockerapi . ListImagesResponse ) return ret0 }


func (m *MockDockerClient) ListPlugins(arg0 context.Context, arg1 time.Duration, arg2 filters.Args) dockerapi.ListPluginsResponse {
	ret := m.ctrl.Call(m, "ListPlugins", arg0, arg1, arg2)
	ret0, _ := ret[0].(dockerapi.ListPluginsResponse)
	return ret0
}

func ( m * MockDockerClient ) ListPlugins ( arg0 context . Context , arg1 time . Duration , arg2 filters . Args ) dockerapi . ListPluginsResponse { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 ) ret0 , _ := ret [ 0 ] . ( dockerapi . ListPluginsResponse ) return ret0 }


func (m *MockDockerClient) ListPluginsWithFilters(arg0 context.Context, arg1 bool, arg2 []string, arg3 time.Duration) ([]string, error) {
	ret := m.ctrl.Call(m, "ListPluginsWithFilters", arg0, arg1, arg2, arg3)
	ret0, _ := ret[0].([]string)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

func ( m * MockDockerClient ) ListPluginsWithFilters ( arg0 context . Context , arg1 bool , arg2 [ ] string , arg3 time . Duration ) ( [ ] string , error ) { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 , arg3 ) ret0 , _ := ret [ 0 ] . ( [ ] string ) ret1 , _ := ret [ 1 ] . ( error ) return ret0 , ret1 }

// ListPluginsWithFilters mocks base method


func (m *MockDockerClient) PullImage(arg0 context.Context, arg1 string, arg2 *container.RegistryAuthenticationData, arg3 time.Duration) dockerapi.DockerContainerMetadata {
	ret := m.ctrl.Call(m, "PullImage", arg0, arg1, arg2, arg3)
	ret0, _ := ret[0].(dockerapi.DockerContainerMetadata)
	return ret0
}

func ( m * MockDockerClient ) PullImage ( arg0 context . Context , arg1 string , arg2 * container . RegistryAuthenticationData , arg3 time . Duration ) dockerapi . DockerContainerMetadata { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 , arg3 ) ret0 , _ := ret [ 0 ] . ( dockerapi . DockerContainerMetadata ) return ret0 }


func (m *MockDockerClient) RemoveImage(arg0 context.Context, arg1 string, arg2 time.Duration) error {
	ret := m.ctrl.Call(m, "RemoveImage", arg0, arg1, arg2)
	ret0, _ := ret[0].(error)
	return ret0
}

func ( m * MockDockerClient ) RemoveImage ( arg0 context . Context , arg1 string , arg2 time . Duration ) error { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 ) ret0 , _ := ret [ 0 ] . ( error ) return ret0 }


func (m *MockDockerClient) StopContainer(arg0 context.Context, arg1 string, arg2 time.Duration) dockerapi.DockerContainerMetadata {
	ret := m.ctrl.Call(m, "StopContainer", arg0, arg1, arg2)
	ret0, _ := ret[0].(dockerapi.DockerContainerMetadata)
	return ret0
}

func ( m * MockDockerClient ) StopContainer ( arg0 context . Context , arg1 string , arg2 time . Duration ) dockerapi . DockerContainerMetadata { ret := m . ctrl . Call ( m , " " , arg0 , arg1 , arg2 ) ret0 , _ := ret [ 0 ] . ( dockerapi . DockerContainerMetadata ) return ret0 }


func (m *MockDockerClient) WithVersion(arg0 dockerclient.DockerVersion) dockerapi.DockerClient {
	ret := m.ctrl.Call(m, "WithVersion", arg0)
	ret0, _ := ret[0].(dockerapi.DockerClient)
	return ret0
}

func ( m * MockDockerClient ) WithVersion ( arg0 dockerclient . DockerVersion ) dockerapi . DockerClient { ret := m . ctrl . Call ( m , " " , arg0 ) ret0 , _ := ret [ 0 ] . ( dockerapi . DockerClient ) return ret0 }


func NewMockCache(ctrl *gomock.Controller) *MockCache {
	mock := &MockCache{ctrl: ctrl}
	mock.recorder = &MockCacheMockRecorder{mock}
	return mock
}

func NewMockCache ( ctrl * gomock . Controller ) * MockCache { mock := & MockCache { ctrl : ctrl } mock . recorder = & MockCacheMockRecorder { mock } return mock }

// NewMockCache creates a new mock instance


func (m *MockCache) Delete(arg0 string) {
	m.ctrl.Call(m, "Delete", arg0)
}

func ( m * MockCache ) Delete ( arg0 string ) { m . ctrl . Call ( m , " " , arg0 ) }


func (m *MockCache) Get(arg0 string) (async.Value, bool) {
	ret := m.ctrl.Call(m, "Get", arg0)
	ret0, _ := ret[0].(async.Value)
	ret1, _ := ret[1].(bool)
	return ret0, ret1
}

func ( m * MockCache ) Get ( arg0 string ) ( async . Value , bool ) { ret := m . ctrl . Call ( m , " " , arg0 ) ret0 , _ := ret [ 0 ] . ( async . Value ) ret1 , _ := ret [ 1 ] . ( bool ) return ret0 , ret1 }


func (m *MockCache) Set(arg0 string, arg1 async.Value) {
	m.ctrl.Call(m, "Set", arg0, arg1)
}

func ( m * MockCache ) Set ( arg0 string , arg1 async . Value ) { m . ctrl . Call ( m , " " , arg0 , arg1 ) }


func (mr *MockCacheMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockCache)(nil).Set), arg0, arg1)
}

func ( mr * MockCacheMockRecorder ) Set ( arg0 , arg1 interface { } ) * gomock . Call { return mr . mock . ctrl . RecordCallWithMethodType ( mr . mock , " " , reflect . TypeOf ( ( * MockCache ) ( nil ) . Set ) , arg0 , arg1 ) }

// Set indicates an expected call of Set


func (engine *DockerTaskEngine) newManagedTask(task *apitask.Task) *managedTask {
	ctx, cancel := context.WithCancel(engine.ctx)
	t := &managedTask{
		ctx:                        ctx,
		cancel:                     cancel,
		Task:                       task,
		acsMessages:                make(chan acsTransition),
		dockerMessages:             make(chan dockerContainerChange),
		resourceStateChangeEvent:   make(chan resourceStateChange),
		engine:                     engine,
		cfg:                        engine.cfg,
		stateChangeEvents:          engine.stateChangeEvents,
		containerChangeEventStream: engine.containerChangeEventStream,
		saver:                      engine.saver,
		credentialsManager:         engine.credentialsManager,
		cniClient:                  engine.cniClient,
		taskStopWG:                 engine.taskStopGroup,
		steadyStatePollInterval:    engine.taskSteadyStatePollInterval,
	}
	engine.managedTasks[task.Arn] = t
	return t
}

func ( engine * DockerTaskEngine ) newManagedTask ( task * apitask . Task ) * managedTask { ctx , cancel := context . WithCancel ( engine . ctx ) t := & managedTask { ctx : ctx , cancel : cancel , Task : task , acsMessages : make ( chan acsTransition ) , dockerMessages : make ( chan dockerContainerChange ) , resourceStateChangeEvent : make ( chan resourceStateChange ) , engine : engine , cfg : engine . cfg , stateChangeEvents : engine . stateChangeEvents , containerChangeEventStream : engine . containerChangeEventStream , saver : engine . saver , credentialsManager : engine . credentialsManager , cniClient : engine . cniClient , taskStopWG : engine . taskStopGroup , steadyStatePollInterval : engine . taskSteadyStatePollInterval , } engine . managedTasks [ task . Arn ] = t return t }

// newManagedTask is a method on DockerTaskEngine to create a new managedTask.
// This method must only be called when the engine.processTasks write lock is
// already held.

newManagedTask is a method on DockerTaskEngine to create a new managedTask . This method must only be called when the engine . processTasks write lock is already held .


func (mtask *managedTask) overseeTask() {
	// Do a single updatestatus at the beginning to create the container
	// `desiredstatus`es which are a construct of the engine used only here,
	// not present on the backend
	mtask.UpdateStatus()
	// If this was a 'state restore', send all unsent statuses
	mtask.emitCurrentStatus()

	// Wait for host resources required by this task to become available
	mtask.waitForHostResources()

	// Main infinite loop. This is where we receive messages and dispatch work.
	for {
		select {
		case <-mtask.ctx.Done():
			seelog.Infof("Managed task [%s]: parent context cancelled, exit", mtask.Arn)
			return
		default:
		}

		// If it's steadyState, just spin until we need to do work
		for mtask.steadyState() {
			mtask.waitSteady()
		}

		if !mtask.GetKnownStatus().Terminal() {
			// If we aren't terminal and we aren't steady state, we should be
			// able to move some containers along.
			seelog.Debugf("Managed task [%s]: task not steady state or terminal; progressing it",
				mtask.Arn)

			mtask.progressTask()
		}

		// If we reach this point, we've changed the task in some way.
		// Conversely, for it to spin in steady state it will have to have been
		// loaded in steady state or progressed through here, so saving here should
		// be sufficient to capture state changes.
		err := mtask.saver.Save()
		if err != nil {
			seelog.Warnf("Managed task [%s]: unable to checkpoint task's states to disk: %v",
				mtask.Arn, err)
		}

		if mtask.GetKnownStatus().Terminal() {
			break
		}
	}
	// We only break out of the above if this task is known to be stopped. Do
	// onetime cleanup here, including removing the task after a timeout
	seelog.Debugf("Managed task [%s]: task has reached stopped. Waiting for container cleanup", mtask.Arn)
	mtask.cleanupCredentials()
	if mtask.StopSequenceNumber != 0 {
		seelog.Debugf("Managed task [%s]: marking done for this sequence: %d",
			mtask.Arn, mtask.StopSequenceNumber)
		mtask.taskStopWG.Done(mtask.StopSequenceNumber)
	}
	// TODO: make this idempotent on agent restart
	go mtask.releaseIPInIPAM()
	mtask.cleanupTask(mtask.cfg.TaskCleanupWaitDuration)
}

// overseeTask is the main goroutine of the managedTask. It runs an infinite
// loop of receiving messages and attempting to take action based on those
// messages.

overseeTask is the main goroutine of the managedTask . It runs an infinite loop of receiving messages and attempting to take action based on those messages .


func (mtask *managedTask) emitCurrentStatus() {
	for _, container := range mtask.Containers {
		mtask.emitContainerEvent(mtask.Task, container, "")
	}
	mtask.emitTaskEvent(mtask.Task, "")
}

func ( mtask * managedTask ) emitCurrentStatus ( ) { for _ , container := range mtask . Containers { mtask . emitContainerEvent ( mtask . Task , container , " " ) } mtask . emitTaskEvent ( mtask . Task , " " ) }

// emitCurrentStatus emits a container event for every container and a task
// event for the task

emitCurrentStatus emits a container event for every container and a task event for the task


func (mtask *managedTask) waitForHostResources() {
	if mtask.StartSequenceNumber == 0 {
		// This is the first transition on this host. No need to wait
		return
	}
	if mtask.GetDesiredStatus().Terminal() {
		// Task's desired status is STOPPED. No need to wait in this case either
		return
	}

	seelog.Infof("Managed task [%s]: waiting for any previous stops to complete. Sequence number: %d",
		mtask.Arn, mtask.StartSequenceNumber)

	othersStoppedCtx, cancel := context.WithCancel(mtask.ctx)
	defer cancel()

	go func() {
		mtask.taskStopWG.Wait(mtask.StartSequenceNumber)
		cancel()
	}()

	for !mtask.waitEvent(othersStoppedCtx.Done()) {
		if mtask.GetDesiredStatus().Terminal() {
			// If we end up here, that means we received a start then stop for this
			// task before a task that was expected to stop before it could
			// actually stop
			break
		}
	}
	seelog.Infof("Managed task [%s]: wait over; ready to move towards status: %s",
		mtask.Arn, mtask.GetDesiredStatus().String())
}

// waitForHostResources waits for host resources to become available to start
// the task. This involves waiting for previous stops to complete so the
// resources become free.

waitForHostResources waits for host resources to become available to start the task . This involves waiting for previous stops to complete so the resources become free .


func (mtask *managedTask) waitSteady() {
	seelog.Infof("Managed task [%s]: task at steady state: %s", mtask.Arn, mtask.GetKnownStatus().String())

	timeoutCtx, cancel := context.WithTimeout(mtask.ctx, mtask.steadyStatePollInterval)
	defer cancel()
	timedOut := mtask.waitEvent(timeoutCtx.Done())

	if timedOut {
		seelog.Debugf("Managed task [%s]: checking to make sure it's still at steadystate", mtask.Arn)
		go mtask.engine.checkTaskState(mtask.Task)
	}
}

func ( mtask * managedTask ) waitSteady ( ) { seelog . Infof ( " " , mtask . Arn , mtask . GetKnownStatus ( ) . String ( ) ) timeoutCtx , cancel := context . WithTimeout ( mtask . ctx , mtask . steadyStatePollInterval ) defer cancel ( ) timedOut := mtask . waitEvent ( timeoutCtx . Done ( ) ) if timedOut { seelog . Debugf ( " " , mtask . Arn ) go mtask . engine . checkTaskState ( mtask . Task ) } }

// waitSteady waits for a task to leave steady-state by waiting for a new
// event, or a timeout.

waitSteady waits for a task to leave steady - state by waiting for a new event or a timeout .


func (mtask *managedTask) steadyState() bool {
	select {
	case <-mtask.ctx.Done():
		seelog.Info("Context expired. No longer steady.")
		return false
	default:
		taskKnownStatus := mtask.GetKnownStatus()
		return taskKnownStatus == apitaskstatus.TaskRunning && taskKnownStatus >= mtask.GetDesiredStatus()
	}
}

func ( mtask * managedTask ) steadyState ( ) bool { select { case <- mtask . ctx . Done ( ) : seelog . Info ( " " ) return false default : taskKnownStatus := mtask . GetKnownStatus ( ) return taskKnownStatus == apitaskstatus . TaskRunning && taskKnownStatus >= mtask . GetDesiredStatus ( ) } }

// steadyState returns if the task is in a steady state. Steady state is when task's desired
// and known status are both RUNNING

steadyState returns if the task is in a steady state . Steady state is when task s desired and known status are both RUNNING


func (mtask *managedTask) cleanupCredentials() {
	taskCredentialsID := mtask.GetCredentialsID()
	if taskCredentialsID != "" {
		mtask.credentialsManager.RemoveCredentials(taskCredentialsID)
	}
}

func ( mtask * managedTask ) cleanupCredentials ( ) { taskCredentialsID := mtask . GetCredentialsID ( ) if taskCredentialsID != " " { mtask . credentialsManager . RemoveCredentials ( taskCredentialsID ) } }

// cleanupCredentials removes credentials for a stopped task


func (mtask *managedTask) waitEvent(stopWaiting <-chan struct{}) bool {
	seelog.Debugf("Managed task [%s]: waiting for event for task", mtask.Arn)
	select {
	case acsTransition := <-mtask.acsMessages:
		seelog.Debugf("Managed task [%s]: got acs event", mtask.Arn)
		mtask.handleDesiredStatusChange(acsTransition.desiredStatus, acsTransition.seqnum)
		return false
	case dockerChange := <-mtask.dockerMessages:
		seelog.Debugf("Managed task [%s]: got container [%s] event: [%s]",
			mtask.Arn, dockerChange.container.Name, dockerChange.event.Status.String())
		mtask.handleContainerChange(dockerChange)
		return false
	case resChange := <-mtask.resourceStateChangeEvent:
		res := resChange.resource
		seelog.Debugf("Managed task [%s]: got resource [%s] event: [%s]",
			mtask.Arn, res.GetName(), res.StatusString(resChange.nextState))
		mtask.handleResourceStateChange(resChange)
		return false
	case <-stopWaiting:
		seelog.Debugf("Managed task [%s]: no longer waiting", mtask.Arn)
		return true
	}
}

func ( mtask * managedTask ) waitEvent ( stopWaiting <- chan struct { } ) bool { seelog . Debugf ( " " , mtask . Arn ) select { case acsTransition := <- mtask . acsMessages : seelog . Debugf ( " " , mtask . Arn ) mtask . handleDesiredStatusChange ( acsTransition . desiredStatus , acsTransition . seqnum ) return false case dockerChange := <- mtask . dockerMessages : seelog . Debugf ( " " , mtask . Arn , dockerChange . container . Name , dockerChange . event . Status . String ( ) ) mtask . handleContainerChange ( dockerChange ) return false case resChange := <- mtask . resourceStateChangeEvent : res := resChange . resource seelog . Debugf ( " " , mtask . Arn , res . GetName ( ) , res . StatusString ( resChange . nextState ) ) mtask . handleResourceStateChange ( resChange ) return false case <- stopWaiting : seelog . Debugf ( " " , mtask . Arn ) return true } }

// waitEvent waits for any event to occur. If an event occurs, the appropriate
// handler is called. Generally the stopWaiting arg is the context's Done
// channel. When the Done channel is signalled by the context, waitEvent will
// return true.

waitEvent waits for any event to occur . If an event occurs the appropriate handler is called . Generally the stopWaiting arg is the context s Done channel . When the Done channel is signalled by the context waitEvent will return true .


func (mtask *managedTask) handleResourceStateChange(resChange resourceStateChange) {
	// locate the resource
	res := resChange.resource
	if !mtask.isResourceFound(res) {
		seelog.Criticalf("Managed task [%s]: state error; invoked with another task's resource [%s]",
			mtask.Arn, res.GetName())
		return
	}

	status := resChange.nextState
	err := resChange.err
	currentKnownStatus := res.GetKnownStatus()

	if status <= currentKnownStatus {
		seelog.Infof("Managed task [%s]: redundant resource state change. %s to %s, but already %s",
			mtask.Arn, res.GetName(), res.StatusString(status), res.StatusString(currentKnownStatus))
		return
	}

	if err == nil {
		res.SetKnownStatus(status)
		mtask.engine.saver.Save()
		return
	}
	seelog.Debugf("Managed task [%s]: error while transitioning resource %s to %s: %v",
		mtask.Arn, res.GetName(), res.StatusString(status), err)
	if status == res.SteadyState() {
		seelog.Errorf("Managed task [%s]: error while creating resource %s, setting the task's desired status to STOPPED",
			mtask.Arn, res.GetName())
		mtask.SetDesiredStatus(apitaskstatus.TaskStopped)
		mtask.Task.SetTerminalReason(res.GetTerminalReason())
		mtask.engine.saver.Save()
	}
}

func
(
mtask
*
managedTask
)
handleResourceStateChange
(
resChange
resourceStateChange
)
{
// locate the resource
res
:=
resChange
.
resource
if
!
mtask
.
isResourceFound
(
res
)
{
seelog
.
Criticalf
(
"
"
,
mtask
.
Arn
,
res
.
GetName
(
)
)
return
}
status
:=
resChange
.
nextState
err
:=
resChange
.
err
currentKnownStatus
:=
res
.
GetKnownStatus
(
)
if
status
<=
currentKnownStatus
{
seelog
.
Infof
(
"
"
,
mtask
.
Arn
,
res
.
GetName
(
)
,
res
.
StatusString
(
status
)
,
res
.
StatusString
(
currentKnownStatus
)
)
return
}
if
err
==
{
res
.
SetKnownStatus
(
status
)
mtask
.
engine
.
saver
.
Save
(
)
return
}
seelog
.
Debugf
(
"
"
,
mtask
.
Arn
,
res
.
GetName
(
)
,
res
.
StatusString
(
status
)
,
err
)
if
status
==
res
.
SteadyState
(
)
{
seelog
.
Errorf
(
"
"
,
mtask
.
Arn
,
res
.
GetName
(
)
)
mtask
.
SetDesiredStatus
(
apitaskstatus
.
TaskStopped
)
mtask
.
Task
.
SetTerminalReason
(
res
.
GetTerminalReason
(
)
)
mtask
.
engine
.
saver
.
Save
(
)
}
}

// handleResourceStateChange attempts to update resource's known status depending on
// the current status and errors during transition

handleResourceStateChange attempts to update resource s known status depending on the current status and errors during transition


func (mtask *managedTask) emitContainerEvent(task *apitask.Task, cont *apicontainer.Container, reason string) {
	event, err := api.NewContainerStateChangeEvent(task, cont, reason)
	if err != nil {
		seelog.Debugf("Managed task [%s]: unable to create state change event for container [%s]: %v",
			task.Arn, cont.Name, err)
		return
	}

	seelog.Infof("Managed task [%s]: sending container change event [%s]: %s",
		mtask.Arn, cont.Name, event.String())
	mtask.stateChangeEvents <- event
	seelog.Infof("Managed task [%s]: sent container change event [%s]: %s",
		mtask.Arn, cont.Name, event.String())
}

func ( mtask * managedTask ) emitContainerEvent ( task * apitask . Task , cont * apicontainer . Container , reason string ) { event , err := api . NewContainerStateChangeEvent ( task , cont , reason ) if err != nil { seelog . Debugf ( " " , task . Arn , cont . Name , err ) return } seelog . Infof ( " " , mtask . Arn , cont . Name , event . String ( ) ) mtask . stateChangeEvents <- event seelog . Infof ( " " , mtask . Arn , cont . Name , event . String ( ) ) }

// emitContainerEvent passes a given event up through the containerEvents channel if necessary.
// It will omit events the backend would not process and will perform best-effort deduplication of events.

emitContainerEvent passes a given event up through the containerEvents channel if necessary . It will omit events the backend would not process and will perform best - effort deduplication of events .


func (mtask *managedTask) releaseIPInIPAM() {
	if mtask.ENI == nil {
		return
	}
	seelog.Infof("Managed task [%s]: IPAM releasing ip for task eni", mtask.Arn)

	cfg, err := mtask.BuildCNIConfig()
	if err != nil {
		seelog.Warnf("Managed task [%s]: failed to release ip; unable to build cni configuration: %v",
			mtask.Arn, err)
		return
	}
	err = mtask.cniClient.ReleaseIPResource(cfg)
	if err != nil {
		seelog.Warnf("Managed task [%s]: failed to release ip; IPAM error: %v",
			mtask.Arn, err)
		return
	}
}

func ( mtask * managedTask ) releaseIPInIPAM ( ) { if mtask . ENI == nil { return } seelog . Infof ( " " , mtask . Arn ) cfg , err := mtask . BuildCNIConfig ( ) if err != nil { seelog . Warnf ( " " , mtask . Arn , err ) return } err = mtask . cniClient . ReleaseIPResource ( cfg ) if err != nil { seelog . Warnf ( " " , mtask . Arn , err ) return } }

// releaseIPInIPAM releases the ip used by the task for awsvpc


func (mtask *managedTask) handleStoppedToRunningContainerTransition(status apicontainerstatus.ContainerStatus, container *apicontainer.Container) {
	containerKnownStatus := container.GetKnownStatus()
	if status > containerKnownStatus {
		// Event status is greater than container's known status.
		// This is not a backward transition, return
		return
	}
	if containerKnownStatus != apicontainerstatus.ContainerStopped {
		// Container's known status is not STOPPED. Nothing to do here.
		return
	}
	if !status.IsRunning() {
		// Container's 'to' transition was not either of RUNNING or RESOURCES_PROVISIONED
		// states. Nothing to do in this case as well
		return
	}
	// If the container becomes running after we've stopped it (possibly
	// because we got an error running it and it ran anyways), the first time
	// update it to 'known running' so that it will be driven back to stopped
	mtask.unexpectedStart.Do(func() {
		seelog.Warnf("Managed task [%s]: stopped container [%s] came back; re-stopping it once",
			mtask.Arn, container.Name)
		go mtask.engine.transitionContainer(mtask.Task, container, apicontainerstatus.ContainerStopped)
		// This will not proceed afterwards because status <= knownstatus below
	})
}

// handleStoppedToRunningContainerTransition detects a "backwards" container
// transition where a known-stopped container is found to be running again and
// handles it.


func (mtask *managedTask) handleEventError(containerChange dockerContainerChange, currentKnownStatus apicontainerstatus.ContainerStatus) bool {
	container := containerChange.container
	event := containerChange.event
	if container.ApplyingError == nil {
		container.ApplyingError = apierrors.NewNamedError(event.Error)
	}
	switch event.Status {
	// event.Status is the desired container transition from container's known status
	// (* -> event.Status)
	case apicontainerstatus.ContainerPulled:
		// If the agent pull behavior is always or once, we receive the error because
		// the image pull fails, the task should fail. If we don't fail task here,
		// then the cached image will probably be used for creating container, and we
		// don't want to use cached image for both cases.
		if mtask.cfg.ImagePullBehavior == config.ImagePullAlwaysBehavior ||
			mtask.cfg.ImagePullBehavior == config.ImagePullOnceBehavior {
			seelog.Errorf("Managed task [%s]: error while pulling image %s for container %s , moving task to STOPPED: %v",
				mtask.Arn, container.Image, container.Name, event.Error)
			// The task should be stopped regardless of whether this container is
			// essential or non-essential.
			mtask.SetDesiredStatus(apitaskstatus.TaskStopped)
			return false
		}
		// If the agent pull behavior is prefer-cached, we receive the error because
		// the image pull fails and there is no cached image in local, we don't make
		// the task fail here, will let create container handle it instead.
		// If the agent pull behavior is default, use local image cache directly,
		// assuming it exists.
		seelog.Errorf("Managed task [%s]: error while pulling container %s and image %s, will try to run anyway: %v",
			mtask.Arn, container.Name, container.Image, event.Error)
		// proceed anyway
		return true
	case apicontainerstatus.ContainerStopped:
		// Container's desired transition was to 'STOPPED'
		return mtask.handleContainerStoppedTransitionError(event, container, currentKnownStatus)
	case apicontainerstatus.ContainerStatusNone:
		fallthrough
	case apicontainerstatus.ContainerCreated:
		// No need to explicitly stop containers if this is a * -> NONE/CREATED transition
		seelog.Warnf("Managed task [%s]: error creating container [%s]; marking its desired status as STOPPED: %v",
			mtask.Arn, container.Name, event.Error)
		container.SetKnownStatus(currentKnownStatus)
		container.SetDesiredStatus(apicontainerstatus.ContainerStopped)
		return false
	default:
		// If this is a * -> RUNNING / RESOURCES_PROVISIONED transition, we need to stop
		// the container.
		seelog.Warnf("Managed task [%s]: error starting/provisioning container[%s]; marking its desired status as STOPPED: %v",
			mtask.Arn, container.Name, event.Error)
		container.SetKnownStatus(currentKnownStatus)
		container.SetDesiredStatus(apicontainerstatus.ContainerStopped)
		errorName := event.Error.ErrorName()
		if errorName == dockerapi.DockerTimeoutErrorName || errorName == dockerapi.CannotInspectContainerErrorName {
			// If there's an error with inspecting the container or in case of timeout error,
			// we'll also assume that the container has transitioned to RUNNING and issue
			// a stop. See #1043 for details
			seelog.Warnf("Managed task [%s]: forcing container [%s] to stop",
				mtask.Arn, container.Name)
			go mtask.engine.transitionContainer(mtask.Task, container, apicontainerstatus.ContainerStopped)
		}
		// Container known status not changed, no need for further processing
		return false
	}
}

// handleEventError handles a container change event error and decides whether
// we should proceed to transition the container

handleEventError handles a container change event error and decides whether we should proceed to transition the container


func (mtask *managedTask) handleContainerStoppedTransitionError(event dockerapi.DockerContainerChangeEvent,
	container *apicontainer.Container,
	currentKnownStatus apicontainerstatus.ContainerStatus) bool {
	// If we were trying to transition to stopped and had a timeout error
	// from docker, reset the known status to the current status and return
	// This ensures that we don't emit a containerstopped event; a
	// terminal container event from docker event stream will instead be
	// responsible for the transition. Alternatively, the steadyState check
	// could also trigger the progress and have another go at stopping the
	// container
	if event.Error.ErrorName() == dockerapi.DockerTimeoutErrorName {
		seelog.Infof("Managed task [%s]: '%s' error stopping container [%s]. Ignoring state change: %v",
			mtask.Arn, dockerapi.DockerTimeoutErrorName, container.Name, event.Error.Error())
		container.SetKnownStatus(currentKnownStatus)
		return false
	}
	// If docker returned a transient error while trying to stop a container,
	// reset the known status to the current status and return
	cannotStopContainerError, ok := event.Error.(cannotStopContainerError)
	if ok && cannotStopContainerError.IsRetriableError() {
		seelog.Infof("Managed task [%s]: error stopping the container [%s]. Ignoring state change: %v",
			mtask.Arn, container.Name, cannotStopContainerError.Error())
		container.SetKnownStatus(currentKnownStatus)
		return false
	}

	// If we were trying to transition to stopped and had an error, we
	// clearly can't just continue trying to transition it to stopped
	// again and again. In this case, assume it's stopped (or close
	// enough) and get on with it
	// This can happen in cases where the container we tried to stop
	// was already stopped or did not exist at all.
	seelog.Warnf("Managed task [%s]: 'docker stop' for container [%s] returned %s: %s",
		mtask.Arn, container.Name, event.Error.ErrorName(), event.Error.Error())
	container.SetKnownStatus(apicontainerstatus.ContainerStopped)
	container.SetDesiredStatus(apicontainerstatus.ContainerStopped)
	return true
}

// handleContainerStoppedTransitionError handles an error when transitioning a container to
// STOPPED. It returns a boolean indicating whether the tak can continue with updating its
// state

handleContainerStoppedTransitionError handles an error when transitioning a container to STOPPED . It returns a boolean indicating whether the tak can continue with updating its state