diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts index 3676f06352068..c44a4b176e6fd 100644 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts @@ -313,7 +313,7 @@ export function arrayWith(...elements: any[]): PropertyMatcher { const ret = (value: any, inspection: InspectionFailure): boolean => { if (!Array.isArray(value)) { - return failMatcher(inspection, `Expect an object but got '${typeof value}'`); + return failMatcher(inspection, `Expect an array but got '${typeof value}'`); } for (const element of elements) { @@ -412,4 +412,4 @@ function isCallable(x: any): x is ((...args: any[]) => any) { function isObject(x: any): x is object { // Because `typeof null === 'object'`. return x && typeof x === 'object'; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-amplify/README.md b/packages/@aws-cdk/aws-amplify/README.md index f6e9b17e71627..9a6ec9b6e48f0 100644 --- a/packages/@aws-cdk/aws-amplify/README.md +++ b/packages/@aws-cdk/aws-amplify/README.md @@ -52,6 +52,17 @@ const amplifyApp = new amplify.App(this, 'MyApp', { }); ``` +To connect your `App` to GitLab, use the `GitLabSourceCodeProvider`: +```ts +const amplifyApp = new amplify.App(this, 'MyApp', { + sourceCodeProvider: new amplify.GitLabSourceCodeProvider({ + owner: '', + repository: '', + oauthToken: cdk.SecretValue.secretsManager('my-gitlab-token') + }) +}); +``` + To connect your `App` to CodeCommit, use the `CodeCommitSourceCodeProvider`: ```ts const repository = new codecommit.Repository(this, 'Repo', { diff --git a/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts b/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts index 1b280d49e6170..8736c76ff7649 100644 --- a/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts +++ b/packages/@aws-cdk/aws-amplify/lib/source-code-providers.ts @@ -36,6 +36,40 @@ export class GitHubSourceCodeProvider implements ISourceCodeProvider { } } +/** + * Properties for a GitLab source code provider + */ +export interface GitLabSourceCodeProviderProps { + /** + * The user or organization owning the repository + */ + readonly owner: string; + + /** + * The name of the repository + */ + readonly repository: string; + + /** + * A personal access token with the `repo` scope + */ + readonly oauthToken: SecretValue; +} + +/** + * GitLab source code provider + */ +export class GitLabSourceCodeProvider implements ISourceCodeProvider { + constructor(private readonly props: GitLabSourceCodeProviderProps) { } + + public bind(_app: App): SourceCodeProviderConfig { + return { + repository: `https://gitlab.com/${this.props.owner}/${this.props.repository}`, + oauthToken: this.props.oauthToken, + }; + } +} + /** * Properties for a CodeCommit source code provider */ diff --git a/packages/@aws-cdk/aws-amplify/test/app.test.ts b/packages/@aws-cdk/aws-amplify/test/app.test.ts index b5c9a3b3a7942..5af765cae6d75 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/app.test.ts @@ -61,6 +61,58 @@ test('create an app connected to a GitHub repository', () => { }); }); +test('create an app connected to a GitLab repository', () => { + // WHEN + new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitLabSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + buildSpec: codebuild.BuildSpec.fromObject({ + version: '1.0', + frontend: { + phases: { + build: { + commands: [ + 'npm run build', + ], + }, + }, + }, + }), + }); + + // THEN + expect(stack).toHaveResource('AWS::Amplify::App', { + Name: 'App', + BuildSpec: '{\n \"version\": \"1.0\",\n \"frontend\": {\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"npm run build\"\n ]\n }\n }\n }\n}', + IAMServiceRole: { + 'Fn::GetAtt': [ + 'AppRole1AF9B530', + 'Arn', + ], + }, + OauthToken: 'secret', + Repository: 'https://gitlab.com/aws/aws-cdk', + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'amplify.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); +}); + test('create an app connected to a CodeCommit repository', () => { // WHEN new amplify.App(stack, 'App', { diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 5e978b796045f..bcb0fb3eff572 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -75,13 +75,16 @@ export class ApiStack extends Stack { }, authorizationConfig: { defaultAuthorization: { - userPool, - defaultAction: UserPoolDefaultAction.ALLOW, + authorizationType: AuthorizationType.USER_POOL, + userPoolConfig: { + userPool, + defaultAction: UserPoolDefaultAction.ALLOW + }, }, additionalAuthorizationModes: [ { - apiKeyDesc: 'My API Key', - }, + authorizationType: AuthorizationType.API_KEY, + } ], }, schemaDefinitionFile: './schema.graphql', diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index b1da44a40bee0..6487d401401b8 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,15 +1,74 @@ import { IUserPool } from '@aws-cdk/aws-cognito'; import { Table } from '@aws-cdk/aws-dynamodb'; -import { IGrantable, IPrincipal, IRole, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { + IGrantable, + IPrincipal, + IRole, + ManagedPolicy, + Role, + ServicePrincipal, +} from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResolvable } from '@aws-cdk/core'; import { readFileSync } from 'fs'; -import { CfnApiKey, CfnDataSource, CfnGraphQLApi, CfnGraphQLSchema, CfnResolver } from './appsync.generated'; +import { + CfnApiKey, + CfnDataSource, + CfnGraphQLApi, + CfnGraphQLSchema, + CfnResolver, +} from './appsync.generated'; /** - * Marker interface for the different authorization modes. + * enum with all possible values for AppSync authorization type */ -export interface AuthMode { } +export enum AuthorizationType { + /** + * API Key authorization type + */ + API_KEY = 'API_KEY', + /** + * AWS IAM authorization type. Can be used with Cognito Identity Pool federated credentials + */ + IAM = 'AWS_IAM', + /** + * Cognito User Pool authorization type + */ + USER_POOL = 'AMAZON_COGNITO_USER_POOLS', + /** + * OpenID Connect authorization type + */ + OIDC = 'OPENID_CONNECT', +} + +/** + * Interface to specify default or additional authorization(s) + */ +export interface AuthorizationMode { + /** + * One of possible four values AppSync supports + * + * @see https://docs.aws.amazon.com/appsync/latest/devguide/security.html + * + * @default - `AuthorizationType.API_KEY` + */ + readonly authorizationType: AuthorizationType; + /** + * If authorizationType is `AuthorizationType.USER_POOL`, this option is required. + * @default - none + */ + readonly userPoolConfig?: UserPoolConfig; + /** + * If authorizationType is `AuthorizationType.API_KEY`, this option can be configured. + * @default - check default values of `ApiKeyConfig` memebers + */ + readonly apiKeyConfig?: ApiKeyConfig; + /** + * If authorizationType is `AuthorizationType.OIDC`, this option is required. + * @default - none + */ + readonly openIdConnectConfig?: OpenIdConnectConfig; +} /** * enum with all possible values for Cognito user-pool default actions @@ -28,8 +87,7 @@ export enum UserPoolDefaultAction { /** * Configuration for Cognito user-pools in AppSync */ -export interface UserPoolConfig extends AuthMode { - +export interface UserPoolConfig { /** * The Cognito user pool to use as identity source */ @@ -48,18 +106,20 @@ export interface UserPoolConfig extends AuthMode { readonly defaultAction?: UserPoolDefaultAction; } -function isUserPoolConfig(obj: unknown): obj is UserPoolConfig { - return (obj as UserPoolConfig).userPool !== undefined; -} - /** * Configuration for API Key authorization in AppSync */ -export interface ApiKeyConfig extends AuthMode { +export interface ApiKeyConfig { + /** + * Unique name of the API Key + * @default - 'DefaultAPIKey' + */ + readonly name?: string; /** - * Unique description of the API key + * Description of API key + * @default - 'Default API Key created by CDK' */ - readonly apiKeyDesc: string; + readonly description?: string; /** * The time from creation time after which the API key expires, using RFC3339 representation. @@ -70,8 +130,33 @@ export interface ApiKeyConfig extends AuthMode { readonly expires?: string; } -function isApiKeyConfig(obj: unknown): obj is ApiKeyConfig { - return (obj as ApiKeyConfig).apiKeyDesc !== undefined; +/** + * Configuration for OpenID Connect authorization in AppSync + */ +export interface OpenIdConnectConfig { + /** + * The number of milliseconds an OIDC token is valid after being authenticated by OIDC provider. + * `auth_time` claim in OIDC token is required for this validation to work. + * @default - no validation + */ + readonly tokenExpiryFromAuth?: number; + /** + * The number of milliseconds an OIDC token is valid after being issued to a user. + * This validation uses `iat` claim of OIDC token. + * @default - no validation + */ + readonly tokenExpiryFromIssue?: number; + /** + * The client identifier of the Relying party at the OpenID identity provider. + * A regular expression can be specified so AppSync can validate against multiple client identifiers at a time. + * @example - 'ABCD|CDEF' where ABCD and CDEF are two different clientId + * @default - * (All) + */ + readonly clientId?: string; + /** + * The issuer for the OIDC configuration. The issuer returned by discovery must exactly match the value of `iss` in the OIDC token. + */ + readonly oidcProvider: string; } /** @@ -83,14 +168,14 @@ export interface AuthorizationConfig { * * @default - API Key authorization */ - readonly defaultAuthorization?: AuthMode; + readonly defaultAuthorization?: AuthorizationMode; /** * Additional authorization modes * * @default - No other modes */ - readonly additionalAuthorizationModes?: [AuthMode] + readonly additionalAuthorizationModes?: AuthorizationMode[]; } /** @@ -206,22 +291,56 @@ export class GraphQLApi extends Construct { constructor(scope: Construct, id: string, props: GraphQLApiProps) { super(scope, id); + this.validateAuthorizationProps(props); + const defaultAuthorizationType = + props.authorizationConfig?.defaultAuthorization?.authorizationType || + AuthorizationType.API_KEY; + let apiLogsRole; if (props.logConfig) { - apiLogsRole = new Role(this, 'ApiLogsRole', { assumedBy: new ServicePrincipal('appsync') }); - apiLogsRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs')); + apiLogsRole = new Role(this, 'ApiLogsRole', { + assumedBy: new ServicePrincipal('appsync'), + }); + apiLogsRole.addManagedPolicy( + ManagedPolicy.fromAwsManagedPolicyName( + 'service-role/AWSAppSyncPushToCloudWatchLogs', + ), + ); } this.api = new CfnGraphQLApi(this, 'Resource', { name: props.name, - authenticationType: 'API_KEY', - ...props.logConfig && { + authenticationType: defaultAuthorizationType, + ...(props.logConfig && { logConfig: { cloudWatchLogsRoleArn: apiLogsRole ? apiLogsRole.roleArn : undefined, excludeVerboseContent: props.logConfig.excludeVerboseContent, - fieldLogLevel: props.logConfig.fieldLogLevel ? props.logConfig.fieldLogLevel.toString() : undefined, + fieldLogLevel: props.logConfig.fieldLogLevel + ? props.logConfig.fieldLogLevel.toString() + : undefined, }, - }, + }), + openIdConnectConfig: + props.authorizationConfig?.defaultAuthorization?.authorizationType === + AuthorizationType.OIDC + ? this.formatOpenIdConnectConfig( + props.authorizationConfig.defaultAuthorization + .openIdConnectConfig!, + ) + : undefined, + userPoolConfig: + props.authorizationConfig?.defaultAuthorization?.authorizationType === + AuthorizationType.USER_POOL + ? this.formatUserPoolConfig( + props.authorizationConfig.defaultAuthorization.userPoolConfig!, + ) + : undefined, + additionalAuthenticationProviders: props.authorizationConfig + ?.additionalAuthorizationModes!.length + ? this.formatAdditionalAuthorizationModes( + props.authorizationConfig!.additionalAuthorizationModes!, + ) + : undefined, }); this.apiId = this.api.attrApiId; @@ -229,8 +348,18 @@ export class GraphQLApi extends Construct { this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; - if (props.authorizationConfig) { - this.setupAuth(props.authorizationConfig); + if ( + defaultAuthorizationType === AuthorizationType.API_KEY || + props.authorizationConfig?.additionalAuthorizationModes?.findIndex( + (authMode) => authMode.authorizationType === AuthorizationType.API_KEY + ) !== -1 + ) { + const apiKeyConfig: ApiKeyConfig = props.authorizationConfig + ?.defaultAuthorization?.apiKeyConfig || { + name: 'DefaultAPIKey', + description: 'Default API Key created by CDK', + }; + this.createAPIKey(apiKeyConfig); } let definition; @@ -266,7 +395,11 @@ export class GraphQLApi extends Construct { * @param description The description of the data source * @param table The DynamoDB table backing this data source [disable-awslint:ref-via-interface] */ - public addDynamoDbDataSource(name: string, description: string, table: Table): DynamoDbDataSource { + public addDynamoDbDataSource( + name: string, + description: string, + table: Table, + ): DynamoDbDataSource { return new DynamoDbDataSource(this, `${name}DS`, { api: this, description, @@ -281,7 +414,11 @@ export class GraphQLApi extends Construct { * @param description The description of the data source * @param lambdaFunction The Lambda function to call to interact with this data source */ - public addLambdaDataSource(name: string, description: string, lambdaFunction: IFunction): LambdaDataSource { + public addLambdaDataSource( + name: string, + description: string, + lambdaFunction: IFunction, + ): LambdaDataSource { return new LambdaDataSource(this, `${name}DS`, { api: this, description, @@ -290,55 +427,132 @@ export class GraphQLApi extends Construct { }); } - private setupAuth(auth: AuthorizationConfig) { - if (isUserPoolConfig(auth.defaultAuthorization)) { - const { authenticationType, userPoolConfig } = this.userPoolDescFrom(auth.defaultAuthorization); - this.api.authenticationType = authenticationType; - this.api.userPoolConfig = userPoolConfig; - } else if (isApiKeyConfig(auth.defaultAuthorization)) { - this.api.authenticationType = this.apiKeyDesc(auth.defaultAuthorization).authenticationType; + private validateAuthorizationProps(props: GraphQLApiProps) { + const defaultAuthorizationType = + props.authorizationConfig?.defaultAuthorization?.authorizationType || + AuthorizationType.API_KEY; + + if ( + defaultAuthorizationType === AuthorizationType.OIDC && + !props.authorizationConfig?.defaultAuthorization?.openIdConnectConfig + ) { + throw new Error('Missing default OIDC Configuration'); } - this.api.additionalAuthenticationProviders = []; - for (const mode of (auth.additionalAuthorizationModes || [])) { - if (isUserPoolConfig(mode)) { - this.api.additionalAuthenticationProviders.push(this.userPoolDescFrom(mode)); - } else if (isApiKeyConfig(mode)) { - this.api.additionalAuthenticationProviders.push(this.apiKeyDesc(mode)); - } + if ( + defaultAuthorizationType === AuthorizationType.USER_POOL && + !props.authorizationConfig?.defaultAuthorization?.userPoolConfig + ) { + throw new Error('Missing default User Pool Configuration'); + } + + if (props.authorizationConfig?.additionalAuthorizationModes) { + props.authorizationConfig.additionalAuthorizationModes.forEach( + (authorizationMode) => { + if ( + authorizationMode.authorizationType === AuthorizationType.API_KEY && + defaultAuthorizationType === AuthorizationType.API_KEY + ) { + throw new Error( + "You can't duplicate API_KEY in additional authorization config. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html", + ); + } + + if ( + authorizationMode.authorizationType === AuthorizationType.IAM && + defaultAuthorizationType === AuthorizationType.IAM + ) { + throw new Error( + "You can't duplicate IAM in additional authorization config. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html", + ); + } + + if ( + authorizationMode.authorizationType === AuthorizationType.OIDC && + !authorizationMode.openIdConnectConfig + ) { + throw new Error( + 'Missing OIDC Configuration inside an additional authorization mode', + ); + } + + if ( + authorizationMode.authorizationType === + AuthorizationType.USER_POOL && + !authorizationMode.userPoolConfig + ) { + throw new Error( + 'Missing User Pool Configuration inside an additional authorization mode', + ); + } + }, + ); } } - private userPoolDescFrom(upConfig: UserPoolConfig): { authenticationType: string; userPoolConfig: CfnGraphQLApi.UserPoolConfigProperty } { + private formatOpenIdConnectConfig( + config: OpenIdConnectConfig, + ): CfnGraphQLApi.OpenIDConnectConfigProperty { return { - authenticationType: 'AMAZON_COGNITO_USER_POOLS', - userPoolConfig: { - appIdClientRegex: upConfig.appIdClientRegex, - userPoolId: upConfig.userPool.userPoolId, - awsRegion: upConfig.userPool.stack.region, - defaultAction: upConfig.defaultAction ? upConfig.defaultAction.toString() : 'ALLOW', - }, + authTtl: config.tokenExpiryFromAuth, + clientId: config.clientId, + iatTtl: config.tokenExpiryFromIssue, + issuer: config.oidcProvider, }; } - private apiKeyDesc(akConfig: ApiKeyConfig): { authenticationType: string } { + private formatUserPoolConfig( + config: UserPoolConfig, + ): CfnGraphQLApi.UserPoolConfigProperty { + return { + userPoolId: config.userPool.userPoolId, + awsRegion: config.userPool.stack.region, + appIdClientRegex: config.appIdClientRegex, + defaultAction: config.defaultAction || 'ALLOW', + }; + } + + private createAPIKey(config: ApiKeyConfig) { let expires: number | undefined; - if (akConfig.expires) { - expires = new Date(akConfig.expires).valueOf(); - const now = Date.now(); - const days = (d: number) => now + Duration.days(d).toMilliseconds(); + if (config.expires) { + expires = new Date(config.expires).valueOf(); + const days = (d: number) => + Date.now() + Duration.days(d).toMilliseconds(); if (expires < days(1) || expires > days(365)) { throw Error('API key expiration must be between 1 and 365 days.'); } expires = Math.round(expires / 1000); } - const key = new CfnApiKey(this, `${akConfig.apiKeyDesc || ''}ApiKey`, { + const key = new CfnApiKey(this, `${config.name || 'DefaultAPIKey'}ApiKey`, { expires, - description: akConfig.apiKeyDesc, + description: config.description || 'Default API Key created by CDK', apiId: this.apiId, }); this._apiKey = key.attrApiKey; - return { authenticationType: 'API_KEY' }; + } + + private formatAdditionalAuthorizationModes( + authModes: AuthorizationMode[], + ): CfnGraphQLApi.AdditionalAuthenticationProviderProperty[] { + return authModes.reduce< + CfnGraphQLApi.AdditionalAuthenticationProviderProperty[] + >( + (acc, authMode) => [ + ...acc, + { + authenticationType: authMode.authorizationType, + userPoolConfig: + authMode.authorizationType === AuthorizationType.USER_POOL + ? this.formatUserPoolConfig(authMode.userPoolConfig!) + : undefined, + openIdConnectConfig: + authMode.authorizationType === AuthorizationType.OIDC + ? this.formatOpenIdConnectConfig(authMode.openIdConnectConfig!) + : undefined, + }, + ], + [], + ); } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json index f51065c3287c2..07215fce52330 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -85,26 +85,20 @@ } } }, - "ApiMyAPIKeyApiKeyACDEE2CC": { + "ApiDefaultAPIKeyApiKey74F5313B": { "Type": "AWS::AppSync::ApiKey", "Properties": { "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] + "Fn::GetAtt": ["ApiF70053CD", "ApiId"] }, - "Description": "My API Key" + "Description": "Default API Key created by CDK" } }, "ApiSchema510EECD7": { "Type": "AWS::AppSync::GraphQLSchema", "Properties": { "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] + "Fn::GetAtt": ["ApiF70053CD", "ApiId"] }, "Definition": "type ServiceVersion {\n version: String!\n}\n\ntype Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getServiceVersion: ServiceVersion\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n}" } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index a04b33bcdb000..07cf7f028d41c 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -2,7 +2,15 @@ import { UserPool } from '@aws-cdk/aws-cognito'; import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import { join } from 'path'; -import { GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, UserPoolDefaultAction, Values } from '../lib'; +import { + AuthorizationType, + GraphQLApi, + KeyCondition, + MappingTemplate, + PrimaryKey, + UserPoolDefaultAction, + Values, +} from '../lib'; const app = new App(); const stack = new Stack(app, 'aws-appsync-integ'); @@ -16,14 +24,15 @@ const api = new GraphQLApi(stack, 'Api', { schemaDefinitionFile: join(__dirname, 'schema.graphql'), authorizationConfig: { defaultAuthorization: { - userPool, - defaultAction: UserPoolDefaultAction.ALLOW, + authorizationType: AuthorizationType.USER_POOL, + userPoolConfig: { + userPool, + defaultAction: UserPoolDefaultAction.ALLOW, + }, }, additionalAuthorizationModes: [ { - apiKeyDesc: 'My API Key', - // Can't specify a date because it will inevitably be in the past. - // expires: '2019-02-05T12:00:00Z', + authorizationType: AuthorizationType.API_KEY, }, ], }, diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json index 9057e8c7ae31b..d3044cf9f313c 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json @@ -231,7 +231,7 @@ }, "/", { - "Ref": "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3Bucket5148F39F" + "Ref": "AssetParametersb73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2S3BucketC9264D73" }, "/", { @@ -241,7 +241,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3VersionKey0618C4C3" + "Ref": "AssetParametersb73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2S3VersionKey992034D6" } ] } @@ -254,7 +254,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3VersionKey0618C4C3" + "Ref": "AssetParametersb73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2S3VersionKey992034D6" } ] } @@ -270,11 +270,11 @@ "referencetocdkdynamodbglobal20191121AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3VersionKey8D3D9B9ARef": { "Ref": "AssetParameters012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746S3VersionKey1C286880" }, - "referencetocdkdynamodbglobal20191121AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket6627F4A7Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "referencetocdkdynamodbglobal20191121AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3BucketF12BD931Ref": { + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, - "referencetocdkdynamodbglobal20191121AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyD04C038CRef": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "referencetocdkdynamodbglobal20191121AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey4CB468E4Ref": { + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } } } @@ -293,29 +293,29 @@ "Type": "String", "Description": "Artifact hash for asset \"012c6b101abc4ea1f510921af61a3e08e05f30f84d7b35c40ca4adb1ace60746\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB": { "Type": "String", - "Description": "S3 bucket for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 bucket for asset \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802": { "Type": "String", - "Description": "S3 key for asset version \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 key for asset version \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1ArtifactHash251241BC": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441ArtifactHash88D60D5A": { "Type": "String", - "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "Artifact hash for asset \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3Bucket5148F39F": { + "AssetParametersb73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2S3BucketC9264D73": { "Type": "String", - "Description": "S3 bucket for asset \"ffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947\"" + "Description": "S3 bucket for asset \"b73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2\"" }, - "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947S3VersionKey0618C4C3": { + "AssetParametersb73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2S3VersionKey992034D6": { "Type": "String", - "Description": "S3 key for asset version \"ffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947\"" + "Description": "S3 key for asset version \"b73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2\"" }, - "AssetParametersffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947ArtifactHashBF6B619B": { + "AssetParametersb73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2ArtifactHash580BBBA9": { "Type": "String", - "Description": "Artifact hash for asset \"ffa367e57788c5b58cfac966968712006cbe11cfd301e6c94eb067350f8de947\"" + "Description": "Artifact hash for asset \"b73a9afb7e79ff941de53cc25f57889657aff1b3c3ad024656b62644e6b25ce2\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index b1e52b71a78d5..fb70871bfe8e5 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -513,7 +513,8 @@ cluster.addChart('NginxIngress', { }); ``` -Helm charts will be installed and updated using `helm upgrade --install`. +Helm charts will be installed and updated using `helm upgrade --install`, where a few parameters +are being passed down (such as `repo`, `values`, `version`, `namespace`, `wait`, `timeout`, etc). This means that if the chart is added to CDK with the same release name, it will try to update the chart in the cluster. The chart will exists as CloudFormation resource. diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 59cbc0f3e7aa0..f3c6141f5cd0a 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -1,4 +1,4 @@ -import { Construct, CustomResource, Stack } from '@aws-cdk/core'; +import { Construct, CustomResource, Duration, Stack } from '@aws-cdk/core'; import { Cluster } from './cluster'; /** @@ -47,6 +47,12 @@ export interface HelmChartOptions { * @default - Helm will not wait before marking release as successful */ readonly wait?: boolean; + + /** + * Amount of time to wait for any individual Kubernetes operation. Maximum 15 minutes. + * @default Duration.minutes(5) + */ + readonly timeout?: Duration; } /** @@ -68,7 +74,7 @@ export interface HelmChartProps extends HelmChartOptions { */ export class HelmChart extends Construct { /** - * The CloudFormation reosurce type. + * The CloudFormation resource type. */ public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-HelmChart'; @@ -79,6 +85,11 @@ export class HelmChart extends Construct { const provider = props.cluster._kubectlProvider; + const timeout = props.timeout?.toSeconds(); + if (timeout && timeout > 900) { + throw new Error('Helm chart timeout cannot be higher than 15 minutes.'); + } + new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, resourceType: HelmChart.RESOURCE_TYPE, @@ -89,6 +100,7 @@ export class HelmChart extends Construct { Chart: props.chart, Version: props.version, Wait: props.wait || false, + Timeout: timeout, Values: (props.values ? stack.toJsonString(props.values) : undefined), Namespace: props.namespace || 'default', Repository: props.repository, diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py index 05d0fbdaba614..57ea65a2fa3b7 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/helm/__init__.py @@ -25,6 +25,7 @@ def helm_handler(event, context): chart = props['Chart'] version = props.get('Version', None) wait = props.get('Wait', False) + timeout = props.get('Timeout', None) namespace = props.get('Namespace', None) repository = props.get('Repository', None) values_text = props.get('Values', None) @@ -45,14 +46,14 @@ def helm_handler(event, context): f.write(json.dumps(values, indent=2)) if request_type == 'Create' or request_type == 'Update': - helm('upgrade', release, chart, repository, values_file, namespace, version) + helm('upgrade', release, chart, repository, values_file, namespace, version, wait, timeout) elif request_type == "Delete": try: - helm('uninstall', release, namespace=namespace) + helm('uninstall', release, namespace=namespace, timeout=timeout) except Exception as e: logger.info("delete error: %s" % e) -def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False): +def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None, wait = False, timeout = None): import subprocess cmnd = ['helm', verb, release] @@ -70,6 +71,8 @@ def helm(verb, release, chart = None, repo = None, file = None, namespace = None cmnd.extend(['--namespace', namespace]) if wait: cmnd.append('--wait') + if not timeout is None: + cmnd.extend(['--timeout', timeout]) cmnd.extend(['--kubeconfig', kubeconfig]) retry = 3 diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index 64d77fd5a5eb7..3196329daeaf2 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -2356,7 +2356,7 @@ }, "/", { - "Ref": "AssetParameters18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5S3Bucket7B48152A" + "Ref": "AssetParametersfdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7aS3BucketDC230AE0" }, "/", { @@ -2366,7 +2366,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5S3VersionKey75927692" + "Ref": "AssetParametersfdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7aS3VersionKey2373CDCB" } ] } @@ -2379,7 +2379,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5S3VersionKey75927692" + "Ref": "AssetParametersfdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7aS3VersionKey2373CDCB" } ] } @@ -2395,11 +2395,11 @@ "referencetoawscdkeksclustertestAssetParameters95d3377fefffa0934741552d39e46eef13de3a2094050df1057480e0344b402cS3VersionKey42E00C5ARef": { "Ref": "AssetParameters95d3377fefffa0934741552d39e46eef13de3a2094050df1057480e0344b402cS3VersionKey1DF2734D" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3BucketC7CBF350Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "referencetoawscdkeksclustertestAssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket740C4561Ref": { + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKey7E2BE411Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "referencetoawscdkeksclustertestAssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey3B484C19Ref": { + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } } } @@ -2417,7 +2417,7 @@ }, "/", { - "Ref": "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3Bucket2D824DEF" + "Ref": "AssetParameters07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608S3Bucket721C96A9" }, "/", { @@ -2427,7 +2427,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3VersionKey45D8E8E4" + "Ref": "AssetParameters07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608S3VersionKey52A1C30C" } ] } @@ -2440,7 +2440,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3VersionKey45D8E8E4" + "Ref": "AssetParameters07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608S3VersionKey52A1C30C" } ] } @@ -2450,17 +2450,17 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket6A8A7186Ref": { - "Ref": "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket0C3A00C2" + "referencetoawscdkeksclustertestAssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3Bucket973804E9Ref": { + "Ref": "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3BucketC1533EC8" }, - "referencetoawscdkeksclustertestAssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyA18C5C39Ref": { - "Ref": "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyBED95764" + "referencetoawscdkeksclustertestAssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3VersionKey2F733777Ref": { + "Ref": "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3VersionKey2C834492" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3BucketC7CBF350Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "referencetoawscdkeksclustertestAssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket740C4561Ref": { + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, - "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKey7E2BE411Ref": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "referencetoawscdkeksclustertestAssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey3B484C19Ref": { + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } } } @@ -2743,29 +2743,29 @@ "Type": "String", "Description": "Artifact hash for asset \"95d3377fefffa0934741552d39e46eef13de3a2094050df1057480e0344b402c\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB": { "Type": "String", - "Description": "S3 bucket for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 bucket for asset \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802": { "Type": "String", - "Description": "S3 key for asset version \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 key for asset version \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1ArtifactHash251241BC": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441ArtifactHash88D60D5A": { "Type": "String", - "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "Artifact hash for asset \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3Bucket0C3A00C2": { + "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3BucketC1533EC8": { "Type": "String", - "Description": "S3 bucket for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" + "Description": "S3 bucket for asset \"ca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9\"" }, - "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbS3VersionKeyBED95764": { + "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9S3VersionKey2C834492": { "Type": "String", - "Description": "S3 key for asset version \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" + "Description": "S3 key for asset version \"ca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9\"" }, - "AssetParametersa6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afbArtifactHashBF08C2D7": { + "AssetParametersca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9ArtifactHash51A7CDC3": { "Type": "String", - "Description": "Artifact hash for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" + "Description": "Artifact hash for asset \"ca6f286e0d135e22cfefc133659e2f2fe139a4b46b8eef5b8e197606625c9af9\"" }, "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638": { "Type": "String", @@ -2791,29 +2791,29 @@ "Type": "String", "Description": "Artifact hash for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" }, - "AssetParameters18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5S3Bucket7B48152A": { + "AssetParametersfdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7aS3BucketDC230AE0": { "Type": "String", - "Description": "S3 bucket for asset \"18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5\"" + "Description": "S3 bucket for asset \"fdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7a\"" }, - "AssetParameters18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5S3VersionKey75927692": { + "AssetParametersfdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7aS3VersionKey2373CDCB": { "Type": "String", - "Description": "S3 key for asset version \"18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5\"" + "Description": "S3 key for asset version \"fdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7a\"" }, - "AssetParameters18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5ArtifactHash3F4FE787": { + "AssetParametersfdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7aArtifactHashEF3EDEF7": { "Type": "String", - "Description": "Artifact hash for asset \"18f930a3a3efac8df646c455c3afda1a743c13805600915d02fd4f4be87443f5\"" + "Description": "Artifact hash for asset \"fdca05152ae8546b816681ea8e00f45f12df47f6988add4facaf1f8972995b7a\"" }, - "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3Bucket2D824DEF": { + "AssetParameters07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608S3Bucket721C96A9": { "Type": "String", - "Description": "S3 bucket for asset \"36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbc\"" + "Description": "S3 bucket for asset \"07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608\"" }, - "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3VersionKey45D8E8E4": { + "AssetParameters07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608S3VersionKey52A1C30C": { "Type": "String", - "Description": "S3 key for asset version \"36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbc\"" + "Description": "S3 key for asset version \"07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608\"" }, - "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcArtifactHash83AE269A": { + "AssetParameters07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608ArtifactHash134C23B4": { "Type": "String", - "Description": "Artifact hash for asset \"36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbc\"" + "Description": "Artifact hash for asset \"07ba0071b1bf179d8ece256a71220acd174ccb76b36702c2b9b9ecb8004cb608\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts b/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts index ea7828d18fdee..7fed84a2b6185 100644 --- a/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/test/test.helm-chart.ts @@ -1,4 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; +import { Duration } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as eks from '../lib'; import { testFixtureCluster } from './util'; @@ -70,7 +71,18 @@ export = { new eks.HelmChart(stack, 'MyWaitingChart', { cluster, chart: 'chart' }); // THEN - expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: false})); + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Wait: false })); + test.done(); + }, + 'should timeout only after 10 minutes'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', timeout: Duration.minutes(10) }); + + // THEN + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Timeout: 600 })); test.done(); }, }, diff --git a/packages/@aws-cdk/aws-s3-assets/README.md b/packages/@aws-cdk/aws-s3-assets/README.md index 07d3a88bb0208..3833f750ebe02 100644 --- a/packages/@aws-cdk/aws-s3-assets/README.md +++ b/packages/@aws-cdk/aws-s3-assets/README.md @@ -50,9 +50,6 @@ The following examples grants an IAM group read permissions on an asset: [Example of granting read access to an asset](./test/integ.assets.permissions.lit.ts) -The following example uses custom asset bundling to convert a markdown file to html: -[Example of using asset bundling](./test/integ.assets.bundling.lit.ts) - ## How does it work? When an asset is defined in a construct, a construct metadata entry @@ -73,6 +70,26 @@ the asset store, it is uploaded during deployment. Now, when the toolkit deploys the stack, it will set the relevant CloudFormation Parameters to point to the actual bucket and key for each asset. +## Asset Bundling + +When defining an asset, you can use the `bundling` option to specify a command +to run inside a docker container. The command can read the contents of the asset +source from `/asset-input` and is expected to write files under `/asset-output` +(directories mapped inside the container). The files under `/asset-output` will +be zipped and uploaded to S3 as the asset. + +The following example uses custom asset bundling to convert a markdown file to html: + +[Example of using asset bundling](./test/integ.assets.bundling.lit.ts). + +The bundling docker image (`image`) can either come from a registry (`BundlingDockerImage.fromRegistry`) +or it can be built from a `Dockerfile` located inside your project (`BundlingDockerImage.fromAsset`). + +You can set the `CDK_DOCKER` environment variable in order to provide a custom +docker program to execute. This may sometime be needed when building in +environments where the standard docker cannot be executed (see +https://github.com/aws/aws-cdk/issues/8460 for details). + ## CloudFormation Resource Metadata > NOTE: This section is relevant for authors of AWS Resource Constructs. diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 7010b6cbce6fc..8680356027cc2 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -2,7 +2,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import * as path from 'path'; import { AssetHashType, AssetOptions } from './assets'; -import { BUNDLING_INPUT_DIR, BUNDLING_OUTPUT_DIR, BundlingOptions } from './bundling'; +import { BundlingOptions } from './bundling'; import { Construct, ISynthesisSession } from './construct-compat'; import { FileSystem, FingerprintOptions } from './fs'; @@ -35,6 +35,18 @@ export interface AssetStagingProps extends FingerprintOptions, AssetOptions { * means that only if content was changed, copy will happen. */ export class AssetStaging extends Construct { + /** + * The directory inside the bundling container into which the asset sources will be mounted. + * @experimental + */ + public static readonly BUNDLING_INPUT_DIR = '/asset-input'; + + /** + * The directory inside the bundling container into which the bundled output should be written. + * @experimental + */ + public static readonly BUNDLING_OUTPUT_DIR = '/asset-output'; + /** * The path to the asset (stringinfied token). * @@ -142,11 +154,11 @@ export class AssetStaging extends Construct { const volumes = [ { hostPath: this.sourcePath, - containerPath: BUNDLING_INPUT_DIR, + containerPath: AssetStaging.BUNDLING_INPUT_DIR, }, { hostPath: bundleDir, - containerPath: BUNDLING_OUTPUT_DIR, + containerPath: AssetStaging.BUNDLING_OUTPUT_DIR, }, ...options.volumes ?? [], ]; @@ -156,14 +168,14 @@ export class AssetStaging extends Construct { command: options.command, volumes, environment: options.environment, - workingDirectory: options.workingDirectory ?? BUNDLING_INPUT_DIR, + workingDirectory: options.workingDirectory ?? AssetStaging.BUNDLING_INPUT_DIR, }); } catch (err) { throw new Error(`Failed to run bundling Docker image for asset ${this.node.path}: ${err}`); } if (FileSystem.isEmpty(bundleDir)) { - throw new Error(`Bundling did not produce any output. Check that your container writes content to ${BUNDLING_OUTPUT_DIR}.`); + throw new Error(`Bundling did not produce any output. Check that your container writes content to ${AssetStaging.BUNDLING_OUTPUT_DIR}.`); } return bundleDir; diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index bfff68b40f5cd..269a0a5fc172c 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -1,8 +1,5 @@ import { spawnSync } from 'child_process'; -export const BUNDLING_INPUT_DIR = '/asset-input'; -export const BUNDLING_OUTPUT_DIR = '/asset-output'; - /** * Bundling options * @@ -75,7 +72,7 @@ export class BundlingDockerImage { path, ]; - const docker = exec('docker', dockerArgs); + const docker = dockerExec(dockerArgs); const match = docker.stdout.toString().match(/Successfully built ([a-z0-9]+)/); @@ -110,7 +107,7 @@ export class BundlingDockerImage { ...command, ]; - exec('docker', dockerArgs); + dockerExec(dockerArgs); } } @@ -178,8 +175,9 @@ function flatten(x: string[][]) { return Array.prototype.concat([], ...x); } -function exec(cmd: string, args: string[]) { - const proc = spawnSync(cmd, args); +function dockerExec(args: string[]) { + const prog = process.env.CDK_DOCKER ?? 'docker'; + const proc = spawnSync(prog, args); if (proc.error) { throw proc.error; diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index e18fabc5ecaa1..dce0ac508d71f 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -3,7 +3,7 @@ import { basename, dirname } from 'path'; import { major as nodeMajorVersion } from './node-version'; // list of NPM scopes included in version reporting e.g. @aws-cdk and @aws-solutions-konstruk -const WHITELIST_SCOPES = ['@aws-cdk', '@aws-solutions-konstruk']; +const WHITELIST_SCOPES = ['@aws-cdk', '@aws-solutions-konstruk', '@aws-solutions-constructs']; /** * Returns a list of loaded modules and their versions. diff --git a/packages/@aws-cdk/core/test/docker-stub.sh b/packages/@aws-cdk/core/test/docker-stub.sh new file mode 100755 index 0000000000000..45a78ef881ebd --- /dev/null +++ b/packages/@aws-cdk/core/test/docker-stub.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -euo pipefail + +# stub for the `docker` executable. it is used as CDK_DOCKER when executing unit +# tests in `test.staging.ts` It outputs the command line to +# `/tmp/docker-stub.input` and accepts one of 3 commands that impact it's +# behavior. + +echo "$@" > /tmp/docker-stub.input + +if echo "$@" | grep "DOCKER_STUB_SUCCESS_NO_OUTPUT"; then + exit 0 +fi + +if echo "$@" | grep "DOCKER_STUB_FAIL"; then + echo "A HUGE FAILING DOCKER STUFF" + exit 1 +fi + +if echo "$@" | grep "DOCKER_STUB_SUCCESS"; then + outdir=$(echo "$@" | xargs -n1 | grep "/asset-output" | head -n1 | cut -d":" -f1) + touch ${outdir}/test.txt + exit 0 +fi + +echo "Docker mock only supports one of the following commands: DOCKER_STUB_SUCCESS_NO_OUTPUT,DOCKER_STUB_FAIL,DOCKER_STUB_SUCCESS" +exit 1 diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index 5d5ab521eba59..5c6b48629c4b1 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -4,7 +4,26 @@ import { Test } from 'nodeunit'; import * as path from 'path'; import { App, AssetHashType, AssetStaging, BundlingDockerImage, Stack } from '../lib'; +const STUB_INPUT_FILE = '/tmp/docker-stub.input'; + +enum DockerStubCommand { + SUCCESS = 'DOCKER_STUB_SUCCESS', + FAIL = 'DOCKER_STUB_FAIL', + SUCCESS_NO_OUTPUT = 'DOCKER_STUB_SUCCESS_NO_OUTPUT' +} + +// this is a way to provide a custom "docker" command for staging. +process.env.CDK_DOCKER = `${__dirname}/docker-stub.sh`; + export = { + + 'tearDown'(cb: any) { + if (fs.existsSync(STUB_INPUT_FILE)) { + fs.unlinkSync(STUB_INPUT_FILE); + } + cb(); + }, + 'base case'(test: Test) { // GIVEN const stack = new Stack(); @@ -86,12 +105,13 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), - command: ['touch', '/asset-output/test.txt'], + command: [ DockerStubCommand.SUCCESS ], }, }); // THEN const assembly = app.synth(); + test.deepEqual(readDockerStubInput(), 'run --rm -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS'); test.deepEqual(fs.readdirSync(assembly.directory), [ 'asset.2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00', 'cdk.out', @@ -114,9 +134,12 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), + command: [ DockerStubCommand.SUCCESS_NO_OUTPUT ], }, }), /Bundling did not produce any output/); + test.equal(readDockerStubInput(), + 'run --rm -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS_NO_OUTPUT'); test.done(); }, @@ -131,11 +154,13 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), - command: ['touch', '/asset-output/test.txt'], + command: [ DockerStubCommand.SUCCESS ], }, assetHashType: AssetHashType.BUNDLE, }); + // THEN + test.equal(readDockerStubInput(), 'run --rm -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS'); test.equal(asset.assetHash, '33cbf2cae5432438e0f046bc45ba8c3cef7b6afcf47b59d1c183775c1918fb1f'); test.done(); @@ -153,6 +178,8 @@ export = { assetHash: 'my-custom-hash', }); + // THEN + test.equal(fs.existsSync(STUB_INPUT_FILE), false); test.equal(asset.assetHash, 'my-custom-hash'); test.done(); @@ -169,11 +196,12 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('alpine'), - command: ['touch', '/asset-output/test.txt'], + command: [ DockerStubCommand.SUCCESS ], }, assetHash: 'my-custom-hash', assetHashType: AssetHashType.BUNDLE, }), /Cannot specify `bundle` for `assetHashType`/); + test.equal(readDockerStubInput(), 'run --rm -v /input:/asset-input -v /output:/asset-output -w /asset-input alpine DOCKER_STUB_SUCCESS'); test.done(); }, @@ -189,6 +217,7 @@ export = { sourcePath: directory, assetHashType: AssetHashType.BUNDLE, }), /Cannot use `AssetHashType.BUNDLE` when `bundling` is not specified/); + test.equal(fs.existsSync(STUB_INPUT_FILE), false); test.done(); }, @@ -204,6 +233,7 @@ export = { sourcePath: directory, assetHashType: AssetHashType.CUSTOM, }), /`assetHash` must be specified when `assetHashType` is set to `AssetHashType.CUSTOM`/); + test.equal(fs.existsSync(STUB_INPUT_FILE), false); // "docker" not executed test.done(); }, @@ -219,9 +249,18 @@ export = { sourcePath: directory, bundling: { image: BundlingDockerImage.fromRegistry('this-is-an-invalid-docker-image'), + command: [ DockerStubCommand.FAIL ], }, }), /Failed to run bundling Docker image for asset stack\/Asset/); + test.equal(readDockerStubInput(), 'run --rm -v /input:/asset-input -v /output:/asset-output -w /asset-input this-is-an-invalid-docker-image DOCKER_STUB_FAIL'); test.done(); }, }; + +function readDockerStubInput() { + const out = fs.readFileSync(STUB_INPUT_FILE, 'utf-8').trim(); + return out + .replace(/-v ([^:]+):\/asset-input/, '-v /input:/asset-input') + .replace(/-v ([^:]+):\/asset-output/, '-v /output:/asset-output'); +} diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts index 9b15ec01864f7..682632fd1a40a 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/outbound.ts @@ -1,8 +1,19 @@ /* istanbul ignore file */ // eslint-disable-next-line import/no-extraneous-dependencies import * as AWS from 'aws-sdk'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { ConfigurationOptions } from 'aws-sdk/lib/config'; import * as https from 'https'; +const FRAMEWORK_HANDLER_TIMEOUT = 900000; // 15 minutes + +// In order to honor the overall maximum timeout set for the target process, +// the default 2 minutes from AWS SDK has to be overriden: +// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#httpOptions-property +const awsSdkConfig: ConfigurationOptions = { + httpOptions: { timeout: FRAMEWORK_HANDLER_TIMEOUT }, +}; + async function defaultHttpRequest(options: https.RequestOptions, responseBody: string) { return new Promise((resolve, reject) => { try { @@ -21,7 +32,7 @@ let lambda: AWS.Lambda; async function defaultStartExecution(req: AWS.StepFunctions.StartExecutionInput): Promise { if (!sfn) { - sfn = new AWS.StepFunctions(); + sfn = new AWS.StepFunctions(awsSdkConfig); } return await sfn.startExecution(req).promise(); @@ -29,7 +40,7 @@ async function defaultStartExecution(req: AWS.StepFunctions.StartExecutionInput) async function defaultInvokeFunction(req: AWS.Lambda.InvocationRequest): Promise { if (!lambda) { - lambda = new AWS.Lambda(); + lambda = new AWS.Lambda(awsSdkConfig); } return await lambda.invoke(req).promise(); diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json index 9907ab690dd70..eac6081caf809 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json @@ -200,7 +200,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, "S3Key": { "Fn::Join": [ @@ -213,7 +213,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -226,7 +226,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -579,7 +579,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, "S3Key": { "Fn::Join": [ @@ -592,7 +592,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -605,7 +605,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -721,7 +721,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, "S3Key": { "Fn::Join": [ @@ -734,7 +734,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -747,7 +747,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -860,7 +860,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB" }, "S3Key": { "Fn::Join": [ @@ -873,7 +873,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -886,7 +886,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB" + "Ref": "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802" } ] } @@ -1042,17 +1042,17 @@ "Type": "String", "Description": "Artifact hash for asset \"f465f835a93a93413d7d25f5572670bbb6379304f4cdbad718d4f6a5562d1368\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3Bucket222C40AB": { "Type": "String", - "Description": "S3 bucket for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 bucket for asset \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3VersionKeyF33697EB": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441S3VersionKey70131802": { "Type": "String", - "Description": "S3 key for asset version \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "S3 key for asset version \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, - "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1ArtifactHash251241BC": { + "AssetParameters164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441ArtifactHash88D60D5A": { "Type": "String", - "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" + "Description": "Artifact hash for asset \"164517a9acdab562a370414273e0c6c190700c9be1df02e00bdddf142c7bf441\"" }, "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF": { "Type": "String",