// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package app

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"

	"github.com/aws/amazon-ecs-agent/agent/config"
	"github.com/aws/amazon-ecs-agent/agent/dockerclient"
	dm "github.com/aws/amazon-ecs-agent/agent/engine/daemonmanager"
	"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
	"github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
	md "github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/ecs/types"
	"github.com/cihub/seelog"
	"github.com/pkg/errors"
)

const (
	// capabilityPrefix is deprecated. For new capabilities, use attributePrefix.
	capabilityPrefix                                       = "com.amazonaws.ecs.capability."
	attributePrefix                                        = "ecs.capability."
	capabilityTaskIAMRole                                  = "task-iam-role"
	capabilityTaskIAMRoleNetHost                           = "task-iam-role-network-host"
	taskENIAttributeSuffix                                 = "task-eni"
	taskENIIPv6AttributeSuffix                             = "task-eni.ipv6"
	taskENIBlockInstanceMetadataAttributeSuffix            = "task-eni-block-instance-metadata"
	appMeshAttributeSuffix                                 = "aws-appmesh"
	cniPluginVersionSuffix                                 = "cni-plugin-version"
	capabilityTaskCPUMemLimit                              = "task-cpu-mem-limit"
	capabilityIncreasedTaskCPULimit                        = "increased-task-cpu-limit"
	capabilityDockerPluginInfix                            = "docker-plugin."
	attributeSeparator                                     = "."
	capabilityPrivateRegistryAuthASM                       = "private-registry-authentication.secretsmanager"
	capabilitySecretEnvSSM                                 = "secrets.ssm.environment-variables"
	capabilitySecretEnvASM                                 = "secrets.asm.environment-variables"
	capabilitySecretLogDriverSSM                           = "secrets.ssm.bootstrap.log-driver"
	capabilitySecretLogDriverASM                           = "secrets.asm.bootstrap.log-driver"
	capabiltyPIDAndIPCNamespaceSharing                     = "pid-ipc-namespace-sharing"
	capabilityNvidiaDriverVersionInfix                     = "nvidia-driver-version."
	capabilityECREndpoint                                  = "ecr-endpoint"
	capabilityContainerOrdering                            = "container-ordering"
	taskEIAAttributeSuffix                                 = "task-eia"
	taskEIAWithOptimizedCPU                                = "task-eia.optimized-cpu"
	taskENITrunkingAttributeSuffix                         = "task-eni-trunking"
	branchCNIPluginVersionSuffix                           = "branch-cni-plugin-version"
	capabilityFirelensFluentd                              = "firelens.fluentd"
	capabilityFirelensFluentbit                            = "firelens.fluentbit"
	capabilityFirelensLoggingDriver                        = "logging-driver.awsfirelens"
	capabilityFireLensLoggingDriverConfigBufferLimitSuffix = ".log-driver-buffer-limit"
	capabilityFirelensConfigFile                           = "firelens.options.config.file"
	capabilityFirelensConfigS3                             = "firelens.options.config.s3"
	capabilityFirelensNonRootUser                          = "firelens.non-root-user"
	capabilityFullTaskSync                                 = "full-sync"
	capabilityGMSA                                         = "gmsa"
	capabilityGMSADomainless                               = "gmsa-domainless"
	capabilityEFS                                          = "efs"
	capabilityEFSAuth                                      = "efsAuth"
	capabilityEnvFilesS3                                   = "env-files.s3"
	capabilityFSxWindowsFileServer                         = "fsxWindowsFileServer"
	capabilityExec                                         = "execute-command"
	capabilityExecBinRelativePath                          = "bin"
	capabilityExecConfigRelativePath                       = "config"
	capabilityExecCertsRelativePath                        = "certs"
	capabilityExternal                                     = "external"
	capabilityServiceConnect                               = "service-connect-v1"
	capabilityGpuDriverVersion                             = "gpu-driver-version"
	capabilityEBSTaskAttach                                = "storage.ebs-task-volume-attach"
	capabilityEBSTANonRootUser                             = "storage.ebsta-non-root-user"
	capabilityContainerRestartPolicy                       = "container-restart-policy"
	capabilityFaultInjection                               = "fault-injection"
	capabilityIPv6Only                                     = "ipv6-only"

	// network capabilities, going forward, please append "network." prefix to any new networking capability we introduce
	networkCapabilityPrefix      = "network."
	capabilityContainerPortRange = networkCapabilityPrefix + "container-port-range"
)

