@@ -38,15 +38,22 @@ import (
3838 "github.com/aws/amazon-ecs-agent/agent/dockerclient"
3939 "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
4040 "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclientfactory"
41+ mockssmfactory "github.com/aws/amazon-ecs-agent/agent/ssm/factory/mocks"
42+ mockssm "github.com/aws/amazon-ecs-agent/agent/ssm/mocks"
4143 "github.com/aws/amazon-ecs-agent/agent/statechange"
4244 "github.com/aws/amazon-ecs-agent/agent/taskresource"
4345 taskresourcevolume "github.com/aws/amazon-ecs-agent/agent/taskresource/volume"
4446 "github.com/aws/amazon-ecs-agent/agent/utils"
4547 apicontainerstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/container/status"
4648 apitaskstatus "github.com/aws/amazon-ecs-agent/ecs-agent/api/task/status"
49+ "github.com/aws/amazon-ecs-agent/ecs-agent/credentials"
50+ "github.com/aws/amazon-ecs-agent/ecs-agent/ipcompatibility"
4751 "github.com/aws/amazon-ecs-agent/ecs-agent/utils/ttime"
52+ "github.com/golang/mock/gomock"
4853
4954 "github.com/aws/aws-sdk-go-v2/aws"
55+ "github.com/aws/aws-sdk-go-v2/service/ssm"
56+ ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
5057 "github.com/cihub/seelog"
5158 "github.com/containerd/cgroups/v3"
5259 "github.com/docker/docker/api/types"
@@ -1908,3 +1915,107 @@ func TestHostResourceManagerLaunchTypeBehavior(t *testing.T) {
19081915 })
19091916 }
19101917}
1918+
1919+ // TestTaskResourceDependencyOnRestart verifies that a task with resources properly waits for execution role credentials after agent restart, blocking
1920+ // until credentials arrive from ACS, then progressing to RUNNING.
1921+ // Note: although this test asserts functionality that is platform-agnostic, the test setup is such on Windows that we'd run into a timing issue.
1922+ // This is because the VerifyManifestPulledStateChange helper methods wait for events on the task engine's state channels,
1923+ // but on Windows, these are skipped due to no local registry setup, so the test progresses to its assertions faster than intended.
1924+ func TestTaskResourceDependencyOnRestart (t * testing.T ) {
1925+ ctrl := gomock .NewController (t )
1926+ defer ctrl .Finish ()
1927+
1928+ taskArn := "arn:aws:ecs:us-east-1:123456789012:task/testTaskResourceDependencyOnRestart"
1929+ testCredentialsID := "test-execution-credentials-id"
1930+ secretValueFrom := "arn:aws:ssm:us-east-1:123456789012:parameter/test-secret"
1931+ secretValue := "mock-secret-value"
1932+
1933+ // Create a task with a SSM secret resource dependency
1934+ testTask := CreateTestTask (taskArn )
1935+ testTask .Containers [0 ].Image = "busybox:latest" // Override container image
1936+ testTask .Containers [0 ].Secrets = []apicontainer.Secret {
1937+ {
1938+ Name : "test-secret" ,
1939+ ValueFrom : secretValueFrom ,
1940+ Provider : "ssm" ,
1941+ Region : "us-east-1" ,
1942+ },
1943+ }
1944+ testTask .Containers [0 ].TransitionDependenciesMap = make (map [apicontainerstatus.ContainerStatus ]apicontainer.TransitionDependencySet )
1945+ testTask .ResourcesMapUnsafe = make (map [string ][]taskresource.TaskResource )
1946+ testTask .SetExecutionRoleCredentialsID (testCredentialsID )
1947+
1948+ // Start engine with default config
1949+ taskEngine , done , _ , credentialsManager := setupWithDefaultConfig (t )
1950+ defer done ()
1951+
1952+ // Set up SSM mocks
1953+ ssmClientCreator := mockssmfactory .NewMockSSMClientCreator (ctrl )
1954+ mockSSMClient := mockssm .NewMockSSMClient (ctrl )
1955+ mockCreds := & credentials.TaskIAMRoleCredentials {
1956+ ARN : taskArn ,
1957+ IAMRoleCredentials : credentials.IAMRoleCredentials {
1958+ RoleArn : "arn:aws:iam::123456789012:role/execution-role" ,
1959+ CredentialsID : testCredentialsID ,
1960+ },
1961+ }
1962+ ssmOutput := & ssm.GetParametersOutput {
1963+ Parameters : []ssmtypes.Parameter {
1964+ {
1965+ Name : aws .String (secretValueFrom ),
1966+ Value : aws .String (secretValue ),
1967+ },
1968+ },
1969+ }
1970+ ssmClientCreator .EXPECT ().NewSSMClient ("us-east-1" , mockCreds .IAMRoleCredentials , ipcompatibility .NewIPv4OnlyCompatibility ()).Return (mockSSMClient , nil ).AnyTimes ()
1971+ mockSSMClient .EXPECT ().GetParameters (gomock .Any (), gomock .Any ()).Do (func (ctx context.Context , in * ssm.GetParametersInput , optFns ... func (* ssm.Options )) {
1972+ require .Equal (t , []string {secretValueFrom }, in .Names )
1973+ }).Return (ssmOutput , nil ).AnyTimes ()
1974+
1975+ // Configure engine with SSM client creator
1976+ taskEngine .(* DockerTaskEngine ).resourceFields = & taskresource.ResourceFields {
1977+ ResourceFieldsCommon : & taskresource.ResourceFieldsCommon {
1978+ SSMClientCreator : ssmClientCreator ,
1979+ CredentialsManager : credentialsManager ,
1980+ },
1981+ }
1982+
1983+ // Add task to engine without credentials
1984+ go taskEngine .AddTask (testTask )
1985+
1986+ // Verify task progresses but gets blocked waiting for execution credentials
1987+ VerifyContainerManifestPulledStateChange (t , taskEngine )
1988+ VerifyTaskManifestPulledStateChange (t , taskEngine )
1989+
1990+ // Verify the task has SSM resource that requires execution credentials
1991+ resources := testTask .GetResources ()
1992+ var ssmResource taskresource.TaskResource
1993+ for _ , resource := range resources {
1994+ if resource .GetName () == "ssmsecret" {
1995+ ssmResource = resource
1996+ break
1997+ }
1998+ }
1999+ require .NotNil (t , ssmResource , "SSM resource should exist" )
2000+ require .True (t , ssmResource .RequiresExecutionRoleCredentials (), "SSM resource should require execution credentials" )
2001+
2002+ // Mock credentials arrival from ACS
2003+ credentialsManager .SetTaskCredentials (mockCreds )
2004+
2005+ // Mimic ACS payload responder: after setting credentials, call AddTask to trigger task re-evaluation
2006+ // This is how blocked tasks get unblocked when credentials arrive from ACS
2007+ go taskEngine .AddTask (testTask )
2008+
2009+ // Verify task progresses through expected states after credentials are available
2010+ VerifyContainerRunningStateChange (t , taskEngine )
2011+ VerifyTaskRunningStateChange (t , taskEngine )
2012+
2013+ // Stop the task
2014+ taskUpdate := CreateTestTask ("testTaskResourceDependencyOnRestart" )
2015+ taskUpdate .Arn = taskArn
2016+ taskUpdate .SetDesiredStatus (apitaskstatus .TaskStopped )
2017+ go taskEngine .AddTask (taskUpdate )
2018+
2019+ VerifyContainerStoppedStateChange (t , taskEngine )
2020+ VerifyTaskStoppedStateChange (t , taskEngine )
2021+ }
0 commit comments