diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts index f2748ad57d607..91a1a00e575dc 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts @@ -788,19 +788,28 @@ export = { }); // THEN - const parentParams = SynthUtils.toCloudFormation(parent).Parameters; - const nestedParams = SynthUtils.toCloudFormation(nested).Parameters; - test.ok(parentParams.AssetParametershashofsourceImageName1CFB7817); - test.ok(nestedParams.referencetomystackAssetParametershashofsourceImageName7D5F0882Ref); - - // verify parameter is passed to nested stack - expect(parent).to(haveResource('AWS::CloudFormation::Stack', { - Parameters: { - referencetomystackAssetParametershashofsourceImageName7D5F0882Ref: { - Ref: "AssetParametershashofsourceImageName1CFB7817" - } + const asm = app.synth(); + test.deepEqual(asm.getStackArtifact(parent.artifactId).assets, [ + { + repositoryName: 'aws-cdk/assets', + imageTag: 'hash-of-source', + id: 'hash-of-source', + packaging: 'container-image', + path: 'my-image', + sourceHash: 'hash-of-source', + buildArgs: { key: 'value', boom: 'bam' }, + target: 'buildTarget' + }, + { + path: 'mystacknestedstackFAE12FB5.nested.template.json', + id: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', + packaging: 'file', + sourceHash: 'fcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5', + s3BucketParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3Bucket67A749F8', + s3KeyParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5S3VersionKeyE1E6A8D4', + artifactHashParameter: 'AssetParametersfcdaee79eb79f37eca3a9b1cc0cc9ba150e4eea8c5d6d0c343cb6cd9dc68e2e5ArtifactHash0AEDBE8A' } - })); + ]); test.done(); }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json index 6f0652023ea13..561aeb8f22f9a 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json @@ -1,181 +1,5 @@ { "Resources": { - "MyImageAdoptRepository6CA902F6": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters90a0be42035e43667ec935f49efc23c6bd6c9ec566b92dae96a94f505f091f2bImageName2105B480" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters90a0be42035e43667ec935f49efc23c6bd6c9ec566b92dae96a94f505f091f2bImageName2105B480" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - }, "MyProjectRole9BBE5233": { "Type": "AWS::IAM::Role", "Properties": { @@ -221,13 +45,7 @@ { "Ref": "AWS::AccountId" }, - ":repository/", - { - "Fn::GetAtt": [ - "MyImageAdoptRepository6CA902F6", - "RepositoryName" - ] - } + ":repository/aws-cdk/assets" ] ] } @@ -328,34 +146,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters90a0be42035e43667ec935f49efc23c6bd6c9ec566b92dae96a94f505f091f2bImageName2105B480" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters90a0be42035e43667ec935f49efc23c6bd6c9ec566b92dae96a94f505f091f2bImageName2105B480" - } - ] - } - ] - } + "/aws-cdk/assets:ee9ebbb592f461ed4638ea9ea64fab9fd384fd2f890c4fef981f9938d82419f4" ] ] }, @@ -375,23 +166,5 @@ } } } - }, - "Parameters": { - "AssetParameters90a0be42035e43667ec935f49efc23c6bd6c9ec566b92dae96a94f505f091f2bImageName2105B480": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"90a0be42035e43667ec935f49efc23c6bd6c9ec566b92dae96a94f505f091f2b\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/adopt-repository/.gitignore b/packages/@aws-cdk/aws-ecr-assets/lib/adopt-repository/.gitignore deleted file mode 100644 index d4aa116a26c73..0000000000000 --- a/packages/@aws-cdk/aws-ecr-assets/lib/adopt-repository/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!*.js diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/adopt-repository/handler.js b/packages/@aws-cdk/aws-ecr-assets/lib/adopt-repository/handler.js deleted file mode 100644 index 3d0cb009c8773..0000000000000 --- a/packages/@aws-cdk/aws-ecr-assets/lib/adopt-repository/handler.js +++ /dev/null @@ -1,131 +0,0 @@ -const AWS = require('aws-sdk'); -const ecr = new AWS.ECR(); - -exports.handler = async function(event, context, _callback, respond) { - respond = respond || respondCFN; - try { - console.log(JSON.stringify(event)); - - const markerStatement = { - Sid: event.StackId, - Effect: "Deny", - Action: "OwnedBy:CDKStack", - Principal: "*" - }; - - // The repository must already exist - async function getAdopter(name) { - try { - const policyResponse = await ecr.getRepositoryPolicy({ repositoryName: name }).promise(); - const policy = JSON.parse(policyResponse.policyText); - // Search the policy for an adopter marker - return (policy.Statement || []).find((x) => x.Action === markerStatement.Action) || {}; - } catch (e) { - if (e.code !== 'RepositoryPolicyNotFoundException') { throw e; } - return {}; - } - } - - let repo = event.ResourceProperties.RepositoryName; - if (!repo) { - throw new Error('Missing required property "RepositoryName"'); - } - const isRepoUri = repo.match(/^(\d+\.dkr\.ecr\.[^.]+\.[^/]+\/)(.+)$/i); - if (isRepoUri) { - repo = isRepoUri[2]; - } - - const adopter = await getAdopter(repo); - if (event.RequestType === 'Delete') { - if (!adopter.Sid) { - console.log(`Repository '${repo}' not found. Delete is no-op`); - } else { - if (adopter.Sid !== markerStatement.Sid) { - throw new Error(`This repository is already owned by another stack: ${adopter.Sid}`); - } - try { - console.log('Deleting', repo); - const ids = (await ecr.listImages({ repositoryName: repo }).promise()).imageIds; - await ecr.batchDeleteImage({ repositoryName: repo, imageIds: ids }).promise(); - await ecr.deleteRepository({ repositoryName: repo }).promise(); - } catch(e) { - if (e.code !== 'RepositoryNotFoundException') { throw e; } - } - } - } - - if (event.RequestType === 'Create' || event.RequestType === 'Update') { - if (adopter.Sid !== undefined && adopter.Sid !== markerStatement.Sid) { - throw new Error(`This repository is already owned by another stack: ${adopter.Sid}`); - } - console.log('Adopting', repo); - - const policy = event.ResourceProperties.PolicyDocument || { - Version: '2008-10-17', - Statement: [ ] - }; - - if (!policy.Version) { - policy.Version = '2008-10-17'; - } - - if (!policy.Statement) { - policy.Statement = [ ]; - } - - if (!Array.isArray(policy.Statement)) { - policy.Statement = [ policy.Statement ]; - } - - policy.Statement.push(markerStatement); - - console.log('policy document:', JSON.stringify(policy, undefined, 2)); - - await ecr.setRepositoryPolicy({ repositoryName: repo, policyText: JSON.stringify(policy) }).promise(); - } - - // we reflect back the repository name as a resource attribute - // this will allow taking an implicit dependency in this custom resource by - // referencing this attribute via { "Fn::GetAtt": [ ID, "RepositoryName" ] } - await respond("SUCCESS", "OK", repo, { - RepositoryName: repo - }); - } catch (e) { - console.log(e); - await respond("FAILED", e.message, context.logStreamName, {}); - } - - function respondCFN(responseStatus, reason, physId, data) { - const responseBody = JSON.stringify({ - Status: responseStatus, - Reason: reason, - PhysicalResourceId: physId, - StackId: event.StackId, - RequestId: event.RequestId, - LogicalResourceId: event.LogicalResourceId, - NoEcho: false, - Data: data - }); - - console.log('Responding', JSON.stringify(responseBody)); - - const parsedUrl = require('url').parse(event.ResponseURL); - const requestOptions = { - hostname: parsedUrl.hostname, - path: parsedUrl.path, - method: "PUT", - headers: { "content-type": "", "content-length": responseBody.length } - }; - - return new Promise((resolve, reject) => { - try { - const request = require('https').request(requestOptions, resolve); - request.on("error", reject); - request.write(responseBody); - request.end(); - } catch (e) { - reject(e); - } - }); - } -} diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts b/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts deleted file mode 100644 index 6c68e1df64b6f..0000000000000 --- a/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as cfn from '@aws-cdk/aws-cloudformation'; -import * as ecr from '@aws-cdk/aws-ecr'; -import * as iam from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; - -interface AdoptedRepositoryProps { - /** - * An ECR repository to adopt. Once adopted, the repository will - * practically become part of this stack, so it will be removed when - * the stack is deleted. - */ - repositoryName: string; -} - -/** - * An internal class used to adopt an ECR repository used for the locally built - * image into the stack. - * - * Since the repository is not created by the stack (but by the CDK toolkit), - * adopting will make the repository "owned" by the stack. It will be cleaned - * up when the stack gets deleted, to avoid leaving orphaned repositories on - * stack cleanup. - */ -export class AdoptedRepository extends ecr.RepositoryBase { - public readonly repositoryName: string; - public readonly repositoryArn: string; - - private readonly policyDocument = new iam.PolicyDocument(); - - constructor(scope: cdk.Construct, id: string, private readonly props: AdoptedRepositoryProps) { - super(scope, id); - - const fn = new lambda.SingletonFunction(this, 'Function', { - runtime: lambda.Runtime.NODEJS_10_X, - lambdaPurpose: 'AdoptEcrRepository', - handler: 'handler.handler', - code: lambda.Code.fromAsset(path.join(__dirname, 'adopt-repository')), - uuid: 'dbc60def-c595-44bc-aa5c-28c95d68f62c', - timeout: cdk.Duration.minutes(5) - }); - - fn.addToRolePolicy(new iam.PolicyStatement({ - resources: [ecr.Repository.arnForLocalRepository(props.repositoryName, this)], - actions: [ - 'ecr:GetRepositoryPolicy', - 'ecr:SetRepositoryPolicy', - 'ecr:DeleteRepository', - 'ecr:ListImages', - 'ecr:BatchDeleteImage' - ], - })); - - const adopter = new cfn.CustomResource(this, 'Resource', { - resourceType: 'Custom::ECRAdoptedRepository', - provider: cfn.CustomResourceProvider.lambda(fn), - properties: { - RepositoryName: props.repositoryName, - PolicyDocument: this.policyDocument - } - }); - if (fn.role) { - // Need to explicitly depend on the role's policies, so they are applied before we try to use them - adopter.node.addDependency(fn.role); - } - - // we use the Fn::GetAtt with the RepositoryName returned by the custom - // resource in order to implicitly create a dependency between consumers - // and the custom resource. - this.repositoryName = adopter.getAtt('RepositoryName').toString(); - - // this this repository is "local" to the stack (in the same region/account) - // we can render it's ARN from it's name. - this.repositoryArn = ecr.Repository.arnForLocalRepository(this.repositoryName, this); - } - - /** - * Export this repository from the stack - */ - public export() { - return this.props; - } - - /** - * Adds a statement to the repository resource policy. - * - * Contrary to normal imported repositories, which no-op here, we can - * use the custom resource to modify the ECR resource policy if needed. - */ - public addToResourcePolicy(statement: iam.PolicyStatement) { - this.policyDocument.addStatements(statement); - } -} diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index cc011d5b928be..3a0cd48f2315c 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -3,7 +3,6 @@ import * as ecr from '@aws-cdk/aws-ecr'; import { Construct, Stack, Token } from '@aws-cdk/core'; import * as fs from 'fs'; import * as path from 'path'; -import { AdoptedRepository } from './adopted-repository'; export interface DockerImageAssetProps extends assets.FingerprintOptions { /** @@ -18,7 +17,10 @@ export interface DockerImageAssetProps extends assets.FingerprintOptions { * from a Kubernetes Pod. Note, this is only the repository name, without the * registry and the tag parts. * - * @default - automatically derived from the asset's ID. + * @default - the default ECR repository for CDK assets + * @deprecated to control the location of docker image assets, please override + * `Stack.addDockerImageAsset`. this feature will be removed in future + * releases. */ readonly repositoryName?: string; @@ -93,6 +95,10 @@ export class DockerImageAsset extends Construct implements assets.IAsset { exclude = [...exclude, ...fs.readFileSync(ignore).toString().split('\n').filter(e => !!e)]; } + if (props.repositoryName) { + this.node.addWarning(`DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations`); + } + // include build context in "extra" so it will impact the hash const extraHash: { [field: string]: any } = { }; if (props.extraHash) { extraHash.user = props.extraHash; } @@ -101,6 +107,11 @@ export class DockerImageAsset extends Construct implements assets.IAsset { if (props.file) { extraHash.file = props.file; } if (props.repositoryName) { extraHash.repositoryName = props.repositoryName; } + // add "salt" to the hash in order to invalidate the image in the upgrade to + // 1.21.0 which removes the AdoptedRepository resource (and will cause the + // deletion of the ECR repository the app used). + extraHash.version = '1.21.0'; + const staging = new assets.Staging(this, 'Staging', { ...props, exclude, @@ -117,18 +128,12 @@ export class DockerImageAsset extends Construct implements assets.IAsset { directoryName: staging.stagedPath, dockerBuildArgs: props.buildArgs, dockerBuildTarget: props.target, - dockerFile: file, - repositoryName: props.repositoryName || `cdk/${this.node.uniqueId.replace(/[:/]/g, '-').toLowerCase()}`, - sourceHash: staging.sourceHash + dockerFile: props.file, + repositoryName: props.repositoryName, + sourceHash: staging.sourceHash, }); - // Require that repository adoption happens first, so we route the - // input ARN into the Custom Resource and then get the URI which we use to - // refer to the image FROM the Custom Resource. - // - // If adoption fails (because the repository might be twice-adopted), we - // haven't already started using the image. - this.repository = new AdoptedRepository(this, 'AdoptRepository', { repositoryName: location.repositoryName }); + this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName); this.imageUri = location.imageUri; } } diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.expected.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.expected.json index 01c97c292d139..1b289472b4cc1 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.expected.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.expected.json @@ -1,78 +1,18 @@ { "Resources": { - "DockerImageAdoptRepositoryA86481BC": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } + "MyUserDC45028B": { + "Type": "AWS::IAM::User" }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { + "MyUserDefaultPolicy7B897426": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ { "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" ], "Effect": "Allow", "Resource": { @@ -91,108 +31,26 @@ { "Ref": "AWS::AccountId" }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } + ":repository/aws-cdk/assets" ] ] } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ + "PolicyName": "MyUserDefaultPolicy7B897426", + "Users": [ { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" + "Ref": "MyUserDC45028B" } ] } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - } - }, - "Parameters": { - "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" } }, "Outputs": { @@ -212,37 +70,10 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } + "/aws-cdk/assets:baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540" ] ] } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts index e9123cd842d84..b5d9347de5cc6 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts @@ -1,3 +1,4 @@ +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as path from 'path'; import * as assets from '../lib'; @@ -9,6 +10,14 @@ const asset = new assets.DockerImageAsset(stack, 'DockerImage', { directory: path.join(__dirname, 'demo-image'), }); +const asset2 = new assets.DockerImageAsset(stack, 'DockerImage2', { + directory: path.join(__dirname, 'demo-image'), +}); + +const user = new iam.User(stack, 'MyUser'); +asset.repository.grantPull(user); +asset2.repository.grantPull(user); + new cdk.CfnOutput(stack, 'ImageUri', { value: asset.imageUri }); app.synth(); diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.expected.json b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.expected.json index 39ad3e28fc6ce..0a189b5e6d229 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.expected.json +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.expected.json @@ -17,7 +17,7 @@ }, "/", { - "Ref": "AssetParametersce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddcS3Bucket9C1F8CF3" + "Ref": "AssetParameters3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775S3Bucket6CE4FC69" }, "/", { @@ -27,7 +27,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddcS3VersionKeyD34A6061" + "Ref": "AssetParameters3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775S3VersionKey5FE1DA58" } ] } @@ -40,7 +40,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddcS3VersionKeyD34A6061" + "Ref": "AssetParameters3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775S3VersionKey5FE1DA58" } ] } @@ -48,49 +48,22 @@ } ] ] - }, - "Parameters": { - "referencetonestedstacksdockerAssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName6A10D714Ref": { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - }, - "referencetonestedstacksdockerAssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketDC016D33Ref": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "referencetonestedstacksdockerAssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey3A8B3E45Ref": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } } } } }, "Parameters": { - "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3": { + "AssetParameters3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775S3Bucket6CE4FC69": { "Type": "String", - "Description": "ECR repository name and tag for asset \"1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c\"" + "Description": "S3 bucket for asset \"3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775\"" }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { + "AssetParameters3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775S3VersionKey5FE1DA58": { "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" + "Description": "S3 key for asset version \"3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775\"" }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { + "AssetParameters3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775ArtifactHash5D96BEC9": { "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddcS3Bucket9C1F8CF3": { - "Type": "String", - "Description": "S3 bucket for asset \"ce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddc\"" - }, - "AssetParametersce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddcS3VersionKeyD34A6061": { - "Type": "String", - "Description": "S3 key for asset version \"ce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddc\"" - }, - "AssetParametersce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddcArtifactHash3F66D1AC": { - "Type": "String", - "Description": "Artifact hash for asset \"ce471cbd591a9b42ec50b0bb34562bf37cf4e3c95f4ba7d4cf79a4a6b7d11ddc\"" + "Description": "Artifact hash for asset \"3e9c40a6bf877a4b814d5ca4817c057400e4f8f661cd52c8951bf40f5763b775\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.ts b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.ts index 16a334a2e9e03..69484c7da852a 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/integ.nested-stacks-docker.ts @@ -1,4 +1,5 @@ import * as cfn from '@aws-cdk/aws-cloudformation'; +import * as iam from '@aws-cdk/aws-iam'; import { App, CfnOutput, Construct, Stack, StackProps } from '@aws-cdk/core'; import * as path from 'path'; import * as ecr_assets from '../lib'; @@ -11,6 +12,9 @@ class TheNestedStack extends cfn.NestedStack { directory: path.join(__dirname, 'demo-image') }); + const user = new iam.User(this, 'User'); + asset.repository.grantPull(user); + new CfnOutput(this, 'output', { value: asset.imageUri }); } } diff --git a/packages/@aws-cdk/aws-ecr-assets/test/test.adpot-repo.ts b/packages/@aws-cdk/aws-ecr-assets/test/test.adpot-repo.ts deleted file mode 100644 index 8ea9e093cd766..0000000000000 --- a/packages/@aws-cdk/aws-ecr-assets/test/test.adpot-repo.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { Test } from 'nodeunit'; -import * as path from 'path'; -import * as proxyquire from 'proxyquire'; - -let ecrMock: any; - -export = { - async 'exercise handler create with policy'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { - '@noCallThru': true, - "ECR": ECRWithEmptyPolicy, - } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - PolicyDocument: { - Version: '2008-10-01', - My: 'Document' - } - }, - RequestType: 'Create', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(JSON.parse(ecrMock.lastSetRepositoryPolicyRequest.policyText), { - My: "Document", - Version: '2008-10-01', - Statement: [ - { Sid: "StackId", Effect: "Deny", Action: "OwnedBy:CDKStack", Principal: "*" } - ] - }); - - test.deepEqual(output, { - responseStatus: 'SUCCESS', - reason: 'OK', - physId: 'RepositoryName', - data: { - RepositoryName: 'RepositoryName' - } - }); - - test.done(); - }, - - async 'exercise handler create with policy with object statement'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { - '@noCallThru': true, - "ECR": ECRWithEmptyPolicy, - } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - PolicyDocument: { - Statement: { Action: 'boom' } - } - }, - RequestType: 'Create', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(JSON.parse(ecrMock.lastSetRepositoryPolicyRequest.policyText), { - Version: '2008-10-17', - Statement: [ - { Action: 'boom' }, - { Sid: "StackId", Effect: "Deny", Action: "OwnedBy:CDKStack", Principal: "*" } - ] - }); - - test.deepEqual(output, { - responseStatus: 'SUCCESS', - reason: 'OK', - physId: 'RepositoryName', - data: { - RepositoryName: 'RepositoryName' - } - }); - - test.done(); - }, - - async 'exercise handler create with policy with array statement'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { - '@noCallThru': true, - "ECR": ECRWithEmptyPolicy, - } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - PolicyDocument: { - Statement: [ { Action: 'boom' }, { Resource: "foo" } ] - } - }, - RequestType: 'Create', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(JSON.parse(ecrMock.lastSetRepositoryPolicyRequest.policyText), { - Version: '2008-10-17', - Statement: [ - { Action: "boom" }, - { Resource: "foo" }, - { Sid: "StackId", Effect: "Deny", Action: "OwnedBy:CDKStack", Principal: "*" } - ] - }); - - test.deepEqual(output, { - responseStatus: 'SUCCESS', - reason: 'OK', - physId: 'RepositoryName', - data: { - RepositoryName: 'RepositoryName' - } - }); - - test.done(); - }, - - async 'exercise handler create'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { - '@noCallThru': true, - "ECR": ECRWithEmptyPolicy, - } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - }, - RequestType: 'Create', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(output, { - responseStatus: 'SUCCESS', - reason: 'OK', - physId: 'RepositoryName', - data: { - RepositoryName: 'RepositoryName' - } - }); - - test.done(); - }, - - async 'exercise handler delete'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { '@noCallThru': true, "ECR": ECRWithOwningPolicy } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - }, - RequestType: 'Delete', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(output, { - responseStatus: 'SUCCESS', - reason: 'OK', - physId: 'RepositoryName', - data: { - RepositoryName: 'RepositoryName' - } - }); - - test.done(); - }, - - async 'exercise "delete" handler when repository doesnt exist'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { '@noCallThru': true, "ECR": ECRWithRepositoryNotFound } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - }, - RequestType: 'Delete', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(output, { - responseStatus: 'SUCCESS', - reason: 'OK', - physId: 'RepositoryName', - data: { - RepositoryName: 'RepositoryName' - } - }); - - test.done(); - }, - - async 'exercise "create" handler when repository doesnt exist'(test: Test) { - const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { - 'aws-sdk': { '@noCallThru': true, "ECR": ECRWithRepositoryNotFound } - }); - - let output; - async function response(responseStatus: string, reason: string, physId: string, data: any) { - output = { responseStatus, reason, physId, data }; - } - - await handler.handler({ - StackId: 'StackId', - ResourceProperties: { - RepositoryName: 'RepositoryName', - }, - RequestType: 'Create', - ResponseURL: 'https://localhost/test' - }, { - logStreamName: 'xyz', - }, undefined, response); - - test.deepEqual(output, { - responseStatus: 'FAILED', - reason: 'Simulated RepositoryPolicyNotFoundException', - physId: 'xyz', - data: { } - }); - - test.done(); - }, -}; - -function ECRWithEmptyPolicy() { - ecrMock = new ECR({ asdf: 'asdf' }); - return ecrMock; -} - -function ECRWithOwningPolicy() { - return new ECR({ - Statement: [ - { - Sid: 'StackId', - Effect: "Deny", - Action: "OwnedBy:CDKStack", - Principal: "*" - } - ] - }); -} - -function ECRWithRepositoryNotFound() { - const ecr = new ECR({}); - ecr.shouldThrowNotFound = true; - return ecr; -} - -class ECR { - public lastSetRepositoryPolicyRequest: any; - public shouldThrowNotFound = false; - - public constructor(private policy: any) { - } - - public getRepositoryPolicy() { - const self = this; - return { - async promise() { - if (self.shouldThrowNotFound) { return self.throwNotFound(); } - return { policyText: JSON.stringify(self.policy) }; - } - }; - } - - public setRepositoryPolicy(req: any) { - const self = this; - this.lastSetRepositoryPolicyRequest = req; - - return { - async promise() { - if (self.shouldThrowNotFound) { return self.throwNotFound(); } - return; - } - }; - } - - public listImages() { - return { - async promise() { - return { imageIds: [] }; - } - }; - } - - public batchDeleteImage() { - const self = this; - return { - async promise() { - if (self.shouldThrowNotFound) { return self.throwNotFound(); } - return {}; - } - }; - } - - public deleteRepository() { - const self = this; - return { - async promise() { - if (self.shouldThrowNotFound) { return self.throwNotFound(); } - return {}; - } - }; - } - - private throwNotFound() { - const err = new Error('Simulated RepositoryPolicyNotFoundException'); - (err as any).code = 'RepositoryPolicyNotFoundException'; - throw err; - } -} diff --git a/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts index 5da0630ee6705..d704c3f00f5c7 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts @@ -1,6 +1,6 @@ -import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; +import { expect, haveResource } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; -import { App, Construct, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { App, Lazy, Stack } from '@aws-cdk/core'; import { ASSET_METADATA } from '@aws-cdk/cx-api'; import * as fs from 'fs'; import { Test } from 'nodeunit'; @@ -12,7 +12,8 @@ import { DockerImageAsset } from '../lib'; export = { 'test instantiating Asset Image'(test: Test) { // GIVEN - const stack = new Stack(); + const app = new App(); + const stack = new Stack(app, 'test-stack'); // WHEN new DockerImageAsset(stack, 'Image', { @@ -20,33 +21,19 @@ export = { }); // THEN - const template = SynthUtils.synthesize(stack).template; - test.deepEqual(template.Parameters.AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3, { - Type: 'String', - Description: 'ECR repository name and tag for asset "1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c"' - }); - - test.done(); - }, - - 'repository name is derived from node unique id'(test: Test) { - // GIVEN - const stack = new Stack(); - class CoolConstruct extends Resource { - constructor(scope: Construct, id: string) { - super(scope, id); + const asm = app.synth(); + const artifact = asm.getStackArtifact(stack.artifactId); + test.deepEqual(artifact.template, {}, 'template is empty'); + test.deepEqual(artifact.assets, [ + { + repositoryName: 'aws-cdk/assets', + imageTag: 'baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540', + id: 'baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540', + packaging: 'container-image', + path: 'asset.baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540', + sourceHash: 'baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540' } - } - const coolConstruct = new CoolConstruct(stack, 'CoolConstruct'); - - // WHEN - new DockerImageAsset(coolConstruct, 'Image', { - directory: path.join(__dirname, 'demo-image'), - }); - - // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA); - test.deepEqual(assetMetadata && assetMetadata.data.repositoryName, 'cdk/coolconstructimage78ab38fc'); + ]); test.done(); }, @@ -100,7 +87,7 @@ export = { // THEN const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA); - test.deepEqual(assetMetadata && assetMetadata.data.file, path.join(directoryPath, 'Dockerfile.Custom')); + test.deepEqual(assetMetadata && assetMetadata.data.file, 'Dockerfile.Custom'); test.done(); }, @@ -131,13 +118,18 @@ export = { "", [ "arn:", - { "Ref": "AWS::Partition" }, + { + "Ref": "AWS::Partition" + }, ":ecr:", - { "Ref": "AWS::Region" }, + { + "Ref": "AWS::Region" + }, ":", - { "Ref": "AWS::AccountId" }, - ":repository/", - { "Fn::GetAtt": ["ImageAdoptRepositoryE1E84E35", "RepositoryName"] } + { + "Ref": "AWS::AccountId" + }, + ":repository/aws-cdk/assets" ] ] } @@ -161,51 +153,6 @@ export = { test.done(); }, - 'asset.repository.addToResourcePolicy can be used to modify the ECR resource policy via the adoption custom resource'(test: Test) { - // GIVEN - const stack = new Stack(); - const asset = new DockerImageAsset(stack, 'Image', { - directory: path.join(__dirname, 'demo-image') - }); - - // WHEN - asset.repository.addToResourcePolicy(new iam.PolicyStatement({ - actions: ['BAM:BOOM'], - principals: [new iam.ServicePrincipal('test.service')] - })); - - // THEN - expect(stack).to(haveResource('Custom::ECRAdoptedRepository', { - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "BAM:BOOM", - "Effect": "Allow", - "Principal": { - "Service": "test.service" - } - } - ], - "Version": "2012-10-17" - } - })); - - test.done(); - }, - 'fails if the directory does not exist'(test: Test) { // GIVEN const stack = new Stack(); @@ -350,13 +297,13 @@ export = { const asset6 = new DockerImageAsset(stack, 'Asset6', { directory, extraHash: 'random-extra' }); const asset7 = new DockerImageAsset(stack, 'Asset7', { directory, repositoryName: 'foo' }); - test.deepEqual(asset1.sourceHash, 'b84a5001da0f5714e484134e2471213d7e987e22ee6219469029f1779370cc2a'); - test.deepEqual(asset2.sourceHash, 'c6568a7946e92a408c60278f70834b901638e71237d470ed1e5e6d707c55c0c9'); - test.deepEqual(asset3.sourceHash, '963a5329c170c54bc667fddab8d9cc4cec4bffb65ce3a1f323bb5fbc1d268732'); - test.deepEqual(asset4.sourceHash, '0e3eb87273509e0f0d45d67d40fa3080566aa22abd7f976e1ce7ea60a8ccd0a8'); - test.deepEqual(asset5.sourceHash, 'de0fd4b2bff8c9f180351fd59c6f2e9409fa21366453e1e0b75fedbd93dda1fc'); - test.deepEqual(asset6.sourceHash, '00879adf80f97271bf6d7e214b4fac8a043fc6e2661912cbf4d898ccb317d46c'); - test.deepEqual(asset7.sourceHash, 'b8abda995e51bd1a47b2705fa40021f3e9619a334bddb96866e808b09303eff7'); + test.deepEqual(asset1.sourceHash, '31959f03fcdf1bddec1420d315dddedfccf559e87e95c85854a01f2cac103fc8'); + test.deepEqual(asset2.sourceHash, 'a42cd51ab2bc5e2a4399c4bc41f7df761ff19877b54bce52d69c6e8d628f16fd'); + test.deepEqual(asset3.sourceHash, '9efbc91d5c2f43782e49b27e784caad32d8619a0cecf806a6e55cf70f1cfbc22'); + test.deepEqual(asset4.sourceHash, '2907224e59cb720ba5810a624f1c1267f547377e8a23c50d72286dd81dc8435e'); + test.deepEqual(asset5.sourceHash, 'd718928111c650564240141b156188ebdbb93d3c5f1448dd1afe823ae7f742a5'); + test.deepEqual(asset6.sourceHash, 'fe3ef82c91b6321ac17bc3a14c75845fa1ddbafe550e0a04d5cf680015903a2d'); + test.deepEqual(asset7.sourceHash, '80f586ed82faacec8a285dd99c7ee52e06525fee1721bc80bc846d1a8266fe36'); test.done(); } }; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json index 7cf939ff66ebb..63e74d7cadb80 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json @@ -491,34 +491,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } + "/aws-cdk/assets:baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540" ] ] }, @@ -564,36 +537,6 @@ } } }, - "FargateServiceTaskDefwebAssetImageAdoptRepositoryCDAFD419": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "FargateServiceTaskDefwebLogGroup71FAF541": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", @@ -644,13 +587,7 @@ { "Ref": "AWS::AccountId" }, - ":repository/", - { - "Fn::GetAtt": [ - "FargateServiceTaskDefwebAssetImageAdoptRepositoryCDAFD419", - "RepositoryName" - ] - } + ":repository/aws-cdk/assets" ] ] } @@ -773,152 +710,6 @@ }, "ToPort": 8000 } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] } }, "Outputs": { @@ -954,23 +745,5 @@ ] } } - }, - "Parameters": { - "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.scheduled-fargate-task.lit.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.scheduled-fargate-task.lit.expected.json index d7456b3e52042..8b53d3042133f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.scheduled-fargate-task.lit.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.scheduled-fargate-task.lit.expected.json @@ -293,34 +293,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } + "/aws-cdk/assets:baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540" ] ] }, @@ -360,36 +333,6 @@ } } }, - "ScheduledFargateTaskScheduledTaskDefScheduledContainerAssetImageAdoptRepository49B45957": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "ScheduledFargateTaskScheduledTaskDefScheduledContainerLogGroup4134B16C": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", @@ -440,13 +383,7 @@ { "Ref": "AWS::AccountId" }, - ":repository/", - { - "Fn::GetAtt": [ - "ScheduledFargateTaskScheduledTaskDefScheduledContainerAssetImageAdoptRepository49B45957", - "RepositoryName" - ] - } + ":repository/aws-cdk/assets" ] ] } @@ -565,170 +502,6 @@ } ] } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - } - }, - "Parameters": { - "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index a7328ef2a3545..95399d83229b7 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -542,34 +542,7 @@ export = { { Ref: "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - Ref: "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - Ref: "AssetParameters1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439cImageName1ADCADB3" - } - ] - } - ] - } + "/aws-cdk/assets:baa2d6eb2a17c75424df631c8c70ff39f2d5f3bee8b9e1a109ee24ca17300540" ] ] }, diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json index 88cdef93cf952..00d6fb582078a 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json @@ -678,34 +678,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } + "/aws-cdk/assets:1f37178655533422f6654c973b99eadec99a723c7181c912e4fb0976187c687c" ] ] }, @@ -744,36 +717,6 @@ } } }, - "TaskDefTheContainerAssetImageAdoptRepository997406C3": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "TaskDefTheContainerLogGroupD94C8EF5": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", @@ -824,13 +767,7 @@ { "Ref": "AWS::AccountId" }, - ":repository/", - { - "Fn::GetAtt": [ - "TaskDefTheContainerAssetImageAdoptRepository997406C3", - "RepositoryName" - ] - } + ":repository/aws-cdk/assets" ] ] } @@ -924,152 +861,6 @@ ] } }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - }, "Rule4C995B7F": { "Type": "AWS::Events::Rule", "Properties": { @@ -1106,22 +897,6 @@ "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id" - }, - "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.expected.json b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.expected.json index 6cffa13533c6c..4039dbafddaf1 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.expected.json @@ -237,34 +237,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } + "/aws-cdk/assets:1f37178655533422f6654c973b99eadec99a723c7181c912e4fb0976187c687c" ] ] }, @@ -304,36 +277,6 @@ } } }, - "TaskDefTheContainerAssetImageAdoptRepository997406C3": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "TaskDefTheContainerLogGroupD94C8EF5": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", @@ -384,13 +327,7 @@ { "Ref": "AWS::AccountId" }, - ":repository/", - { - "Fn::GetAtt": [ - "TaskDefTheContainerAssetImageAdoptRepository997406C3", - "RepositoryName" - ] - } + ":repository/aws-cdk/assets" ] ] } @@ -510,152 +447,6 @@ ] } }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - }, "Rule4C995B7F": { "Type": "AWS::Events::Rule", "Properties": { @@ -706,23 +497,5 @@ ] } } - }, - "Parameters": { - "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json index 879684a397616..0a57d22dc1fb6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -465,34 +465,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } + "/aws-cdk/assets:1f37178655533422f6654c973b99eadec99a723c7181c912e4fb0976187c687c" ] ] }, @@ -529,36 +502,6 @@ } } }, - "TaskDefTheContainerAssetImageAdoptRepository997406C3": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "TaskDefTheContainerLogGroupD94C8EF5": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", @@ -601,13 +544,7 @@ { "Ref": "AWS::Partition" }, - ":ecr:test-region:12345678:repository/", - { - "Fn::GetAtt": [ - "TaskDefTheContainerAssetImageAdoptRepository997406C3", - "RepositoryName" - ] - } + ":ecr:test-region:12345678:repository/aws-cdk/assets" ] ] } @@ -641,144 +578,6 @@ ] } }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:test-region:12345678:repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - }, "StateMachineRoleB840431D": { "Type": "AWS::IAM::Role", "Properties": { @@ -908,22 +707,6 @@ "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id" - }, - "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json index 8a4cde77c2fcd..c99f991489a69 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json @@ -34,34 +34,7 @@ { "Ref": "AWS::URLSuffix" }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - }, - "@sha256:", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } + "/aws-cdk/assets:1f37178655533422f6654c973b99eadec99a723c7181c912e4fb0976187c687c" ] ] }, @@ -100,36 +73,6 @@ } } }, - "TaskDefTheContainerAssetImageAdoptRepository997406C3": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ], - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, "TaskDefTheContainerLogGroupD94C8EF5": { "Type": "AWS::Logs::LogGroup", "UpdateReplacePolicy": "Retain", @@ -172,13 +115,7 @@ { "Ref": "AWS::Partition" }, - ":ecr:test-region:12345678:repository/", - { - "Fn::GetAtt": [ - "TaskDefTheContainerAssetImageAdoptRepository997406C3", - "RepositoryName" - ] - } + ":ecr:test-region:12345678:repository/aws-cdk/assets" ] ] } @@ -212,144 +149,6 @@ ] } }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:test-region:12345678:repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "@sha256:", - { - "Ref": "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs10.x", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - ] - }, "FargateTaskSecurityGroup0BBB27CB": { "Type": "AWS::EC2::SecurityGroup", "Properties": { @@ -495,23 +294,5 @@ "StateMachineRoleB840431D" ] } - }, - "Parameters": { - "AssetParameters849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3ImageName999B381A": { - "Type": "String", - "Description": "ECR repository name and tag for asset \"849429eca8deea448d1c953aea3403a3d05d598c09880305dde8e99010d05db3\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3BucketE797C7BB": { - "Type": "String", - "Description": "S3 bucket for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1S3VersionKey56C3F6D7": { - "Type": "String", - "Description": "S3 key for asset version \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - }, - "AssetParametersea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1ArtifactHash7E5AE478": { - "Type": "String", - "Description": "Artifact hash for asset \"ea7034d81c091be1158bcd85b4958dc86ec6672c345be27607d68fdfcf26b1c1\"" - } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/assets.ts b/packages/@aws-cdk/core/lib/assets.ts index ed2e59cd94d0f..a397a8aa6624a 100644 --- a/packages/@aws-cdk/core/lib/assets.ts +++ b/packages/@aws-cdk/core/lib/assets.ts @@ -72,6 +72,7 @@ export interface DockerImageAssetSource { * registry and the tag parts. * * @default - automatically derived from the asset's ID. + * @deprecated repository name should be specified at the environment-level and not at the image level */ readonly repositoryName?: string; } diff --git a/packages/@aws-cdk/core/lib/private/asset-parameters.ts b/packages/@aws-cdk/core/lib/private/asset-parameters.ts index cc74067bccc37..727491de55159 100644 --- a/packages/@aws-cdk/core/lib/private/asset-parameters.ts +++ b/packages/@aws-cdk/core/lib/private/asset-parameters.ts @@ -29,16 +29,3 @@ export class FileAssetParameters extends Construct { }); } } - -export class DockerImageAssetParameters extends Construct { - public readonly imageNameParameter: CfnParameter; - - constructor(scope: Construct, id: string) { - super(scope, id); - - this.imageNameParameter = new CfnParameter(this, 'ImageName', { - type: 'String', - description: `ECR repository name and tag for asset "${id}"`, - }); - } -} diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 1683063020163..30a0119cbebd8 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -6,7 +6,7 @@ import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation , F import { Construct, ConstructNode, IConstruct, ISynthesisSession } from './construct'; import { ContextProvider } from './context-provider'; import { Environment } from './environment'; -import { DockerImageAssetParameters, FileAssetParameters } from './private/asset-parameters'; +import { FileAssetParameters } from './private/asset-parameters'; import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './private/cloudformation-lang'; import { LogicalIDs } from './private/logical-id'; import { findTokens , resolve } from './private/resolve'; @@ -17,6 +17,21 @@ const MY_STACK_CACHE = Symbol.for('@aws-cdk/core.Stack.myStack'); const VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/; +/** + * The well-known name for the docker image asset ECR repository. All docker + * image assets will be pushed into this repository with an image tag based on + * the source hash. + */ +const ASSETS_ECR_REPOSITORY_NAME = "aws-cdk/assets"; + +/** + * This allows users to work around the fact that the ECR repository is + * (currently) not configurable by setting this context key to their desired + * repository name. The CLI will auto-create this ECR repository if it's not + * already created. + */ +const ASSETS_ECR_REPOSITORY_NAME_OVERRIDE_CONTEXT_KEY = "assets-ecr-repository-name"; + export interface StackProps { /** * A description of the stack. @@ -220,6 +235,12 @@ export class Stack extends Construct implements ITaggable { private _templateUrl?: string; private readonly _stackName: string; + /** + * The image ID of all the docker image assets that were already added to this + * stack (to avoid duplication). + */ + private readonly addedImageAssets = new Set(); + /** * Creates a new stack. * @@ -551,34 +572,33 @@ export class Stack extends Construct implements ITaggable { return this.nestedStackParent.addDockerImageAsset(asset); } - let params = this.assetParameters.node.tryFindChild(asset.sourceHash) as DockerImageAssetParameters; - if (!params) { - params = new DockerImageAssetParameters(this.assetParameters, asset.sourceHash); + // check if we have an override from context + const repositoryNameOverride = this.node.tryGetContext(ASSETS_ECR_REPOSITORY_NAME_OVERRIDE_CONTEXT_KEY); + const repositoryName = asset.repositoryName ?? repositoryNameOverride ?? ASSETS_ECR_REPOSITORY_NAME; + const imageTag = asset.sourceHash; + const assetId = asset.sourceHash; + // only add every image (identified by source hash) once for each stack that uses it. + if (!this.addedImageAssets.has(assetId)) { const metadata: cxapi.ContainerImageAssetMetadataEntry = { - id: asset.sourceHash, + repositoryName, + imageTag, + id: assetId, packaging: 'container-image', path: asset.directoryName, sourceHash: asset.sourceHash, - imageNameParameter: params.imageNameParameter.logicalId, - repositoryName: asset.repositoryName, buildArgs: asset.dockerBuildArgs, target: asset.dockerBuildTarget, file: asset.dockerFile, }; this.node.addMetadata(cxapi.ASSET_METADATA, metadata); + this.addedImageAssets.add(assetId); } - // Parse repository name and tag from the parameter (@sha256:) - // Example: cdk/cdkexampleimageb2d7f504@sha256:72c4f956379a43b5623d529ddd969f6826dde944d6221f445ff3e7add9875500 - const components = Fn.split('@sha256:', params.imageNameParameter.valueAsString); - const repositoryName = Fn.select(0, components).toString(); - const imageSha = Fn.select(1, components).toString(); - const imageUri = `${this.account}.dkr.ecr.${this.region}.${this.urlSuffix}/${repositoryName}@sha256:${imageSha}`; - return { - imageUri, repositoryName + imageUri: `${this.account}.dkr.ecr.${this.region}.${this.urlSuffix}/${repositoryName}:${imageTag}`, + repositoryName }; } diff --git a/packages/@aws-cdk/core/test/test.assets.ts b/packages/@aws-cdk/core/test/test.assets.ts index 4ad3d906147ec..f29f264a4837d 100644 --- a/packages/@aws-cdk/core/test/test.assets.ts +++ b/packages/@aws-cdk/core/test/test.assets.ts @@ -44,7 +44,7 @@ export = { }, - 'addDockerImageAsset correctly sets metadata and creates an ECR parameter'(test: Test) { + 'addDockerImageAsset correctly sets metadata'(test: Test) { // GIVEN const stack = new Stack(); @@ -62,16 +62,56 @@ export = { test.equal(assetMetadata && assetMetadata.data.path, 'directory-name'); test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); test.equal(assetMetadata && assetMetadata.data.repositoryName, 'repository-name'); + test.equal(assetMetadata && assetMetadata.data.imageTag, 'source-hash'); - test.deepEqual(toCloudFormation(stack), { - Parameters: { - AssetParameterssourcehashImageName3B572B12: { - Type: 'String', - Description: 'ECR repository name and tag for asset "source-hash"' - } - } + test.deepEqual(toCloudFormation(stack), { }); + test.done(); + }, + + 'addDockerImageAsset uses the default repository name'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + stack.addDockerImageAsset({ + sourceHash: 'source-hash', + directoryName: 'directory-name', }); + // THEN + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA); + + test.equal(assetMetadata && assetMetadata.data.packaging, 'container-image'); + test.equal(assetMetadata && assetMetadata.data.path, 'directory-name'); + test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); + test.equal(assetMetadata && assetMetadata.data.repositoryName, 'aws-cdk/assets'); + test.equal(assetMetadata && assetMetadata.data.imageTag, 'source-hash'); + + test.deepEqual(toCloudFormation(stack), { }); + test.done(); + }, + + 'addDockerImageAsset supports overriding repository name through a context key as a workaround until we have API for that'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext('assets-ecr-repository-name', 'my-custom-repo-name'); + + // WHEN + stack.addDockerImageAsset({ + sourceHash: 'source-hash', + directoryName: 'directory-name', + }); + + // THEN + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA); + + test.equal(assetMetadata && assetMetadata.data.packaging, 'container-image'); + test.equal(assetMetadata && assetMetadata.data.path, 'directory-name'); + test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); + test.equal(assetMetadata && assetMetadata.data.repositoryName, 'my-custom-repo-name'); + test.equal(assetMetadata && assetMetadata.data.imageTag, 'source-hash'); + + test.deepEqual(toCloudFormation(stack), { }); test.done(); }, }; diff --git a/packages/@aws-cdk/cx-api/lib/assets.ts b/packages/@aws-cdk/cx-api/lib/assets.ts index b19730973daf7..9e99f95eed9c5 100644 --- a/packages/@aws-cdk/cx-api/lib/assets.ts +++ b/packages/@aws-cdk/cx-api/lib/assets.ts @@ -78,21 +78,36 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry readonly packaging: 'container-image'; /** - * ECR Repository name and repo digest (separated by "@sha256:") where this image is stored. + * ECR Repository name and repo digest (separated by "@sha256:") where this + * image is stored. + * + * @default undefined If not specified, `repositoryName` and `imageTag` are + * required because otherwise how will the stack know where to find the asset, + * ha? + * @deprecated specify `repositoryName` and `imageTag` instead, and then you + * know where the image will go. */ - readonly imageNameParameter: string; + readonly imageNameParameter?: string; /** - * ECR repository name, if omitted a default name based on the asset's - * ID is used instead. Specify this property if you need to statically - * address the image, e.g. from a Kubernetes Pod. - * Note, this is only the repository name, without the registry and - * the tag parts. + * ECR repository name, if omitted a default name based on the asset's ID is + * used instead. Specify this property if you need to statically address the + * image, e.g. from a Kubernetes Pod. Note, this is only the repository name, + * without the registry and the tag parts. * - * @default automatically derived from the asset's ID. + * @default - this parameter is REQUIRED after 1.21.0 */ readonly repositoryName?: string; + /** + * The docker image tag to use for tagging pushed images. This field is + * required if `imageParameterName` is ommited (otherwise, the app won't be + * able to find the image). + * + * @default - this parameter is REQUIRED after 1.21.0 + */ + readonly imageTag?: string; + /** * Build args to pass to the `docker build` command * diff --git a/packages/@aws-cdk/cx-api/lib/versioning.ts b/packages/@aws-cdk/cx-api/lib/versioning.ts index e5ab25656305e..a35747216548d 100644 --- a/packages/@aws-cdk/cx-api/lib/versioning.ts +++ b/packages/@aws-cdk/cx-api/lib/versioning.ts @@ -31,7 +31,7 @@ import { AssemblyManifest } from './cloud-assembly'; * Note that the versions are not compared in a semver way, they are used as * opaque ordered tokens. */ -export const CLOUD_ASSEMBLY_VERSION = '1.16.0'; +export const CLOUD_ASSEMBLY_VERSION = '1.21.0'; /** * Look at the type of response we get and upgrade it to the latest expected version @@ -71,6 +71,13 @@ export function upgradeAssemblyManifest(manifest: AssemblyManifest): AssemblyMan manifest = justUpgradeVersion(manifest, '1.16.0'); } + if (manifest.version === '1.16.0') { + // Backwards compatible changes to ContainerImageAssetMetadataEntry: + // * Make `imageNameParameter` optional (new apps do not require it anymore because container images go to a well-known repository) + // * Add optional `imageTag` to allow apps to specify exactly where to store the image (required if `imageNameParameter` is not defined) + manifest = justUpgradeVersion(manifest, '1.21.0'); + } + return manifest; } diff --git a/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap b/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap index 3ae076caa47ba..3889ac3a1825e 100644 --- a/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap +++ b/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap @@ -48,7 +48,7 @@ Array [ exports[`empty assembly 1`] = ` Object { - "version": "1.16.0", + "version": "1.21.0", } `; diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 2563ba389d260..92b7da1fc50ac 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -62,7 +62,7 @@ async function parseCommandLineArguments() { .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times.', default: [] }) .option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only deploy requested stacks, don\'t include dependencies' }) .option('require-approval', { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'What security-sensitive changes need manual approval' }) - .option('ci', { type: 'boolean', desc: 'Force CI detection. Use --no-ci to disable CI autodetection.', default: process.env.CI !== undefined }) + .option('ci', { type: 'boolean', desc: 'Force CI detection (deprecated)', default: process.env.CI !== undefined }) .option('notification-arns', {type: 'array', desc: 'ARNs of SNS topics that CloudFormation will notify with stack related events', nargs: 1, requiresArg: true}) .option('tags', { type: 'array', alias: 't', desc: 'Tags to add to the stack (KEY=VALUE)', nargs: 1, requiresArg: true }) .option('execute', {type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true}) @@ -227,7 +227,6 @@ async function initCommandLine() { roleArn: args.roleArn, notificationArns: args.notificationArns, requireApproval: configuration.settings.get(['requireApproval']), - ci: args.ci, reuseAssets: args['build-exclude'], tags: configuration.settings.get(['tags']), sdk: aws, diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index a25e1e51e9214..a9fcd8cff4763 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -34,7 +34,6 @@ export interface DeployStackOptions { notificationArns?: string[]; deployName?: string; quiet?: boolean; - ci?: boolean; reuseAssets?: string[]; tags?: Tag[]; @@ -53,7 +52,7 @@ export async function deployStack(options: DeployStackOptions): Promise { + public async prepareEcrRepository(repositoryName: string): Promise { const ecr = await this.props.sdk.ecr(this.props.environment.account, this.props.environment.region, Mode.ForWriting); - let repositoryName; - if (asset.repositoryName) { - // Repository name provided by user - repositoryName = asset.repositoryName; - } else { - // Repository name based on asset id - const assetId = asset.id; - repositoryName = 'cdk/' + assetId.replace(/[:/]/g, '-').toLowerCase(); - } - let repository; + // check if repo already exists try { - debug(`${repositoryName}: checking for repository.`); + debug(`${repositoryName}: checking if ECR repository already exists`); const describeResponse = await ecr.describeRepositories({ repositoryNames: [repositoryName] }).promise(); - repository = describeResponse.repositories![0]; + const existingRepositoryUri = describeResponse.repositories![0]?.repositoryUri; + if (existingRepositoryUri) { + return { repositoryUri: existingRepositoryUri }; + } } catch (e) { if (e.code !== 'RepositoryNotFoundException') { throw e; } } - if (repository) { - return { - repositoryUri: repository.repositoryUri!, - repositoryName - }; + // create the repo (tag it so it will be easier to garbage collect in the future) + debug(`${repositoryName}: creating ECR repository`); + const assetTag = { Key: 'awscdk:asset', Value: 'true' }; + const response = await ecr.createRepository({ repositoryName, tags: [ assetTag ] }).promise(); + const repositoryUri = response.repository?.repositoryUri; + if (!repositoryUri) { + throw new Error(`CreateRepository did not return a repository URI for ${repositoryUri}`); } - debug(`${repositoryName}: creating`); - const response = await ecr.createRepository({ repositoryName }).promise(); - repository = response.repository!; - - // Better put a lifecycle policy on this so as to not cost too much money - await ecr.putLifecyclePolicy({ - repositoryName, - lifecyclePolicyText: JSON.stringify(DEFAULT_REPO_LIFECYCLE) - }).promise(); - - // Configure image scanning on push (helps in identifying software vulnerabilities, no additional charge) - await ecr.putImageScanningConfiguration({ - repositoryName, - imageScanningConfiguration: { - scanOnPush: true - } - }).promise(); + // configure image scanning on push (helps in identifying software vulnerabilities, no additional charge) + debug(`${repositoryName}: enable image scanning`); + await ecr.putImageScanningConfiguration({ repositoryName, imageScanningConfiguration: { scanOnPush: true } }).promise(); - return { - repositoryUri: repository.repositoryUri!, - repositoryName - }; + return { repositoryUri }; } /** @@ -201,7 +180,6 @@ export class ToolkitInfo { /** @experimental */ export interface EcrRepositoryInfo { repositoryUri: string; - repositoryName: string; } /** @experimental */ @@ -251,18 +229,3 @@ function getOutputValue(stack: aws.CloudFormation.Stack, output: string): string } return result; } - -export const DEFAULT_REPO_LIFECYCLE = { - rules: [ - { - rulePriority: 100, - description: 'Retain only 5 images', - selection: { - tagStatus: 'any', - countType: 'imageCountMoreThan', - countNumber: 5, - }, - action: { type: 'expire' } - } - ] -}; diff --git a/packages/aws-cdk/lib/assets.ts b/packages/aws-cdk/lib/assets.ts index 2d35c2acb99da..30cc111866460 100644 --- a/packages/aws-cdk/lib/assets.ts +++ b/packages/aws-cdk/lib/assets.ts @@ -11,7 +11,7 @@ import { prepareContainerAsset } from './docker'; import { debug, success } from './logging'; // tslint:disable-next-line:max-line-length -export async function prepareAssets(stack: cxapi.CloudFormationStackArtifact, toolkitInfo?: ToolkitInfo, ci?: boolean, reuse?: string[]): Promise { +export async function prepareAssets(stack: cxapi.CloudFormationStackArtifact, toolkitInfo?: ToolkitInfo, reuse?: string[]): Promise { reuse = reuse || []; const assets = stack.assets; @@ -41,21 +41,21 @@ export async function prepareAssets(stack: cxapi.CloudFormationStackArtifact, to } const assemblyDir = stack.assembly.directory; - params = params.concat(await prepareAsset(assemblyDir, asset, toolkitInfo, reuseAsset, ci)); + params = params.concat(await prepareAsset(assemblyDir, asset, toolkitInfo, reuseAsset)); } return params; } // tslint:disable-next-line:max-line-length -async function prepareAsset(assemblyDir: string, asset: cxapi.AssetMetadataEntry, toolkitInfo: ToolkitInfo, reuse: boolean, ci?: boolean): Promise { +async function prepareAsset(assemblyDir: string, asset: cxapi.AssetMetadataEntry, toolkitInfo: ToolkitInfo, reuse: boolean): Promise { switch (asset.packaging) { case 'zip': return await prepareZipAsset(assemblyDir, asset, toolkitInfo, reuse); case 'file': return await prepareFileAsset(assemblyDir, asset, toolkitInfo, reuse); case 'container-image': - return await prepareContainerAsset(assemblyDir, asset, toolkitInfo, reuse, ci); + return await prepareContainerAsset(assemblyDir, asset, toolkitInfo, reuse); default: // tslint:disable-next-line:max-line-length throw new Error(`Unsupported packaging type: ${(asset as any).packaging}. You might need to upgrade your aws-cdk toolkit to support this asset type.`); diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 690a7e45ba82a..f9a3717b30071 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -138,7 +138,6 @@ export class CdkToolkit { stack, deployName: stack.stackName, roleArn: options.roleArn, - ci: options.ci, toolkitStackName: options.toolkitStackName, reuseAssets: options.reuseAssets, notificationArns: options.notificationArns, @@ -288,13 +287,6 @@ export interface DeployOptions { */ requireApproval?: RequireApproval; - /** - * Whether we're in CI mode - * - * @default false - */ - ci?: boolean; - /** * Reuse the assets with the given asset IDs */ diff --git a/packages/aws-cdk/lib/docker.ts b/packages/aws-cdk/lib/docker.ts index 5d229460642b3..d8efbd707f7fd 100644 --- a/packages/aws-cdk/lib/docker.ts +++ b/packages/aws-cdk/lib/docker.ts @@ -31,7 +31,14 @@ export async function prepareContainerAsset(assemblyDir: string, asset: ContainerImageAssetMetadataEntry, toolkitInfo: ToolkitInfo, reuse: boolean, - ci?: boolean): Promise<[CloudFormation.Parameter]> { + ci?: boolean): Promise { + + // following 1.21.0, image asset location (repositoryName and imageTag) is fully determined by the + // app, and therefore there is no need to wire the image name through a cloudformation parameter. + if (!asset.imageNameParameter) { + await prepareContainerAssetNew(assemblyDir, asset, toolkitInfo); + return [ ]; + } if (reuse) { return [ @@ -44,8 +51,10 @@ export async function prepareContainerAsset(assemblyDir: string, debug(' 👑 Preparing Docker image asset:', contextPath); try { - const ecr = await toolkitInfo.prepareEcrRepository(asset); - const latest = `${ecr.repositoryUri}:latest`; + const repositoryName = asset.repositoryName ?? 'cdk/' + asset.id.replace(/[:/]/g, '-').toLowerCase(); + const ecr = await toolkitInfo.prepareEcrRepository(repositoryName); + const imageTag = asset.imageTag ?? 'latest'; + const latest = `${ecr.repositoryUri}:${imageTag}`; let loggedIn = false; @@ -103,7 +112,7 @@ export async function prepareContainerAsset(assemblyDir: string, } return [ - { ParameterKey: asset.imageNameParameter, ParameterValue: repoDigest.replace(ecr.repositoryUri, ecr.repositoryName) }, + { ParameterKey: asset.imageNameParameter, ParameterValue: repoDigest.replace(ecr.repositoryUri, repositoryName) }, ]; } catch (e) { if (e.code === 'ENOENT') { @@ -114,6 +123,71 @@ export async function prepareContainerAsset(assemblyDir: string, } } +/** + * Build and upload a Docker image + */ +export async function prepareContainerAssetNew(assemblyDir: string, + asset: ContainerImageAssetMetadataEntry, + toolkitInfo: ToolkitInfo) { + + if (asset.imageNameParameter || !asset.repositoryName || !asset.imageTag) { + throw new Error(`invalid docker image asset configuration. "repositoryName" and "imageTag" are required and "imageNameParameter" is not allowed`); + } + + const contextPath = path.isAbsolute(asset.path) ? asset.path : path.join(assemblyDir, asset.path); + + debug(' 👑 Preparing Docker image asset:', contextPath); + const ecr = await toolkitInfo.prepareEcrRepository(asset.repositoryName); + + // if both repo name and image tag are explicitly defined, we assume the + // image is immutable and can skip build & push. + debug(`${asset.repositoryName}:${asset.imageTag}: checking if image already exists`); + if (await toolkitInfo.checkEcrImage(asset.repositoryName, asset.imageTag)) { + print(`${asset.repositoryName}:${asset.imageTag}: image already exists, skipping build and push`); + return; + } + + // we use "latest" for image tag for backwards compatibility with pre-1.21.0 apps. + const fullImageName = `${ecr.repositoryUri}:${asset.imageTag}`; + + // render "docker build" command + + const buildCommand = [ 'docker', 'build' ]; + + buildCommand.push('--tag', fullImageName); + + if (asset.target) { + buildCommand.push('--target', asset.target); + } + + if (asset.file) { + buildCommand.push('--file', asset.file); + } + + for (const [ key, value ] of Object.entries(asset.buildArgs || {})) { + buildCommand.push(`--build-arg`, `${key}=${value}`); + } + + buildCommand.push(contextPath); + + try { + await shell(buildCommand); + } catch (e) { + if (e.code === 'ENOENT') { + throw new Error('Unable to execute "docker" in order to build a container asset. Please install "docker" and try again.'); + } + throw e; + } + + // login to ECR + await dockerLogin(toolkitInfo); + + // There's no way to make this quiet, so we can't use a PleaseHold. Print a header message. + print(` ⌛ Pushing Docker image for ${contextPath}; this may take a while.`); + await shell(['docker', 'push', fullImageName]); + debug(` 👑 Docker image for ${contextPath} pushed.`); +} + /** * Get credentials from ECR and run docker login */ @@ -123,4 +197,4 @@ async function dockerLogin(toolkitInfo: ToolkitInfo) { '--username', credentials.username, '--password', credentials.password, credentials.endpoint]); -} +} \ No newline at end of file diff --git a/packages/aws-cdk/test/assets.test.ts b/packages/aws-cdk/test/assets.test.ts index 563811e135524..f1fb2a4974ebb 100644 --- a/packages/aws-cdk/test/assets.test.ts +++ b/packages/aws-cdk/test/assets.test.ts @@ -67,7 +67,7 @@ test('prepare assets with reuse', async () => { const toolkit = new FakeToolkit(); // WHEN - const params = await prepareAssets(stack, toolkit as any, undefined, ['SomeStackSomeResource4567']); + const params = await prepareAssets(stack, toolkit as any, ['SomeStackSomeResource4567']); // THEN expect(params).toEqual([ @@ -101,7 +101,7 @@ test('prepare container asset with reuse', async () => { const toolkit = new FakeToolkit(); // WHEN - const params = await prepareAssets(stack, toolkit as any, undefined, ['SomeStackSomeResource4567']); + const params = await prepareAssets(stack, toolkit as any, ['SomeStackSomeResource4567']); // THEN expect(params).toEqual([ diff --git a/packages/aws-cdk/test/docker-new.test.ts b/packages/aws-cdk/test/docker-new.test.ts new file mode 100644 index 0000000000000..b8469e512c71c --- /dev/null +++ b/packages/aws-cdk/test/docker-new.test.ts @@ -0,0 +1,304 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import * as sinon from 'sinon'; +import { ToolkitInfo } from '../lib'; +import { prepareContainerAsset } from '../lib/docker'; +import * as os from '../lib/os'; +import { MockSDK } from './util/mock-sdk'; + +test('fails if "repositoryName" and "imageTag" are not specified', async () => { + // GIVEN + const toolkit = newMockToolkitInfo(); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: '/foo', + sourceHash: '0123456789abcdef', + }; + + // THEN + await expect(prepareContainerAsset('.', asset, toolkit, false)) + .rejects.toEqual(new Error(`invalid docker image asset configuration\. "repositoryName" and "imageTag" are required and "imageNameParameter" is not allowed`)); +}); + +test('fails if "repositoryName" is not specified', async () => { + // GIVEN + const toolkit = newMockToolkitInfo(); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + repositoryName: 'repository-name', + packaging: 'container-image', + path: '/foo', + sourceHash: '0123456789abcdef', + }; + + // THEN + await expect(prepareContainerAsset('.', asset, toolkit, false)) + .rejects.toEqual(new Error(`invalid docker image asset configuration\. "repositoryName" and "imageTag" are required and "imageNameParameter" is not allowed`)); +}); + +test('creates repository with given name', async () => { + // GIVEN + const sdk = new MockSDK(); + const toolkit = newMockToolkitInfo(sdk); + let createdName; + + sdk.stubEcr({ + describeRepositories: () => ({ repositories: [] }), + createRepository: req => { + createdName = req.repositoryName; + + // Stop the test so that we don't actually docker build + throw new Error('STOPTEST'); + }, + }); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: '/foo', + repositoryName: 'some-name', + imageTag: 'image-tag', + sourceHash: '0123456789abcdef', + }; + + try { + await prepareContainerAsset('.', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + expect(createdName).toBe('some-name'); +}); + +test('configures image scanning', async () => { + // GIVEN + let putImageScanningConfigurationParams; + + const sdk = new MockSDK(); + sdk.stubEcr({ + describeRepositories: () => ({ repositories: [] }), + createRepository: () => ({ repository: { repositoryUri: 'uri' } }), + putImageScanningConfiguration: params => { + putImageScanningConfigurationParams = params; + + // Stop the test so that we don't actually docker build + throw new Error('STOPTEST'); + } + }); + + const toolkit = newMockToolkitInfo(sdk); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: '/foo', + repositoryName: 'some-name', + imageTag: 'some-tag', + sourceHash: '0123456789abcdef', + }; + + try { + await prepareContainerAsset('.', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + expect(putImageScanningConfigurationParams).toEqual({ + repositoryName: 'some-name', + imageScanningConfiguration: { + scanOnPush: true + } + }); +}); + +test('passes the correct target to docker build', async () => { + // GIVEN + const toolkit = newMockToolkitInfo(); + + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri' }); + const checkEcrImageStub = sinon.stub(toolkit, 'checkEcrImage').resolves(false); + const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: '/foo', + sourceHash: '1234567890abcdef', + repositoryName: 'some-name', + imageTag: 'some-tag', + buildArgs: { + a: 'b', + c: 'd' + }, + target: 'a-target', + }; + + try { + await prepareContainerAsset('.', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + const command = [ 'docker', 'build', '--tag', `uri:some-tag`, '--target', 'a-target', '--build-arg', 'a=b', '--build-arg', 'c=d', '/foo' ]; + sinon.assert.calledWith(shellStub, command); + + prepareEcrRepositoryStub.restore(); + shellStub.restore(); + checkEcrImageStub.restore(); +}); + +test('passes the correct args to docker build', async () => { + // GIVEN + const toolkit = newMockToolkitInfo(); + + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri' }); + const checkEcrImageStub = sinon.stub(toolkit, 'checkEcrImage').resolves(false); + const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: '/foo', + sourceHash: '1234567890abcdef', + repositoryName: 'some-name', + imageTag: 'some-tag', + buildArgs: { + a: 'b', + c: 'd' + } + }; + + try { + await prepareContainerAsset('.', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + const command = ['docker', 'build', '--tag', `uri:some-tag`, '--build-arg', 'a=b', '--build-arg', 'c=d', '/foo']; + sinon.assert.calledWith(shellStub, command); + + prepareEcrRepositoryStub.restore(); + checkEcrImageStub.restore(); + shellStub.restore(); +}); + +test('passes the correct docker file name if specified', async () => { + const toolkit = newMockToolkitInfo(); + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri' }); + const checkEcrImageStub = sinon.stub(toolkit, 'checkEcrImage').resolves(false); + const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: '/foo', + sourceHash: '1234567890abcdef', + repositoryName: 'some-name', + imageTag: 'some-tag', + file: 'CustomDockerfile', + buildArgs: { + a: 'b', + c: 'd' + } + }; + + try { + await prepareContainerAsset('.', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + const command = ['docker', 'build', '--tag', `uri:some-tag`, '--file', 'CustomDockerfile', '--build-arg', 'a=b', '--build-arg', 'c=d', '/foo']; + sinon.assert.calledWith(shellStub, command); + + prepareEcrRepositoryStub.restore(); + checkEcrImageStub.restore(); + shellStub.restore(); +}); + +test('relative path', async () => { + // GIVEN + const toolkit = newMockToolkitInfo(); + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri' }); + const checkEcrImageStub = sinon.stub(toolkit, 'checkEcrImage').resolves(false); + const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: 'relative-to-assembly', + sourceHash: '1234567890abcdef', + repositoryName: 'some-name', + imageTag: 'some-tag', + buildArgs: { + a: 'b', + c: 'd' + } + }; + + try { + await prepareContainerAsset('/assembly/dir/root', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + const command = ['docker', 'build', '--tag', `uri:some-tag`, '--build-arg', 'a=b', '--build-arg', 'c=d', '/assembly/dir/root/relative-to-assembly']; + sinon.assert.calledWith(shellStub, command); + + prepareEcrRepositoryStub.restore(); + checkEcrImageStub.restore(); + shellStub.restore(); +}); + +test('skips build & push if image already exists in the ECR repo', async () => { + // GIVEN + const toolkit = newMockToolkitInfo(); + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository'); + const shellStub = sinon.stub(os, 'shell'); + const checkEcrImageStub = sinon.stub(toolkit, 'checkEcrImage').resolves(true); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + packaging: 'container-image', + path: 'relative-to-assembly', + sourceHash: '1234567890abcdef', + repositoryName: 'some-name', + imageTag: 'some-tag', + buildArgs: { + a: 'b', + c: 'd' + } + }; + + await prepareContainerAsset('/assembly/dir/root', asset, toolkit, false); + + // THEN + sinon.assert.calledOnce(prepareEcrRepositoryStub); + sinon.assert.calledOnce(checkEcrImageStub); + sinon.assert.notCalled(shellStub); +}); + +function newMockToolkitInfo(sdk: MockSDK = new MockSDK()) { + return new ToolkitInfo({ + sdk, + bucketName: 'BUCKET_NAME', + bucketEndpoint: 'BUCKET_ENDPOINT', + environment: { name: 'env', account: '1234', region: 'abc' } + }); +} \ No newline at end of file diff --git a/packages/aws-cdk/test/docker.test.ts b/packages/aws-cdk/test/docker.test.ts index 1dc03fa0f4c58..be13712c49aff 100644 --- a/packages/aws-cdk/test/docker.test.ts +++ b/packages/aws-cdk/test/docker.test.ts @@ -1,6 +1,6 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as sinon from 'sinon'; -import { DEFAULT_REPO_LIFECYCLE, ToolkitInfo } from '../lib'; +import { ToolkitInfo } from '../lib'; import { prepareContainerAsset } from '../lib/docker'; import * as os from '../lib/os'; import { MockSDK } from './util/mock-sdk'; @@ -96,9 +96,8 @@ test('derives repository name from asset id', async () => { expect(createdName).toBe('cdk/stack-construct-abc123'); }); -test('configures lifecycle policy and image scanning', async () => { +test('configures image scanning', async () => { // GIVEN - let putLifecyclePolicyParams; let putImageScanningConfigurationParams; const sdk = new MockSDK(); @@ -115,11 +114,6 @@ test('configures lifecycle policy and image scanning', async () => { }; }, - putLifecyclePolicy(params) { - putLifecyclePolicyParams = params; - return {}; - }, - putImageScanningConfiguration(params) { putImageScanningConfigurationParams = params; @@ -151,12 +145,6 @@ test('configures lifecycle policy and image scanning', async () => { if (!/STOPTEST/.test(e.toString())) { throw e; } } - // THEN - expect(putLifecyclePolicyParams).toEqual({ - repositoryName: 'some-name', - lifecyclePolicyText: JSON.stringify(DEFAULT_REPO_LIFECYCLE) - }); - expect(putImageScanningConfigurationParams).toEqual({ repositoryName: 'some-name', imageScanningConfiguration: { @@ -176,7 +164,6 @@ test('passes the correct target to docker build', async () => { const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri', - repositoryName: 'name' }); const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); @@ -197,7 +184,7 @@ test('passes the correct target to docker build', async () => { }; try { - await prepareContainerAsset('.', asset, toolkit, false, false); + await prepareContainerAsset('.', asset, toolkit, false); } catch (e) { if (!/STOPTEST/.test(e.toString())) { throw e; } } @@ -221,7 +208,6 @@ test('passes the correct args to docker build', async () => { const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri', - repositoryName: 'name' }); const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); @@ -265,7 +251,6 @@ test('relative path', async () => { const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri', - repositoryName: 'name' }); const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); @@ -309,7 +294,6 @@ test('passes the correct file to docker build', async () => { const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ repositoryUri: 'uri', - repositoryName: 'name' }); const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); @@ -322,6 +306,11 @@ test('passes the correct file to docker build', async () => { path: '/foo', sourceHash: '1234567890abcdef', repositoryName: 'some-name', + buildArgs: { + a: 'b', + c: 'd' + }, + target: 'a-target', file: 'some-file' }; @@ -331,10 +320,58 @@ test('passes the correct file to docker build', async () => { if (!/STOPTEST/.test(e.toString())) { throw e; } } - // THEN - const command = ['docker', 'build', '--tag', `uri:latest`, '/foo', '--file', 'some-file']; + const command = ['docker', 'build', '--build-arg', 'a=b', '--build-arg', 'c=d', '--tag', 'uri:latest', '/foo', '--target', 'a-target', '--file', 'some-file']; + sinon.assert.calledWith(shellStub, command); - expect(shellStub.calledWith(command)).toBeTruthy(); + prepareEcrRepositoryStub.restore(); + shellStub.restore(); +}); + +// since "imageNameParameter" is present, this means we are pre 1.21.0, which +// implies which is before "imageTag" was supported. still, for the sake of +// correctness of the protocol we added support for specifying image tag even if +// it's probably not going to be used. +test('"imageTag" is used instead of "latest"', async () => { + // GIVEN + const toolkit = new ToolkitInfo({ + sdk: new MockSDK(), + bucketName: 'BUCKET_NAME', + bucketEndpoint: 'BUCKET_ENDPOINT', + environment: { name: 'env', account: '1234', region: 'abc' } + }); + + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ + repositoryUri: 'uri', + }); + + const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + imageNameParameter: 'MyParameter', + packaging: 'container-image', + path: '/foo', + sourceHash: '1234567890abcdef', + repositoryName: 'some-name', + imageTag: 'image-tag', + buildArgs: { + a: 'b', + c: 'd' + }, + target: 'a-target', + file: 'some-file' + }; + + try { + await prepareContainerAsset('.', asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + const command = ['docker', 'build', '--build-arg', 'a=b', '--build-arg', 'c=d', '--tag', 'uri:image-tag', '/foo', '--target', 'a-target', '--file', 'some-file']; + sinon.assert.calledWith(shellStub, command); prepareEcrRepositoryStub.restore(); shellStub.restore(); diff --git a/packages/decdk/test/fixture/tsconfig.json b/packages/decdk/test/fixture/tsconfig.json index ff97fed386484..07afb1f64860c 100644 --- a/packages/decdk/test/fixture/tsconfig.json +++ b/packages/decdk/test/fixture/tsconfig.json @@ -7,7 +7,7 @@ "inlineSourceMap": true, "inlineSources": true, "lib": [ - "es2016" + "es2018" ], "module": "CommonJS", "noEmitOnError": true, @@ -22,7 +22,7 @@ "strictNullChecks": true, "strictPropertyInitialization": true, "stripInternal": true, - "target": "ES2017" + "target": "ES2018" }, "include": [ "**/*.ts"