var (
	nameOnlyAttributes = []string{
		// ecs agent version 1.19.0 supports private registry authentication using
		// aws secrets manager
		capabilityPrivateRegistryAuthASM,
		// ecs agent version 1.22.0 supports ecs secrets integrating with aws systems manager
		capabilitySecretEnvSSM,
		// ecs agent version 1.27.0 supports ecs secrets for logging drivers
		capabilitySecretLogDriverSSM,
		// support ecr endpoint override
		capabilityECREndpoint,
		// ecs agent version 1.23.0 supports ecs secrets integrating with aws secrets manager
		capabilitySecretEnvASM,
		// ecs agent version 1.27.0 supports ecs secrets for logging drivers
		capabilitySecretLogDriverASM,
		// support container ordering in agent
		capabilityContainerOrdering,
		// support full task sync
		capabilityFullTaskSync,
		// ecs agent version 1.39.0 supports bulk loading env vars through environmentFiles in S3
		capabilityEnvFilesS3,
		// support container port range in container definition - port mapping field
		capabilityContainerPortRange,
		// support container restart policy
		capabilityContainerRestartPolicy,
	}
	// use empty struct as value type to simulate set
	capabilityExecInvalidSsmVersions = map[string]struct{}{}

	pathExists              = defaultPathExists
	getSubDirectories       = defaultGetSubDirectories
	isPlatformExecSupported = defaultIsPlatformExecSupported

	// List of capabilities that are not supported on external capacity.
	externalUnsupportedCapabilities = []string{
		attributePrefix + taskENIAttributeSuffix,
		attributePrefix + cniPluginVersionSuffix,
		attributePrefix + taskENIIPv6AttributeSuffix,
		attributePrefix + taskENIBlockInstanceMetadataAttributeSuffix,
		attributePrefix + taskENITrunkingAttributeSuffix,
		attributePrefix + appMeshAttributeSuffix,
		attributePrefix + taskEIAAttributeSuffix,
		attributePrefix + taskEIAWithOptimizedCPU,
		attributePrefix + capabilityServiceConnect,
		attributePrefix + capabilityEBSTaskAttach,
		attributePrefix + capabilityEBSTANonRootUser,
		attributePrefix + capabilityFaultInjection,
	}
	// List of capabilities that are only supported on external capaciity. Currently only one but keep as a list
	// for future proof and also align with externalUnsupportedCapabilities.
	externalSpecificCapabilities = []string{
		attributePrefix + capabilityExternal,
	}

	capabilityExecRootDir = filepath.Join(capabilityDepsRootDir, capabilityExec)
	binDir                = filepath.Join(capabilityExecRootDir, capabilityExecBinRelativePath)
	configDir             = filepath.Join(capabilityExecRootDir, capabilityExecConfigRelativePath)

	// excludedDockerAPIVersions contains Docker API versions that should not be reported as capabilities
	// This is in addition to versions 1.33 and beyond
	excludedDockerAPIVersions = map[dockerclient.DockerVersion]bool{
		dockerclient.Version_1_26: true,
		dockerclient.Version_1_27: true,
		dockerclient.Version_1_31: true,
	}
)

