From cb2995a514bcee9233446d1139faef81a16f45cd Mon Sep 17 00:00:00 2001
From: Ian Kerins <git@isk.haus>
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            |   1 +
 .../aws-ecs/lib/images/asset-image.ts         |   1 +
 .../aws-ecs/test/container-definition.test.ts | 104 ++++++++++
 14 files changed, 671 insertions(+)
 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<String>",
+   "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<String>",
+   "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<string, PortMapping>;
 
+  private versionConsistency?: VersionConsistency;
+
   /**
    * Constructs a new instance of the ContainerDefinition class.
    */
@@ -569,6 +584,8 @@ export class ContainerDefinition extends Construct {
 
     this._namedPorts = new Map<string, PortMapping>();
 
+    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 79ecf2f707ff1..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,
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 bd1a14129d390..ff0211ea2233d 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',
+        },
+      ],
+    });
+  });
 });