From b28e0070b2305418ac3b629497dd68708124b9cb Mon Sep 17 00:00:00 2001 From: Ian Kerins Date: Thu, 21 Nov 2024 00:08:13 -0500 Subject: [PATCH] feat(ecs): support container version consistency Support the new ECS::TaskDefinition ContainerDefinition VersionConsistency property. This is a simple enabled/disabled flag. Additionally, set a default disabled value if the container image is a CDK asset, for the reasons described in the comments. --- ...efaultTestDeployAssert2196C660.assets.json | 19 ++ ...aultTestDeployAssert2196C660.template.json | 36 ++++ ...-container-version-consistency.assets.json | 19 ++ ...ontainer-version-consistency.template.json | 81 ++++++++ .../cdk.out | 1 + .../integ.json | 12 ++ .../manifest.json | 119 +++++++++++ .../tree.json | 196 ++++++++++++++++++ ...efinition-container-version-consistency.ts | 17 ++ packages/aws-cdk-lib/aws-ecs/README.md | 14 ++ .../aws-ecs/lib/container-definition.ts | 51 +++++ .../aws-ecs/lib/container-image.ts | 3 +- .../aws-ecs/lib/images/asset-image.ts | 1 + .../aws-ecs/test/container-definition.test.ts | 104 ++++++++++ 14 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets.json new file mode 100644 index 0000000000000..48b88f5f298f1 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.assets.json new file mode 100644 index 0000000000000..02056230d34c2 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "d4cdd7da3243deb657e43e94ffbd700f5c2b24fbca065b5f78377b1746e91654": { + "source": { + "path": "aws-ecs-task-definition-container-version-consistency.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d4cdd7da3243deb657e43e94ffbd700f5c2b24fbca065b5f78377b1746e91654.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.template.json new file mode 100644 index 0000000000000..684f80f8a194c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/aws-ecs-task-definition-container-version-consistency.template.json @@ -0,0 +1,81 @@ +{ + "Resources": { + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "Name": "Container", + "VersionConsistency": "disabled" + } + ], + "Cpu": "256", + "Family": "awsecstaskdefinitioncontainerversionconsistencyTaskDefF7D6E447", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/cdk.out new file mode 100644 index 0000000000000..c6e612584e352 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"38.0.1"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/integ.json new file mode 100644 index 0000000000000..b23f7f4ee3fb9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "38.0.1", + "testCases": { + "TaskDefinitionContainerRestartPolicy/DefaultTest": { + "stacks": [ + "aws-ecs-task-definition-container-version-consistency" + ], + "assertionStack": "TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert", + "assertionStackName": "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/manifest.json new file mode 100644 index 0000000000000..b3976e0fbc19e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/manifest.json @@ -0,0 +1,119 @@ +{ + "version": "38.0.1", + "artifacts": { + "aws-ecs-task-definition-container-version-consistency.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-ecs-task-definition-container-version-consistency.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-ecs-task-definition-container-version-consistency": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-ecs-task-definition-container-version-consistency.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d4cdd7da3243deb657e43e94ffbd700f5c2b24fbca065b5f78377b1746e91654.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-ecs-task-definition-container-version-consistency.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-ecs-task-definition-container-version-consistency.assets" + ], + "metadata": { + "/aws-ecs-task-definition-container-version-consistency/TaskDef/TaskRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDefTaskRole1EDB4A67" + } + ], + "/aws-ecs-task-definition-container-version-consistency/TaskDef/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TaskDef54694570" + } + ], + "/aws-ecs-task-definition-container-version-consistency/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecs-task-definition-container-version-consistency/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecs-task-definition-container-version-consistency" + }, + "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "TaskDefinitionContainerRestartPolicyDefaultTestDeployAssert2196C660.assets" + ], + "metadata": { + "/TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/tree.json new file mode 100644 index 0000000000000..8d2eac56dae24 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.js.snapshot/tree.json @@ -0,0 +1,196 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-ecs-task-definition-container-version-consistency": { + "id": "aws-ecs-task-definition-container-version-consistency", + "path": "aws-ecs-task-definition-container-version-consistency", + "children": { + "TaskDef": { + "id": "TaskDef", + "path": "aws-ecs-task-definition-container-version-consistency/TaskDef", + "children": { + "TaskRole": { + "id": "TaskRole", + "path": "aws-ecs-task-definition-container-version-consistency/TaskDef/TaskRole", + "children": { + "ImportTaskRole": { + "id": "ImportTaskRole", + "path": "aws-ecs-task-definition-container-version-consistency/TaskDef/TaskRole/ImportTaskRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-version-consistency/TaskDef/TaskRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-ecs-task-definition-container-version-consistency/TaskDef/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECS::TaskDefinition", + "aws:cdk:cloudformation:props": { + "containerDefinitions": [ + { + "essential": true, + "image": "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest", + "name": "Container", + "versionConsistency": "disabled" + } + ], + "cpu": "256", + "family": "awsecstaskdefinitioncontainerversionconsistencyTaskDefF7D6E447", + "memory": "512", + "networkMode": "awsvpc", + "requiresCompatibilities": [ + "FARGATE" + ], + "taskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.CfnTaskDefinition", + "version": "0.0.0" + } + }, + "Container": { + "id": "Container", + "path": "aws-ecs-task-definition-container-version-consistency/TaskDef/Container", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.ContainerDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ecs.FargateTaskDefinition", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-ecs-task-definition-container-version-consistency/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-ecs-task-definition-container-version-consistency/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "TaskDefinitionContainerRestartPolicy": { + "id": "TaskDefinitionContainerRestartPolicy", + "path": "TaskDefinitionContainerRestartPolicy", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "TaskDefinitionContainerRestartPolicy/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "TaskDefinitionContainerRestartPolicy/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "TaskDefinitionContainerRestartPolicy/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.ts new file mode 100644 index 0000000000000..341d8ca132e76 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/base/integ.task-definition-container-version-consistency.ts @@ -0,0 +1,17 @@ +import * as cdk from 'aws-cdk-lib'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-task-definition-container-version-consistency'); + +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', {}); + +taskDefinition.addContainer('Container', { + image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'), + versionConsistency: ecs.VersionConsistency.DISABLED, +}); + +new IntegTest(app, 'TaskDefinitionContainerRestartPolicy', { + testCases: [stack], +}); diff --git a/packages/aws-cdk-lib/aws-ecs/README.md b/packages/aws-cdk-lib/aws-ecs/README.md index dce4c0c88c817..9c62944b98fbd 100644 --- a/packages/aws-cdk-lib/aws-ecs/README.md +++ b/packages/aws-cdk-lib/aws-ecs/README.md @@ -1877,6 +1877,20 @@ taskDefinition.addContainer('TheContainer', { }); ``` +## Disable service container image version consistency + +You can disable the +[container image "version consistency"](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html#deployment-container-image-stability) +feature of ECS service deployments on a per-container basis. + +```ts +const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); +taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('example-image'), + versionConsistency: ecs.VersionConsistency.DISABLED, +}); +``` + ## Specify a container ulimit You can specify a container `ulimits` by specifying them in the `ulimits` option while adding the container diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts index ba028210e0bae..253c2022b237a 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-definition.ts @@ -321,6 +321,19 @@ export interface ContainerDefinitionOptions { */ readonly user?: string; + /** + * Specifies whether Amazon ECS will resolve the container image tag provided + * in the container definition to an image digest. + * + * If you set the value for a container as disabled, Amazon ECS will + * not resolve the provided container image tag to a digest and will use the + * original image URI specified in the container definition for deployment. + * + * @default VersionConsistency.DISABLED if `image` is a CDK asset, VersionConsistency.ENABLED otherwise + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinition.html#cfn-ecs-taskdefinition-containerdefinition-versionconsistency + */ + readonly versionConsistency?: VersionConsistency; + /** * The working directory in which to run commands inside the container. * @@ -548,6 +561,8 @@ export class ContainerDefinition extends Construct { private _namedPorts: Map; + private versionConsistency?: VersionConsistency; + /** * Constructs a new instance of the ContainerDefinition class. */ @@ -569,6 +584,8 @@ export class ContainerDefinition extends Construct { this._namedPorts = new Map(); + this.versionConsistency = props.versionConsistency; + if (props.logging) { this.logDriverConfig = props.logging.bind(this, this); } @@ -876,6 +893,23 @@ export class ContainerDefinition extends Construct { return defaultPortMapping.containerPort; } + /** + * Allows disabling version consistency if the user did not specify a value. + * + * Intended for CDK asset images, as asset images are tagged based upon a hash + * of image inputs, meaning the image won't change if the tag didn't change, + * making version consistency for such containers a waste of time. Literally, + * as version consistency can only be achieved by slowing down deployments. + * + * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html#deployment-container-image-stability + * @internal + */ + public _defaultDisableVersionConsistency() { + if (!this.versionConsistency) { + this.versionConsistency = VersionConsistency.DISABLED; + } + } + /** * Render this container definition to a CloudFormation object * @@ -910,6 +944,7 @@ export class ContainerDefinition extends Construct { stopTimeout: this.props.stopTimeout && this.props.stopTimeout.toSeconds(), ulimits: cdk.Lazy.any({ produce: () => this.ulimits.map(renderUlimit) }, { omitEmptyArray: true }), user: this.props.user, + versionConsistency: this.versionConsistency, volumesFrom: cdk.Lazy.any({ produce: () => this.volumesFrom.map(renderVolumeFrom) }, { omitEmptyArray: true }), workingDirectory: this.props.workingDirectory, logConfiguration: this.logDriverConfig, @@ -1500,6 +1535,22 @@ function renderMountPoint(mp: MountPoint): CfnTaskDefinition.MountPointProperty }; } +/** + * State of the container version consistency feature. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinition.html#cfn-ecs-taskdefinition-containerdefinition-versionconsistency + */ +export enum VersionConsistency { + /** + * The version consistency feature is enabled for this container. + */ + ENABLED = 'enabled', + /** + * The version consistency feature is disabled for this container. + */ + DISABLED = 'disabled', +} + /** * The details on a data volume from another container in the same task definition. */ diff --git a/packages/aws-cdk-lib/aws-ecs/lib/container-image.ts b/packages/aws-cdk-lib/aws-ecs/lib/container-image.ts index 5c710df600727..5da58d47d740e 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/container-image.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/container-image.ts @@ -45,6 +45,7 @@ export abstract class ContainerImage { public static fromDockerImageAsset(asset: DockerImageAsset): ContainerImage { return { bind(_scope: Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { + containerDefinition._defaultDisableVersionConsistency?.(); asset.repository.grantPull(containerDefinition.taskDefinition.obtainExecutionRole()); return { imageName: asset.imageUri, @@ -100,4 +101,4 @@ export interface ContainerImageConfig { // These imports have to be at the end to prevent circular imports import { AssetImage, AssetImageProps } from './images/asset-image'; import { EcrImage } from './images/ecr'; -import { RepositoryImage, RepositoryImageProps } from './images/repository'; \ No newline at end of file +import { RepositoryImage, RepositoryImageProps } from './images/repository'; diff --git a/packages/aws-cdk-lib/aws-ecs/lib/images/asset-image.ts b/packages/aws-cdk-lib/aws-ecs/lib/images/asset-image.ts index 07eef9ca04773..150db07ab5aa2 100644 --- a/packages/aws-cdk-lib/aws-ecs/lib/images/asset-image.ts +++ b/packages/aws-cdk-lib/aws-ecs/lib/images/asset-image.ts @@ -23,6 +23,7 @@ export class AssetImage extends ContainerImage { } public bind(scope: Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { + containerDefinition._defaultDisableVersionConsistency?.(); const asset = new DockerImageAsset(scope, 'AssetImage', { directory: this.directory, ...this.props, diff --git a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts index 191f687089dc8..6a2a5604958c7 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/container-definition.test.ts @@ -2961,4 +2961,108 @@ describe('container definition', () => { }); }).toThrow(/The restartAttemptPeriod must be between 60 seconds and 1800 seconds, got 59 seconds/); }); + + test('can specify version consistency', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + new ecs.ContainerDefinition(stack, 'Container', { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + taskDefinition, + memoryLimitMiB: 2048, + versionConsistency: ecs.VersionConsistency.ENABLED, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: '/aws/aws-example-app', + Name: 'Container', + Memory: 2048, + VersionConsistency: 'enabled', + }, + ], + }); + }); + + test('version consistency will not be set if not specified', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + new ecs.ContainerDefinition(stack, 'Container', { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + taskDefinition, + memoryLimitMiB: 2048, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: '/aws/aws-example-app', + Name: 'Container', + Memory: 2048, + VersionConsistency: Match.absent(), + }, + ], + }); + }); + + test('version consistency can be default disabled if appropriate', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + const container = new ecs.ContainerDefinition(stack, 'Container', { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + taskDefinition, + memoryLimitMiB: 2048, + }); + container._defaultDisableVersionConsistency(); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: '/aws/aws-example-app', + Name: 'Container', + Memory: 2048, + VersionConsistency: 'disabled', + }, + ], + }); + }); + + test('version consistency default disable does not override props', () => { + // GIVEN + const stack = new cdk.Stack(); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + + // WHEN + const container = new ecs.ContainerDefinition(stack, 'Container', { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + taskDefinition, + memoryLimitMiB: 2048, + versionConsistency: ecs.VersionConsistency.ENABLED, + }); + container._defaultDisableVersionConsistency(); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: '/aws/aws-example-app', + Name: 'Container', + Memory: 2048, + VersionConsistency: 'enabled', + }, + ], + }); + }); });