// capabilities returns the supported capabilities of this agent / docker-client pair.
// Currently, the following capabilities are possible:
//
//	com.amazonaws.ecs.capability.privileged-container
//	com.amazonaws.ecs.capability.docker-remote-api.1.17
//	com.amazonaws.ecs.capability.docker-remote-api.1.18
//	com.amazonaws.ecs.capability.docker-remote-api.1.19
//	com.amazonaws.ecs.capability.docker-remote-api.1.20
//	com.amazonaws.ecs.capability.logging-driver.json-file
//	com.amazonaws.ecs.capability.logging-driver.syslog
//	com.amazonaws.ecs.capability.logging-driver.fluentd
//	com.amazonaws.ecs.capability.logging-driver.journald
//	com.amazonaws.ecs.capability.logging-driver.gelf
//	com.amazonaws.ecs.capability.logging-driver.none
//	com.amazonaws.ecs.capability.selinux
//	com.amazonaws.ecs.capability.apparmor
//	com.amazonaws.ecs.capability.ecr-auth
//	com.amazonaws.ecs.capability.task-iam-role
//	com.amazonaws.ecs.capability.task-iam-role-network-host
//	ecs.capability.docker-volume-driver.${driverName}
//	ecs.capability.task-eni
//	ecs.capability.task-eni-block-instance-metadata
//	ecs.capability.execution-role-ecr-pull
//	ecs.capability.execution-role-awslogs
//	ecs.capability.container-health-check
//	ecs.capability.private-registry-authentication.secretsmanager
//	ecs.capability.secrets.ssm.environment-variables
//	ecs.capability.secrets.ssm.bootstrap.log-driver
//	ecs.capability.pid-ipc-namespace-sharing
//	ecs.capability.ecr-endpoint
//	ecs.capability.secrets.asm.environment-variables
//	ecs.capability.secrets.asm.bootstrap.log-driver
//	ecs.capability.aws-appmesh
//	ecs.capability.task-eia
//	ecs.capability.task-eni-trunking
//	ecs.capability.task-eia.optimized-cpu
//	ecs.capability.firelens.fluentd
//	ecs.capability.firelens.fluentbit
//	ecs.capability.efs
//	com.amazonaws.ecs.capability.logging-driver.awsfirelens
//	ecs.capability.logging-driver.awsfirelens.log-driver-buffer-limit
//	ecs.capability.firelens.options.config.file
//	ecs.capability.firelens.options.config.s3
//	ecs.capability.firelens.non-root-user
//	ecs.capability.full-sync
//	ecs.capability.gmsa
//	ecs.capability.efsAuth
//	ecs.capability.env-files.s3
//	ecs.capability.fsxWindowsFileServer
//	ecs.capability.execute-command
//	ecs.capability.external
//	ecs.capability.service-connect-v1
//	ecs.capability.storage.ebs-task-volume-attach
//	ecs.capability.storage.ebsta-non-root-user
//	ecs.capability.network.container-port-range
//	ecs.capability.container-restart-policy
//	ecs.capability.fault-injection
func (agent *ecsAgent) capabilities() ([]types.Attribute, error) {
	var capabilities []types.Attribute

	for _, cap := range nameOnlyAttributes {
		capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+cap)
	}

	if !agent.cfg.PrivilegedDisabled.Enabled() {
		capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+"privileged-container")
	}

	supportedVersions := make(map[dockerclient.DockerVersion]bool)
	// Determine API versions to report as supported via com.amazonaws.ecs.capability.docker-remote-api.X.XX capabilities
	// and for determining which features we support that depend on specific docker API versions
	for _, version := range dockerclient.SupportedVersionsExtended(agent.dockerClient.SupportedVersions) {
		supportedVersions[version] = true
		// Every new Docker version update brings in new client API versions that agent can support.
		// We have a limit on the number of capabilities that agent can send during instance registration.
		// Hence, we don't report  1.26, 1.27 and 1.31, 1.33 and beyond.
		// We need to continue reporting all other versions to support legacy ECS backend logic.
		if version.Compare(dockerclient.Version_1_33) < 0 && !excludedDockerAPIVersions[version] {
			capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+"docker-remote-api."+string(version))
		}
	}

	capabilities = agent.appendLoggingDriverCapabilities(capabilities, supportedVersions)

	if agent.cfg.SELinuxCapable.Enabled() {
		capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+"selinux")
	}
	if agent.cfg.AppArmorCapable.Enabled() {
		capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+"apparmor")
	}

	capabilities = agent.appendTaskIamRoleCapabilities(capabilities, supportedVersions)

	capabilities, err := agent.appendTaskCPUMemLimitCapabilities(capabilities, supportedVersions)
	if err != nil {
		return nil, err
	}

	capabilities = agent.appendIncreasedTaskCPULimitCapability(capabilities)
	capabilities = agent.appendTaskENICapabilities(capabilities)
	capabilities = agent.appendENITrunkingCapabilities(capabilities)
	capabilities = agent.appendDockerDependentCapabilities(capabilities, supportedVersions)

	// TODO: gate this on docker api version when ecs supported docker includes
	// credentials endpoint feature from upstream docker
	if agent.cfg.OverrideAWSLogsExecutionRole.Enabled() {
		capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+"execution-role-awslogs")
	}

	capabilities = agent.appendVolumeDriverCapabilities(capabilities)

	if agent.cfg.GPUSupportEnabled {
		capabilities = agent.appendNvidiaDriverVersionAttribute(capabilities)
	}

	// ecs agent version 1.22.0 supports sharing PID namespaces and IPC resource namespaces
	// with host EC2 instance and among containers within the task
	capabilities = agent.appendPIDAndIPCNamespaceSharingCapabilities(capabilities)

	// ecs agent version 1.26.0 supports aws-appmesh cni plugin
	capabilities = agent.appendAppMeshCapabilities(capabilities)

	// support elastic inference in agent
	capabilities = agent.appendTaskEIACapabilities(capabilities)

	// support aws router capabilities for fluentd
	capabilities = agent.appendFirelensFluentdCapabilities(capabilities)

	// support aws router capabilities for fluentbit
	capabilities = agent.appendFirelensFluentbitCapabilities(capabilities)

	// support aws router capabilities for log driver router
	capabilities = agent.appendFirelensLoggingDriverCapabilities(capabilities)

	// support aws router capabilities for log driver router config
	capabilities = agent.appendFirelensLoggingDriverConfigCapabilities(capabilities)

	// support firelens non root user mode
	capabilities = agent.appendFirelensNonRootUserCapability(capabilities)

	// support efs on ecs capabilities
	capabilities = agent.appendEFSCapabilities(capabilities)

	// support external firelens config
	capabilities = agent.appendFirelensConfigCapabilities(capabilities)

	// support GMSA capabilities
	capabilities = agent.appendGMSACapabilities(capabilities)

	// support GMSA domainless capabilities
	capabilities = agent.appendGMSADomainlessCapabilities(capabilities)

	// support efs auth on ecs capabilities
	for _, cap := range agent.cfg.VolumePluginCapabilities {
		capabilities = agent.appendEFSVolumePluginCapabilities(capabilities, cap)
	}

	// support fsxWindowsFileServer on ecs capabilities
	capabilities = agent.appendFSxWindowsFileServerCapabilities(capabilities)
	// add ecs-exec capabilities if applicable
	capabilities, err = agent.appendExecCapabilities(capabilities)
	if err != nil {
		return nil, err
	}
	// add service-connect capabilities if applicable
	capabilities = agent.appendServiceConnectCapabilities(capabilities)

	if agent.cfg.EBSTASupportEnabled {
		// add ebs-task-attach attribute if applicable
		capabilities = agent.appendEBSTaskAttachCapabilities(capabilities)
		capabilities = agent.appendEBSTANonRootUserCapabilities(capabilities)
	}

	if agent.cfg.External.Enabled() {
		// Add external specific capability; remove external unsupported capabilities.
		for _, cap := range externalSpecificCapabilities {
			capabilities = appendNameOnlyAttribute(capabilities, cap)
		}
		capabilities = removeAttributesByNames(capabilities, externalUnsupportedCapabilities)
	}

	capabilities = agent.appendFaultInjectionCapabilities(capabilities)

	// IPv6-only cap
	capabilities = appendIPv6OnlyCapability(capabilities)

	return capabilities, nil
}

