diff --git a/ecs-cli/modules/cli/cluster/cluster_app.go b/ecs-cli/modules/cli/cluster/cluster_app.go index d2f000ed3..7c7d0eb82 100644 --- a/ecs-cli/modules/cli/cluster/cluster_app.go +++ b/ecs-cli/modules/cli/cluster/cluster_app.go @@ -17,7 +17,6 @@ import ( "bufio" "fmt" "os" - "regexp" "strconv" "strings" @@ -72,10 +71,6 @@ const ( ParameterKeySpotPrice = "SpotPrice" ) -const ( - defaultARM64InstanceType = "a1.medium" -) - var flagNamesToStackParameterKeys map[string]string var requiredParameters []string = []string{ParameterKeyCluster} @@ -317,20 +312,13 @@ func createCluster(context *cli.Context, awsClients *AWSClients, commandConfig * } if launchType == config.LaunchTypeEC2 { - architecture, err := determineArchitecture(cfnParams) - if err != nil { - return err - } - // Check if image id was supplied, else populate _, err = cfnParams.GetParameter(ParameterKeyAmiId) if err == cloudformation.ParameterNotFoundError { - amiMetadata, err := metadataClient.GetRecommendedECSLinuxAMI(architecture) + err := populateAMIID(cfnParams, metadataClient) if err != nil { return err } - logrus.Infof("Using recommended %s AMI with ECS Agent %s and %s", amiMetadata.OsName, amiMetadata.AgentVersion, amiMetadata.RuntimeVersion) - cfnParams.Add(ParameterKeyAmiId, amiMetadata.ImageID) } else if err != nil { return err } @@ -393,26 +381,33 @@ func canEnableContainerInstanceTagging(client ecsclient.ECSClient) (bool, error) return false, nil } -func determineArchitecture(cfnParams *cloudformation.CfnStackParams) (string, error) { - architecture := amimetadata.ArchitectureTypeX86 +func retrieveInstanceType(cfnParams *cloudformation.CfnStackParams) (string, error) { + param, err := cfnParams.GetParameter(ParameterKeyInstanceType) - // a1 instances get the Arm based ECS AMI - instanceTypeParam, err := cfnParams.GetParameter(ParameterKeyInstanceType) if err == cloudformation.ParameterNotFoundError { - logrus.Infof("Defaulting instance type to t2.micro") - } else if err != nil { + logrus.Infof("Defaulting instance type to %s", cloudformation.DefaultECSInstanceType) + return cloudformation.DefaultECSInstanceType, nil + } + if err != nil { return "", err - } else { - instanceType := aws.StringValue(instanceTypeParam.ParameterValue) - // This regex matches all current a1 instances, and should work for any future additions as well - r := regexp.MustCompile("a1\\.(medium|\\d*x?large)") - if r.MatchString(instanceType) { - logrus.Infof("Using Arm ecs-optimized AMI because instance type was %s", instanceType) - architecture = amimetadata.ArchitectureTypeARM64 - } + } + return aws.StringValue(param.ParameterValue), nil +} + +func populateAMIID(cfnParams *cloudformation.CfnStackParams, client amimetadata.Client) error { + instanceType, err := retrieveInstanceType(cfnParams) + if err != nil { + return err } - return architecture, nil + amiMetadata, err := client.GetRecommendedECSLinuxAMI(instanceType) + if err != nil { + return err + } + logrus.Infof("Using recommended %s AMI with ECS Agent %s and %s", + amiMetadata.OsName, amiMetadata.AgentVersion, amiMetadata.RuntimeVersion) + cfnParams.Add(ParameterKeyAmiId, amiMetadata.ImageID) + return nil } // unfortunately go SDK lacks a unified Tag type diff --git a/ecs-cli/modules/cli/cluster/cluster_app_test.go b/ecs-cli/modules/cli/cluster/cluster_app_test.go index 183ef9934..0ab473fb3 100644 --- a/ecs-cli/modules/cli/cluster/cluster_app_test.go +++ b/ecs-cli/modules/cli/cluster/cluster_app_test.go @@ -144,7 +144,7 @@ func TestClusterUpWithForce(t *testing.T) { ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( @@ -179,7 +179,7 @@ func TestClusterUpWithoutPublicIP(t *testing.T) { ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( @@ -232,7 +232,7 @@ func TestClusterUpWithUserData(t *testing.T) { ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( @@ -281,7 +281,7 @@ func TestClusterUpWithSpotPrice(t *testing.T) { ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( @@ -976,7 +976,7 @@ func TestClusterUpARM64(t *testing.T) { ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("arm64").Return(amiMetadata(armAMIID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("a1.medium").Return(amiMetadata(armAMIID), nil), ) gomock.InOrder( @@ -1050,7 +1050,7 @@ func TestClusterUpWithTags(t *testing.T) { }), ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( mockCloudformation.EXPECT().ValidateStackExists(stackName).Return(errors.New("error")), @@ -1131,7 +1131,7 @@ func TestClusterUpWithTagsContainerInstanceTaggingEnabled(t *testing.T) { }), ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( mockCloudformation.EXPECT().ValidateStackExists(stackName).Return(errors.New("error")), @@ -1165,33 +1165,6 @@ func TestClusterUpWithTagsContainerInstanceTaggingEnabled(t *testing.T) { assert.Equal(t, userdataMock.tags, expectedECSTags, "Expected tags to match") } -func TestDetermineArchitecture(t *testing.T) { - var testCases = []struct { - in string - out string - }{ - {"a1.medium", "arm64"}, - {"a1.large", "arm64"}, - {"a1.xlarge", "arm64"}, - {"a1.2xlarge", "arm64"}, - {"a1.4xlarge", "arm64"}, - {"t2.medium", "x86"}, - {"c5.large", "x86"}, - {"i3.metal", "x86"}, - {"t3.micro", "x86"}, - } - - for _, tt := range testCases { - t.Run(tt.in, func(t *testing.T) { - cfnParams := cloudformation.NewCfnStackParams(requiredParameters) - cfnParams.Add(ParameterKeyInstanceType, tt.in) - arch, err := determineArchitecture(cfnParams) - assert.NoError(t, err, "Unexpected error determining architecture") - assert.Equal(t, tt.out, arch, "Expected architecture to match") - }) - } -} - /////////////////// // Cluster Down // ////////////////// @@ -1397,7 +1370,7 @@ func mocksForSuccessfulClusterUp(mockECS *mock_ecs.MockECSClient, mockCloudforma mockECS.EXPECT().CreateCluster(clusterName, gomock.Any()).Return(clusterName, nil), ) gomock.InOrder( - mockSSM.EXPECT().GetRecommendedECSLinuxAMI("x86").Return(amiMetadata(amiID), nil), + mockSSM.EXPECT().GetRecommendedECSLinuxAMI("t2.micro").Return(amiMetadata(amiID), nil), ) gomock.InOrder( mockCloudformation.EXPECT().ValidateStackExists(stackName).Return(errors.New("error")), diff --git a/ecs-cli/modules/clients/aws/amimetadata/client.go b/ecs-cli/modules/clients/aws/amimetadata/client.go index e276333fe..92f3693bc 100644 --- a/ecs-cli/modules/clients/aws/amimetadata/client.go +++ b/ecs-cli/modules/clients/aws/amimetadata/client.go @@ -11,11 +11,11 @@ // express or implied. See the License for the specific language governing // permissions and limitations under the License. +// Package amimetadata provides AMI metadata given an instance type. package amimetadata import ( "encoding/json" - "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients" "github.com/aws/amazon-ecs-cli/ecs-cli/modules/config" "github.com/aws/aws-sdk-go/aws" @@ -23,18 +23,24 @@ import ( "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/aws-sdk-go/service/ssm/ssmiface" "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "regexp" + "strings" ) +// SSM parameter names to retrieve ECS optimized AMI. +// See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/retrieve-ecs-optimized_AMI.html const ( - amazonLinux2X86RecommendedParameterName = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" - amazonLinux2ARM64RecommendedParameterName = "/aws/service/ecs/optimized-ami/amazon-linux-2/arm64/recommended" -) - -const ( - ArchitectureTypeARM64 = "arm64" - ArchitectureTypeX86 = "x86" + amazonLinux2X86RecommendedParameterName = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" + amazonLinux2ARM64RecommendedParameterName = "/aws/service/ecs/optimized-ami/amazon-linux-2/arm64/recommended" + amazonLinux2X86GPURecommendedParameterName = "/aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended" ) +// AMIMetadata is returned through ssm:GetParameters and can be used to retrieve the ImageId +// while launching instances. +// +// See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/retrieve-ecs-optimized_AMI.html +// See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-launchconfig.html#cfn-as-launchconfig-imageid type AMIMetadata struct { ImageID string `json:"image_id"` OsName string `json:"os"` @@ -47,13 +53,13 @@ type Client interface { GetRecommendedECSLinuxAMI(string) (*AMIMetadata, error) } -// ssmClient implements Client +// metadataClient implements Client. type metadataClient struct { client ssmiface.SSMAPI region string } -// NewSSMClient creates an instance of Client. +// NewMetadataClient creates an instance of Client. func NewMetadataClient(commandConfig *config.CommandConfig) Client { client := ssm.New(commandConfig.Session) client.Handlers.Build.PushBackNamed(clients.CustomUserAgentHandler()) @@ -63,20 +69,31 @@ func NewMetadataClient(commandConfig *config.CommandConfig) Client { } } -func (c *metadataClient) GetRecommendedECSLinuxAMI(architecture string) (*AMIMetadata, error) { - ssmParam := amazonLinux2X86RecommendedParameterName - if architecture == ArchitectureTypeARM64 { - ssmParam = amazonLinux2ARM64RecommendedParameterName +// GetRecommendedECSLinuxAMI returns the recommended Amazon ECS-Optimized AMI Metadata given the instance type. +func (c *metadataClient) GetRecommendedECSLinuxAMI(instanceType string) (*AMIMetadata, error) { + if isARM64Instance(instanceType) { + logrus.Infof("Using Arm ecs-optimized AMI because instance type was %s", instanceType) + return c.parameterValueFor(amazonLinux2ARM64RecommendedParameterName) + } + if isGPUInstance(instanceType) { + logrus.Infof("Using GPU ecs-optimized AMI because instance type was %s", instanceType) + return c.parameterValueFor(amazonLinux2X86GPURecommendedParameterName) } + return c.parameterValueFor(amazonLinux2X86RecommendedParameterName) +} +func (c *metadataClient) parameterValueFor(ssmParamName string) (*AMIMetadata, error) { response, err := c.client.GetParameter(&ssm.GetParameterInput{ - Name: aws.String(ssmParam), + Name: aws.String(ssmParamName), }) if err != nil { if aerr, ok := err.(awserr.Error); ok { if aerr.Code() == ssm.ErrCodeParameterNotFound { - // Added for arm AMIs which are only supported in some regions - return nil, errors.Wrapf(err, "Could not find Recommended Amazon Linux 2 AMI in %s with architecture %s; the AMI may not be supported in this region", c.region, architecture) + // Added for AMIs which are only supported in some regions + return nil, errors.Wrapf(err, + "Could not find Recommended Amazon Linux 2 AMI %s in %s; the AMI may not be supported in this region", + ssmParamName, + c.region) } } return nil, err @@ -85,3 +102,24 @@ func (c *metadataClient) GetRecommendedECSLinuxAMI(architecture string) (*AMIMet err = json.Unmarshal([]byte(aws.StringValue(response.Parameter.Value)), metadata) return metadata, err } + +func isARM64Instance(instanceType string) bool { + r := regexp.MustCompile("a1\\.(medium|\\d*x?large)") + if r.MatchString(instanceType) { + return true + } + return false +} + +func isGPUInstance(instanceType string) bool { + if strings.HasPrefix(instanceType, "p2.") { + return true + } + if strings.HasPrefix(instanceType, "p3.") { + return true + } + if strings.HasPrefix(instanceType, "p3dn.") { + return true + } + return false +} diff --git a/ecs-cli/modules/clients/aws/amimetadata/client_test.go b/ecs-cli/modules/clients/aws/amimetadata/client_test.go new file mode 100644 index 000000000..bafbd951c --- /dev/null +++ b/ecs-cli/modules/clients/aws/amimetadata/client_test.go @@ -0,0 +1,113 @@ +package amimetadata + +import ( + "fmt" + "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/amimetadata/mock/sdk" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/golang/mock/gomock" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "testing" +) + +type Configurer func(ssmClient *mock_ssmiface.MockSSMAPI) *mock_ssmiface.MockSSMAPI + +func TestMetadataClient_GetRecommendedECSLinuxAMI(t *testing.T) { + tests := []struct { + instanceType string + configureMock Configurer + expectedErr error + }{ + { + // validate that we use the ARM64 optimized AMI for Arm instances + "a1.medium", + func(ssmClient *mock_ssmiface.MockSSMAPI) *mock_ssmiface.MockSSMAPI { + ssmClient.EXPECT().GetParameter(gomock.Any()).Do(func(input *ssm.GetParameterInput) { + assert.Equal(t, amazonLinux2ARM64RecommendedParameterName, *input.Name) + }).Return(emptySSMParameterOutput(), nil) + return ssmClient + }, + nil, + }, + { + // validate that we use GPU optimized AMI for GPU instances + "p2.large", + func(ssmClient *mock_ssmiface.MockSSMAPI) *mock_ssmiface.MockSSMAPI { + ssmClient.EXPECT().GetParameter(gomock.Any()).Do(func(input *ssm.GetParameterInput) { + assert.Equal(t, amazonLinux2X86GPURecommendedParameterName, *input.Name) + }).Return(emptySSMParameterOutput(), nil) + return ssmClient + }, + nil, + }, + { + // validate that we use the generic AMI for other instances + "t2.micro", + func(ssmClient *mock_ssmiface.MockSSMAPI) *mock_ssmiface.MockSSMAPI { + ssmClient.EXPECT().GetParameter(gomock.Any()).Do(func(input *ssm.GetParameterInput) { + assert.Equal(t, amazonLinux2X86RecommendedParameterName, *input.Name) + }).Return(emptySSMParameterOutput(), nil) + return ssmClient + }, + nil, + }, + { + // validate that we throw an error if the AMI is not available in a region + "t2.micro", + func(ssmClient *mock_ssmiface.MockSSMAPI) *mock_ssmiface.MockSSMAPI { + ssmClient.EXPECT().GetParameter(gomock.Any()).Do(func(input *ssm.GetParameterInput) { + assert.Equal(t, amazonLinux2X86RecommendedParameterName, *input.Name) + }).Return(nil, awserr.New(ssm.ErrCodeParameterNotFound, "some error", nil)) + return ssmClient + }, + errors.New(fmt.Sprintf( + "Could not find Recommended Amazon Linux 2 AMI %s in %s; the AMI may not be supported in this region: ParameterNotFound: some error", + amazonLinux2X86RecommendedParameterName, + "us-east-1")), + }, + { + // validate that we throw unexpected errors + "t2.micro", + func(ssmClient *mock_ssmiface.MockSSMAPI) *mock_ssmiface.MockSSMAPI { + ssmClient.EXPECT().GetParameter(gomock.Any()).Do(func(input *ssm.GetParameterInput) { + assert.Equal(t, amazonLinux2X86RecommendedParameterName, *input.Name) + }).Return(nil, errors.New("unexpected error")) + return ssmClient + }, + errors.New("unexpected error"), + }, + } + + for _, test := range tests { + m := newMockSSMAPI(t) + test.configureMock(m) + + c := metadataClient{ + m, + "us-east-1", + } + _, actualErr := c.GetRecommendedECSLinuxAMI(test.instanceType) + + if test.expectedErr == nil { + assert.NoError(t, actualErr) + } else { + assert.EqualError(t, actualErr, test.expectedErr.Error()) + } + } +} + +func newMockSSMAPI(t *testing.T) *mock_ssmiface.MockSSMAPI { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + return mock_ssmiface.NewMockSSMAPI(ctrl) +} + +func emptySSMParameterOutput() *ssm.GetParameterOutput { + outputJson := "{}" + return &ssm.GetParameterOutput{ + Parameter: &ssm.Parameter{ + Value: &outputJson, + }, + } +} diff --git a/ecs-cli/modules/clients/aws/cloudformation/cluster_template.go b/ecs-cli/modules/clients/aws/cloudformation/cluster_template.go index 65920bb80..868d6ee10 100644 --- a/ecs-cli/modules/clients/aws/cloudformation/cluster_template.go +++ b/ecs-cli/modules/clients/aws/cloudformation/cluster_template.go @@ -33,7 +33,7 @@ func GetClusterTemplate(tags []*ecs.Tag, stackName string) (string, error) { return "", err } - return fmt.Sprintf(cluster_template, string(tagJSON), string(asgTagJSON)), nil + return fmt.Sprintf(clusterTemplate, string(tagJSON), string(asgTagJSON)), nil } // Autoscaling CFN tags have an additional field that determines if they are @@ -87,9 +87,10 @@ const ( Subnet2LogicalResourceId = "PubSubnetAz2" VPCLogicalResourceId = "Vpc" SecurityGroupLogicalResourceId = "EcsSecurityGroup" + DefaultECSInstanceType = "t2.micro" ) -var cluster_template = ` +var clusterTemplate = ` { "AWSTemplateFormatVersion": "2010-09-09", "Description": "AWS CloudFormation template to create resources required to run tasks on an ECS cluster.", @@ -109,8 +110,15 @@ var cluster_template = ` "EcsInstanceType": { "Type": "String", "Description": "ECS EC2 instance type", - "Default": "t2.micro", + "Default": "` + DefaultECSInstanceType + `", "AllowedValues": [ + "p2.xlarge", + "p2.8xlarge", + "p2.16xlarge", + "p3.2xlarge", + "p3.8xlarge", + "p3.16xlarge", + "p3dn.24xlarge", "a1.medium", "a1.large", "a1.xlarge", diff --git a/ecs-cli/modules/utils/compose/convert_task_definition_test.go b/ecs-cli/modules/utils/compose/convert_task_definition_test.go index 41f4244db..bf0148cf0 100644 --- a/ecs-cli/modules/utils/compose/convert_task_definition_test.go +++ b/ecs-cli/modules/utils/compose/convert_task_definition_test.go @@ -1807,6 +1807,41 @@ task_definition: } } +func TestConvertToTaskDefinitionWithECSParams_Gpu(t *testing.T) { + expectedGpuValue := "2" + content := `version: 1 +task_definition: + services: + web: + gpu: ` + expectedGpuValue + ecsParams, err := createTempECSParamsForTest(t, content) + + containerConfig := &adapter.ContainerConfig{ + Name: "web", + Image: "wordpress", + } + containerConfigs := []adapter.ContainerConfig{*containerConfig} + taskDefinition, err := convertToTaskDefinitionForTest(t, containerConfigs, "", "", ecsParams, nil) + + containerDefs := taskDefinition.ContainerDefinitions + web := findContainerByName("web", containerDefs) + + resourceType := ecs.ResourceTypeGpu + expectedResourceRequirements := []*ecs.ResourceRequirement{ + { + Type: &resourceType, + Value: &expectedGpuValue, + }, + } + + if assert.NoError(t, err) { + assert.ElementsMatch(t, + expectedResourceRequirements, + web.ResourceRequirements, + "Expected resourceRequirements to match") + } +} + /////////////////////// // helper functions // ////////////////////// @@ -1842,3 +1877,23 @@ func findContainerByName(name string, containerDefs []*ecs.ContainerDefinition) } return nil } + +func createTempECSParamsForTest(t *testing.T, content string) (*ECSParams, error) { + b := []byte(content) + + f, err := ioutil.TempFile("", "ecs-params") + assert.NoError(t, err, "Could not create ecs params tempfile") + + defer os.Remove(f.Name()) + + _, err = f.Write(b) + assert.NoError(t, err, "Could not write data to ecs params tempfile") + + err = f.Close() + assert.NoError(t, err, "Could not close tempfile") + + ecsParamsFileName := f.Name() + ecsParams, err := ReadECSParams(ecsParamsFileName) + assert.NoError(t, err, "Could not read ECS Params file") + return ecsParams, err +} diff --git a/ecs-cli/modules/utils/compose/ecs_params_reader.go b/ecs-cli/modules/utils/compose/ecs_params_reader.go index 76100908b..4ab1c287e 100644 --- a/ecs-cli/modules/utils/compose/ecs_params_reader.go +++ b/ecs-cli/modules/utils/compose/ecs_params_reader.go @@ -67,6 +67,7 @@ type ContainerDef struct { MemoryReservation libYaml.MemStringorInt `yaml:"mem_reservation"` HealthCheck *HealthCheck `yaml:"healthcheck"` Secrets []Secret `yaml:"secrets"` + GPU string `yaml:"gpu"` } type DockerVolume struct { diff --git a/ecs-cli/modules/utils/compose/reconcile_container_def.go b/ecs-cli/modules/utils/compose/reconcile_container_def.go index d6eab5969..674fab60b 100644 --- a/ecs-cli/modules/utils/compose/reconcile_container_def.go +++ b/ecs-cli/modules/utils/compose/reconcile_container_def.go @@ -15,7 +15,6 @@ package utils import ( "errors" - "github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/adapter" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" @@ -96,6 +95,7 @@ func reconcileContainerDef(inputCfg *adapter.ContainerConfig, ecsConDef *Contain memLimit := inputCfg.Memory memRes := inputCfg.MemoryReservation healthCheck := inputCfg.HealthCheck + var resourceRequirements []*ecs.ResourceRequirement if ecsConDef != nil { outputContDef.Essential = aws.Bool(ecsConDef.Essential) @@ -126,6 +126,15 @@ func reconcileContainerDef(inputCfg *adapter.ContainerConfig, ecsConDef *Contain if err != nil { return nil, err } + + if ecsConDef.GPU != "" { + resourceType := ecs.ResourceTypeGpu + resourceRequirement := ecs.ResourceRequirement{ + Type: &resourceType, + Value: &ecsConDef.GPU, + } + resourceRequirements = append(resourceRequirements, &resourceRequirement) + } } // At least one memory value is required to register a task definition. @@ -153,5 +162,9 @@ func reconcileContainerDef(inputCfg *adapter.ContainerConfig, ecsConDef *Contain outputContDef.SetHealthCheck(healthCheck) } + if len(resourceRequirements) > 0 { + outputContDef.SetResourceRequirements(resourceRequirements) + } + return outputContDef, nil }