diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 2889c4d94df3d..fe17ce6cb2317 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Names, Resource } from '@aws-cdk/core'; +import { IResource, Lazy, Names, PhysicalName, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ContainerDefinition, ContainerDefinitionOptions, PortMapping, Protocol } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; @@ -610,6 +610,8 @@ export class TaskDefinition extends TaskDefinitionBase { if (!this._executionRole) { this._executionRole = new iam.Role(this, 'ExecutionRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + // needed for cross-account access with TagParameterContainerImage + roleName: PhysicalName.GENERATE_IF_NEEDED, }); } return this._executionRole; diff --git a/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts b/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts index beaa9a23308c8..d782d39d874cb 100644 --- a/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/images/tag-parameter-container-image.test.ts @@ -1,4 +1,4 @@ -import { SynthUtils } from '@aws-cdk/assert-internal'; +import { expect, haveResourceLike, SynthUtils } from '@aws-cdk/assert-internal'; import * as ecr from '@aws-cdk/aws-ecr'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -37,5 +37,100 @@ nodeunitShim({ test.done(); }, + + 'can be used in a cross-account manner'(test: Test) { + // GIVEN + const app = new cdk.App(); + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { + env: { + account: 'pipeline-account', + region: 'us-west-1', + }, + }); + const repositoryName = 'my-ecr-repo'; + const repository = new ecr.Repository(pipelineStack, 'Repository', { + repositoryName: repositoryName, + }); + const tagParameterContainerImage = new ecs.TagParameterContainerImage(repository); + + const serviceStack = new cdk.Stack(app, 'ServiceStack', { + env: { + account: 'service-account', + region: 'us-west-1', + }, + }); + const fargateTaskDefinition = new ecs.FargateTaskDefinition(serviceStack, 'ServiceTaskDefinition'); + + // WHEN + fargateTaskDefinition.addContainer('Container', { + image: tagParameterContainerImage, + }); + + // THEN + expect(pipelineStack).to(haveResourceLike('AWS::ECR::Repository', { + RepositoryName: repositoryName, + RepositoryPolicyText: { + Statement: [{ + Action: [ + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::service-account:role/servicestackionexecutionrolee7e2d9a783a54eb795f4', + ]], + }, + }, + }], + }, + })); + expect(serviceStack).to(haveResourceLike('AWS::IAM::Role', { + RoleName: 'servicestackionexecutionrolee7e2d9a783a54eb795f4', + })); + expect(serviceStack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + { + Image: { + 'Fn::Join': ['', [ + { + 'Fn::Select': [4, { + 'Fn::Split': [':', { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + `:ecr:us-west-1:pipeline-account:repository/${repositoryName}`, + ]], + }], + }], + }, + '.dkr.ecr.', + { + 'Fn::Select': [3, { + 'Fn::Split': [':', { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + `:ecr:us-west-1:pipeline-account:repository/${repositoryName}`, + ]], + }], + }], + }, + '.', + { Ref: 'AWS::URLSuffix' }, + `/${repositoryName}:`, + { Ref: 'ServiceTaskDefinitionContainerImageTagParamCEC9D0BA' }, + ]], + }, + }, + ], + })); + + test.done(); + }, }, });