func (agent *ecsAgent) appendDockerDependentCapabilities(capabilities []types.Attribute,
	supportedVersions map[dockerclient.DockerVersion]bool) []types.Attribute {
	if _, ok := supportedVersions[dockerclient.Version_1_19]; ok {
		capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+"ecr-auth")
		capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+"execution-role-ecr-pull")
	}
	if _, ok := supportedVersions[dockerclient.Version_1_24]; ok && !agent.cfg.DisableDockerHealthCheck.Enabled() {
		// Docker health check was added in API 1.24
		capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+"container-health-check")
	}
	return capabilities
}

func (agent *ecsAgent) appendGMSACapabilities(capabilities []types.Attribute) []types.Attribute {
	if agent.cfg.GMSACapable.Enabled() {
		return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityGMSA)
	}

	return capabilities
}

func (agent *ecsAgent) appendGMSADomainlessCapabilities(capabilities []types.Attribute) []types.Attribute {
	if agent.cfg.GMSADomainlessCapable.Enabled() {
		return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityGMSADomainless)
	}

	return capabilities
}

func (agent *ecsAgent) appendLoggingDriverCapabilities(capabilities []types.Attribute, supportedVersions map[dockerclient.DockerVersion]bool) []types.Attribute {
	for _, loggingDriver := range agent.cfg.AvailableLoggingDrivers {
		requiredVersion := dockerclient.LoggingDriverMinimumVersion[loggingDriver]
		if _, ok := supportedVersions[requiredVersion]; ok {
			capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+"logging-driver."+string(loggingDriver))
		}
	}
	return capabilities
}

func (agent *ecsAgent) appendTaskIamRoleCapabilities(capabilities []types.Attribute, supportedVersions map[dockerclient.DockerVersion]bool) []types.Attribute {
	if agent.cfg.TaskIAMRoleEnabled.Enabled() {
		// The "task-iam-role" capability is supported for docker v1.7.x onwards
		// Refer https://github.com/docker/docker/blob/master/docs/reference/api/docker_remote_api.md
		// to lookup the table of docker supportedVersions to API supportedVersions
		if _, ok := supportedVersions[dockerclient.Version_1_19]; ok {
			capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+capabilityTaskIAMRole)
		} else {
			seelog.Warn("Task IAM Role not enabled due to unsuppported Docker version")
		}
	}

	if agent.cfg.TaskIAMRoleEnabledForNetworkHost {
		// The "task-iam-role-network-host" capability is supported for docker v1.7.x onwards
		if _, ok := supportedVersions[dockerclient.Version_1_19]; ok {
			capabilities = appendNameOnlyAttribute(capabilities, capabilityPrefix+capabilityTaskIAMRoleNetHost)
		} else {
			seelog.Warn("Task IAM Role for Host Network not enabled due to unsuppported Docker version")
		}
	}
	return capabilities
}

func (agent *ecsAgent) appendTaskCPUMemLimitCapabilities(capabilities []types.Attribute, supportedVersions map[dockerclient.DockerVersion]bool) ([]types.Attribute, error) {
	if agent.cfg.TaskCPUMemLimit.Enabled() {
		if _, ok := supportedVersions[dockerclient.Version_1_22]; ok {
			capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+capabilityTaskCPUMemLimit)
		} else if agent.cfg.TaskCPUMemLimit.Value == config.ExplicitlyEnabled {
			// explicitly enabled -- return an error because we cannot fulfil an explicit request
			return nil, errors.New("engine: Task CPU + Mem limit cannot be enabled due to unsupported Docker version")
		} else {
			// implicitly enabled -- don't register the capability, but degrade gracefully
			seelog.Warn("Task CPU + Mem Limit disabled due to unsupported Docker version. API version 1.22 or greater is required.")
			agent.cfg.TaskCPUMemLimit.Value = config.ExplicitlyDisabled
		}
	}
	return capabilities, nil
}

func (agent *ecsAgent) appendIncreasedTaskCPULimitCapability(capabilities []types.Attribute) []types.Attribute {
	if !agent.cfg.TaskCPUMemLimit.Enabled() {
		// don't register the "increased-task-cpu-limit" capability if the "task-cpu-mem-limit" capability is disabled.
		// "task-cpu-mem-limit" capability may be explicitly disabled or disabled due to unsupported docker version.
		seelog.Warn("Increased Task CPU Limit capability is disabled since the Task CPU + Mem Limit capability is disabled.")
	} else {
		capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+capabilityIncreasedTaskCPULimit)
	}
	return capabilities
}

func (agent *ecsAgent) appendTaskENICapabilities(capabilities []types.Attribute) []types.Attribute {
	if agent.cfg.TaskENIEnabled.Enabled() {
		// The assumption here is that all of the dependencies for supporting the
		// Task ENI in the Agent have already been validated prior to the invocation of
		// the `agent.capabilities()` call
		capabilities = append(capabilities, types.Attribute{
			Name: aws.String(attributePrefix + taskENIAttributeSuffix),
		})
		capabilities = agent.appendIPv6Capability(capabilities)
		taskENIVersionAttribute, err := agent.getTaskENIPluginVersionAttribute()
		if err != nil {
			return capabilities
		}
		capabilities = append(capabilities, taskENIVersionAttribute)

		// We only care about AWSVPCBlockInstanceMetdata if Task ENI is enabled
		if agent.cfg.AWSVPCBlockInstanceMetdata.Enabled() {
			// If the Block Instance Metadata flag is set for AWS VPC networking mode, register a capability
			// indicating the same
			capabilities = append(capabilities, types.Attribute{
				Name: aws.String(attributePrefix + taskENIBlockInstanceMetadataAttributeSuffix),
			})
		}
	}

	return capabilities
}

func (agent *ecsAgent) appendExecCapabilities(capabilities []types.Attribute) ([]types.Attribute, error) {

	// Only Windows 2019 and above are supported, all Linux supported
	if platformSupported, err := isPlatformExecSupported(); err != nil || !platformSupported {
		return capabilities, err
	}

	// for an instance to be exec-enabled, it needs resources needed by SSM (binaries, configuration files and certs)
	// the following bind mounts are defined in ecs-init and added to the ecs-agent container

	if exists, err := dependenciesExist(dependencies); err != nil || !exists {
		return capabilities, err
	}

	// ssm binaries are stored in /bin/<version>/, 1 version is downloaded by ami builder for ECS instances
	binDependencies := map[string][]string{}
	// child folders named by version inside binDir, e.g. 3.0.236.0
	binFolders, err := getSubDirectories(binDir)
	if err != nil {
		return capabilities, err
	}
	// use raw string for regular expression to avoid escaping backslash (\)
	var validSsmVersion = regexp.MustCompile(`^\d+(\.\d+)*$`)
	for _, binFolder := range binFolders {
		if matched := validSsmVersion.Match([]byte(binFolder)); !matched {
			continue
		}
		if _, found := capabilityExecInvalidSsmVersions[binFolder]; found {
			continue
		}

		// check for the same set of binaries for all versions for now
		// change this if future versions of ssm agent require different binaries
		versionSubDirectory := filepath.Join(binDir, binFolder)
		binDependencies[versionSubDirectory] = capabilityExecRequiredBinaries
	}
	if len(binDependencies) < 1 {
		return capabilities, nil
	}
	if exists, err := checkAnyValidDependency(binDependencies); err != nil || !exists {
		return capabilities, err
	}

	return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityExec), nil
}

func (agent *ecsAgent) appendServiceConnectCapabilities(capabilities []types.Attribute) []types.Attribute {
	if loaded, _ := agent.serviceconnectManager.IsLoaded(agent.dockerClient); !loaded {
		_, err := agent.serviceconnectManager.LoadImage(agent.ctx, agent.cfg, agent.dockerClient)
		if err != nil {
			logger.Error("ServiceConnect Capability: Failed to load appnet Agent container. This container instance will not be able to support ServiceConnect tasks",
				logger.Fields{
					field.Error: err,
				},
			)
			return capabilities
		}
	}
	loadedVer, _ := agent.serviceconnectManager.GetLoadedAppnetVersion()
	supportedAppnetInterfaceVerToCapabilities, _ := agent.serviceconnectManager.GetCapabilitiesForAppnetInterfaceVersion(loadedVer)
	if supportedAppnetInterfaceVerToCapabilities == nil {
		logger.Warn("ServiceConnect Capability: No service connect capabilities were found for Appnet version:", logger.Fields{
			field.Image: loadedVer,
		},
		)
	}
	for _, serviceConnectCapability := range supportedAppnetInterfaceVerToCapabilities {
		capabilities = appendNameOnlyAttribute(capabilities, serviceConnectCapability)
	}
	return capabilities
}

func (agent *ecsAgent) appendEBSTaskAttachCapabilities(capabilities []types.Attribute) []types.Attribute {
	// todo update to import multiple daemons and append capabilities
	// for now load only EBS CSI Driver daemon
	daemonDefinitions, err := md.ImportAll()
	if err != nil {
		logger.Error(fmt.Sprintf("Daemon import failure: %s", err))
		return capabilities
	}
	if len(daemonDefinitions) == 0 {
		logger.Warn("daemonDefinitions is empty/nil after import")
		return capabilities
	}
	for _, daemonDef := range daemonDefinitions {
		if daemonDef.GetImageName() == md.EbsCsiDriver {
			csiDaemonManager := dm.NewDaemonManager(daemonDef)
			agent.setDaemonManager(md.EbsCsiDriver, csiDaemonManager)
			imageExists, err := csiDaemonManager.ImageExists()
			if !imageExists {
				logger.Error(
					"Either EBS Daemon image does not exist or failed to check its existence."+
						" This container instance will not advertise EBS Task Attach capability.",
					logger.Fields{
						field.ImageName:    csiDaemonManager.GetManagedDaemon().GetImageName(),
						field.ImageTARPath: csiDaemonManager.GetManagedDaemon().GetImageTarPath(),
						field.Error:        err,
					})
				return capabilities
			}
		}
	}
	capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+capabilityEBSTaskAttach)
	return capabilities
}

func (agent *ecsAgent) appendFaultInjectionCapabilities(capabilities []types.Attribute) []types.Attribute {
	// Check if the agent is running in EXTERNAL launch type
	if agent.cfg.External.Enabled() {
		seelog.Warn("Fault injection capability not enabled: EXTERNAL launch type detected")
		return capabilities
	}

	if isFaultInjectionToolingAvailable(agent.cfg) {
		capabilities = appendNameOnlyAttribute(capabilities, attributePrefix+capabilityFaultInjection)
		seelog.Debug("Fault injection capability is enabled.")
	} else {
		seelog.Warn("Fault injection capability not enabled: Required network tools are missing")
	}
	return capabilities
}

func defaultGetSubDirectories(path string) ([]string, error) {
	var subDirectories []string

	fileInfos, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}
	for _, fileInfo := range fileInfos {
		if fileInfo.IsDir() {
			subDirectories = append(subDirectories, fileInfo.Name())
		}
	}
	return subDirectories, nil
}

func dependenciesExist(dependencies map[string][]string) (bool, error) {
	for directory, files := range dependencies {
		if exists, err := pathExists(directory, true); err != nil || !exists {
			return false, err
		}

		for _, filename := range files {
			path := filepath.Join(directory, filename)
			if exists, err := pathExists(path, false); err != nil || !exists {
				return false, err
			}
		}
	}
	return true, nil
}

func checkAnyValidDependency(dependencies map[string][]string) (bool, error) {
	var validDependencies = 0
	for directory, files := range dependencies {
		filesValid := true
		if exists, err := pathExists(directory, true); err != nil || !exists {
			continue
		}

		for _, filename := range files {
			path := filepath.Join(directory, filename)
			if exists, err := pathExists(path, false); err != nil || !exists {
				filesValid = false
			}
		}

		if filesValid {
			validDependencies++
		}
	}
	if validDependencies >= 1 {
		return true, nil
	}
	return false, fmt.Errorf("no valid dependencies")
}

func defaultPathExists(path string, shouldBeDirectory bool) (bool, error) {
	fileInfo, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}

	isDirectory := fileInfo.IsDir()
	return (isDirectory && shouldBeDirectory) || (!isDirectory && !shouldBeDirectory), nil
}

func appendNameOnlyAttribute(attributes []types.Attribute, name string) []types.Attribute {
	return append(attributes, types.Attribute{
		Name: aws.String(name),
	})
}

func removeAttributesByNames(attributes []types.Attribute, names []string) []types.Attribute {
	nameMap := make(map[string]struct{})
	for _, name := range names {
		nameMap[name] = struct{}{}
	}

	var ret []types.Attribute
	for _, attr := range attributes {
		if _, ok := nameMap[aws.ToString(attr.Name)]; !ok {
			ret = append(ret, attr)
		}
	}
	return ret
}
