From ce34c0369e0edf8eedb45c71b1767ddc73c7b6a7 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 12 Aug 2020 16:50:39 +0200 Subject: [PATCH 001/422] chore: revert introduction of "Construct.construct" member (#9637) The addition of the new `construct` member leads to problems in the C# code generation, where it would properly be called `Construct.Construct`: a member may not have the same name as the class, because that is the name of the class constructor (see https://github.com/aws/jsii/issues/1880). Our build is currently broken because of this. Revert the renames to unblock the build, giving us the opportunity to tackle this problem afresh next week. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/pipeline-deploy-stack-action.ts | 2 +- .../test/test.pipeline-deploy-stack-action.ts | 2 +- packages/@aws-cdk/assert/lib/synth-utils.ts | 2 +- packages/@aws-cdk/assets/test/test.staging.ts | 2 +- packages/@aws-cdk/aws-amplify/lib/app.ts | 2 +- .../aws-apigateway/lib/authorizers/lambda.ts | 10 +- .../@aws-cdk/aws-apigateway/lib/deployment.ts | 6 +- .../aws-apigateway/lib/domain-name.ts | 2 +- .../aws-apigateway/lib/gateway-response.ts | 2 +- .../aws-apigateway/lib/integrations/lambda.ts | 4 +- .../@aws-cdk/aws-apigateway/lib/method.ts | 2 +- packages/@aws-cdk/aws-apigateway/lib/model.ts | 2 +- .../aws-apigateway/lib/requestvalidator.ts | 2 +- .../@aws-cdk/aws-apigateway/lib/resource.ts | 4 +- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 6 +- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 2 +- .../@aws-cdk/aws-apigateway/lib/vpc-link.ts | 2 +- .../test/integ.restapi-import.lit.ts | 2 +- .../aws-apigateway/test/test.deployment.ts | 2 +- .../aws-apigateway/test/test.method.ts | 8 +- .../aws-apigateway/test/test.model.ts | 4 +- .../test/test.requestvalidator.ts | 4 +- .../aws-apigateway/test/test.restapi.ts | 10 +- .../aws-apigatewayv2/lib/http/api-mapping.ts | 4 +- .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 2 +- .../lib/http/integrations/lambda.ts | 2 +- .../aws-apigatewayv2/lib/http/route.ts | 2 +- .../aws-apigatewayv2/lib/http/stage.ts | 2 +- .../lib/step-scaling-action.ts | 2 +- .../lib/target-tracking-scaling-policy.ts | 2 +- packages/@aws-cdk/aws-appmesh/lib/mesh.ts | 2 +- packages/@aws-cdk/aws-appmesh/lib/route.ts | 2 +- .../@aws-cdk/aws-appmesh/lib/virtual-node.ts | 2 +- .../aws-appmesh/lib/virtual-router.ts | 2 +- .../aws-appmesh/lib/virtual-service.ts | 2 +- .../aws-autoscaling/lib/auto-scaling-group.ts | 12 +- .../aws-autoscaling/lib/lifecycle-hook.ts | 2 +- .../test/auto-scaling-group.test.ts | 14 +- packages/@aws-cdk/aws-backup/lib/plan.ts | 2 +- packages/@aws-cdk/aws-backup/lib/selection.ts | 4 +- packages/@aws-cdk/aws-backup/lib/vault.ts | 2 +- .../aws-batch/lib/compute-environment.ts | 2 +- .../test/compute-environment.test.ts | 4 +- .../aws-certificatemanager/lib/util.ts | 2 +- .../aws-cloudformation/test/test.deps.ts | 2 +- .../test/test.nested-stack.ts | 4 +- .../aws-cloudfront/lib/distribution.ts | 4 +- .../aws-cloudfront/lib/web_distribution.ts | 2 +- .../test/web_distribution.test.ts | 2 +- .../@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts | 4 +- .../aws-cloudwatch/lib/composite-alarm.ts | 2 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 4 +- .../@aws-cdk/aws-codecommit/lib/repository.ts | 2 +- .../lib/lambda/deployment-group.ts | 2 +- .../lib/profiling-group.ts | 2 +- .../lib/cloudformation/pipeline-actions.ts | 4 +- .../lib/codecommit/source-action.ts | 2 +- .../lib/ecr/source-action.ts | 2 +- .../lib/s3/source-action.ts | 4 +- .../test.cloudformation-pipeline-actions.ts | 2 +- .../test/test.pipeline.ts | 4 +- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 14 +- .../aws-codepipeline/test/test.pipeline.ts | 2 +- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 2 +- packages/@aws-cdk/aws-config/lib/rule.ts | 2 +- packages/@aws-cdk/aws-docdb/lib/cluster.ts | 6 +- .../@aws-cdk/aws-docdb/test/cluster.test.ts | 2 +- .../@aws-cdk/aws-docdb/test/instance.test.ts | 2 +- .../lib/aws-dynamodb-global.ts | 2 +- .../test/test.dynamodb.global.ts | 6 +- .../aws-dynamodb/lib/replica-provider.ts | 2 +- packages/@aws-cdk/aws-dynamodb/lib/table.ts | 18 +- .../aws-dynamodb/test/dynamodb.test.ts | 8 +- .../test/integ.dynamodb.ondemand.ts | 2 +- .../aws-dynamodb/test/integ.dynamodb.sse.ts | 2 +- .../aws-dynamodb/test/integ.dynamodb.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/instance.ts | 10 +- .../@aws-cdk/aws-ec2/lib/security-group.ts | 10 +- packages/@aws-cdk/aws-ec2/lib/user-data.ts | 4 +- packages/@aws-cdk/aws-ec2/lib/util.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/volume.ts | 4 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 24 +-- .../@aws-cdk/aws-ec2/test/instance.test.ts | 8 +- .../aws-ec2/test/vpc-endpoint.test.ts | 10 +- packages/@aws-cdk/aws-ec2/test/vpc.test.ts | 12 +- .../aws-ecr-assets/lib/image-asset.ts | 2 +- .../aws-ecr-assets/test/test.image-asset.ts | 6 +- .../application-load-balanced-service-base.ts | 4 +- ...ion-multiple-target-groups-service-base.ts | 14 +- .../network-load-balanced-service-base.ts | 4 +- ...ork-multiple-target-groups-service-base.ts | 12 +- .../lib/base/queue-processing-service-base.ts | 6 +- .../lib/base/scheduled-task-base.ts | 4 +- .../application-load-balanced-ecs-service.ts | 2 +- .../ecs/network-load-balanced-ecs-service.ts | 2 +- .../lib/ecs/scheduled-ecs-task.ts | 2 +- ...plication-load-balanced-fargate-service.ts | 2 +- .../network-load-balanced-fargate-service.ts | 2 +- .../lib/fargate/scheduled-fargate-task.ts | 2 +- .../aws-ecs-patterns/test/ec2/test.l3s-v2.ts | 12 +- .../integ.load-balanced-fargate-service.ts | 10 +- .../test.load-balanced-fargate-service.ts | 15 +- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 4 +- .../aws-ecs/lib/base/task-definition.ts | 2 +- .../aws-ecs/lib/container-definition.ts | 4 +- .../@aws-cdk/aws-ecs/lib/images/repository.ts | 2 +- .../aws-ecs/test/ec2/test.ec2-service.ts | 2 +- .../test/ec2/test.ec2-task-definition.ts | 4 +- .../test/fargate/test.fargate-service.ts | 2 +- .../@aws-cdk/aws-efs/lib/efs-file-system.ts | 2 +- .../aws-efs/test/efs-file-system.test.ts | 2 +- .../@aws-cdk/aws-eks-legacy/lib/cluster.ts | 6 +- .../@aws-cdk/aws-eks-legacy/lib/helm-chart.ts | 4 +- .../aws-eks-legacy/lib/kubectl-layer.ts | 4 +- .../@aws-cdk/aws-eks-legacy/lib/user-data.ts | 2 +- .../aws-eks/lib/cluster-resource-provider.ts | 2 +- .../@aws-cdk/aws-eks/lib/cluster-resource.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 14 +- .../@aws-cdk/aws-eks/lib/fargate-profile.ts | 2 +- packages/@aws-cdk/aws-eks/lib/helm-chart.ts | 2 +- .../@aws-cdk/aws-eks/lib/kubectl-layer.ts | 4 +- .../@aws-cdk/aws-eks/lib/legacy-cluster.ts | 4 +- .../@aws-cdk/aws-eks/lib/service-account.ts | 2 +- packages/@aws-cdk/aws-eks/lib/user-data.ts | 2 +- .../aws-eks/test/integ.eks-cluster.ts | 2 +- .../@aws-cdk/aws-eks/test/test.cluster.ts | 14 +- .../lib/load-balancer.ts | 2 +- .../test/test.loadbalancer.ts | 2 +- .../test/integ.cognito.lit.ts | 2 +- .../lib/alb/application-listener-rule.ts | 2 +- .../lib/alb/application-load-balancer.ts | 6 +- .../lib/alb/application-target-group.ts | 4 +- .../lib/nlb/network-load-balancer.ts | 4 +- .../lib/shared/base-listener.ts | 2 +- .../lib/shared/base-load-balancer.ts | 4 +- .../lib/shared/base-target-group.ts | 2 +- .../test/alb/test.listener.ts | 2 +- .../test/integ.alb.dualstack.ts | 4 +- .../test/integ.nlb.ts | 2 +- .../test/nlb/test.listener.ts | 2 +- .../aws-events-targets/lib/aws-api.ts | 2 +- .../@aws-cdk/aws-events-targets/lib/batch.ts | 2 +- .../aws-events-targets/lib/ecs-task.ts | 4 +- .../@aws-cdk/aws-events-targets/lib/util.ts | 4 +- .../test/codepipeline/pipeline.test.ts | 2 +- packages/@aws-cdk/aws-events/lib/event-bus.ts | 2 +- packages/@aws-cdk/aws-events/lib/rule.ts | 8 +- .../@aws-cdk/aws-events/test/test.rule.ts | 6 +- packages/@aws-cdk/aws-glue/lib/table.ts | 2 +- packages/@aws-cdk/aws-glue/test/table.test.ts | 4 +- packages/@aws-cdk/aws-iam/lib/grant.ts | 4 +- packages/@aws-cdk/aws-iam/lib/role.ts | 2 +- .../@aws-cdk/aws-iam/lib/unknown-principal.ts | 4 +- .../aws-iam/test/escape-hatch.test.ts | 6 +- packages/@aws-cdk/aws-iam/test/policy.test.ts | 6 +- .../aws-iam/test/role.from-role-arn.test.ts | 2 +- packages/@aws-cdk/aws-kinesis/lib/stream.ts | 4 +- packages/@aws-cdk/aws-kms/test/test.key.ts | 6 +- .../aws-lambda-event-sources/lib/api.ts | 4 +- .../aws-lambda-event-sources/lib/dynamodb.ts | 4 +- .../aws-lambda-event-sources/lib/kinesis.ts | 2 +- .../aws-lambda-event-sources/lib/sqs.ts | 2 +- .../@aws-cdk/aws-lambda/lib/function-base.ts | 9 +- .../@aws-cdk/aws-lambda/lib/function-hash.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 10 +- .../aws-lambda/lib/singleton-lambda.ts | 6 +- .../@aws-cdk/aws-lambda/test/test.code.ts | 2 +- .../@aws-cdk/aws-lambda/test/test.layers.ts | 2 +- .../aws-logs-destinations/lib/kinesis.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 6 +- packages/@aws-cdk/aws-rds/lib/instance.ts | 6 +- packages/@aws-cdk/aws-rds/lib/proxy.ts | 4 +- .../@aws-cdk/aws-rds/test/test.cluster.ts | 2 +- packages/@aws-cdk/aws-redshift/lib/cluster.ts | 2 +- .../aws-redshift/test/cluster.test.ts | 2 +- .../lib/cloudfront-target.ts | 2 +- .../lib/interface-vpc-endpoint-target.ts | 2 +- .../test/test.hosted-zone-provider.ts | 4 +- .../@aws-cdk/aws-route53/test/test.util.ts | 4 - packages/@aws-cdk/aws-s3-assets/lib/asset.ts | 2 +- .../@aws-cdk/aws-s3-assets/test/asset.test.ts | 6 +- .../@aws-cdk/aws-s3-deployment/lib/source.ts | 2 +- .../aws-s3-notifications/lib/lambda.ts | 2 +- .../test/notifications.test.ts | 2 +- packages/@aws-cdk/aws-s3/lib/bucket.ts | 4 +- .../notifications-resource-handler.ts | 4 +- .../notifications-resource.ts | 2 +- packages/@aws-cdk/aws-s3/test/test.aspect.ts | 6 +- .../aws-secretsmanager/lib/secret-rotation.ts | 2 +- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 2 +- .../lib/alias-target-instance.ts | 2 +- .../aws-servicediscovery/lib/instance.ts | 2 +- .../@aws-cdk/aws-ses-actions/lib/lambda.ts | 4 +- packages/@aws-cdk/aws-ses-actions/lib/s3.ts | 6 +- .../aws-sns-subscriptions/lib/lambda.ts | 4 +- .../@aws-cdk/aws-sns-subscriptions/lib/sqs.ts | 2 +- packages/@aws-cdk/aws-sns/lib/topic-base.ts | 8 +- packages/@aws-cdk/aws-sqs/lib/queue.ts | 2 +- packages/@aws-cdk/aws-ssm/lib/parameter.ts | 4 +- .../aws-ssm/test/integ.parameter-arns.ts | 2 +- .../lib/ecs/run-ecs-task-base.ts | 2 +- .../lib/ecs/run-task.ts | 2 +- .../aws-stepfunctions/lib/activity.ts | 2 +- .../lib/state-machine-fragment.ts | 2 +- .../aws-stepfunctions/lib/state-machine.ts | 2 +- .../aws-stepfunctions/lib/states/state.ts | 4 +- .../test/valid-templates.test.ts | 4 +- packages/@aws-cdk/core/README.md | 10 +- packages/@aws-cdk/core/lib/annotations.ts | 2 +- packages/@aws-cdk/core/lib/app.ts | 8 +- packages/@aws-cdk/core/lib/aspect.ts | 4 +- packages/@aws-cdk/core/lib/asset-staging.ts | 8 +- packages/@aws-cdk/core/lib/cfn-element.ts | 6 +- packages/@aws-cdk/core/lib/cfn-output.ts | 2 +- packages/@aws-cdk/core/lib/cfn-parse.ts | 2 +- packages/@aws-cdk/core/lib/cfn-resource.ts | 10 +- .../@aws-cdk/core/lib/construct-compat.ts | 25 +-- .../@aws-cdk/core/lib/context-provider.ts | 2 +- .../custom-resource-provider.ts | 2 +- packages/@aws-cdk/core/lib/deps.ts | 6 +- packages/@aws-cdk/core/lib/nested-stack.ts | 4 +- .../core/lib/private/cfn-reference.ts | 7 +- .../lib/private/physical-name-generator.ts | 6 +- .../@aws-cdk/core/lib/private/prepare-app.ts | 6 +- packages/@aws-cdk/core/lib/private/refs.ts | 28 +-- .../@aws-cdk/core/lib/private/synthesis.ts | 10 +- .../core/lib/private/tree-metadata.ts | 10 +- packages/@aws-cdk/core/lib/resource.ts | 2 +- .../core/lib/stack-synthesizers/_shared.ts | 12 +- .../stack-synthesizers/default-synthesizer.ts | 2 +- .../core/lib/stack-synthesizers/legacy.ts | 8 +- packages/@aws-cdk/core/lib/stack.ts | 32 +-- packages/@aws-cdk/core/lib/stage.ts | 10 +- .../test.custom-resource-provider.ts | 2 +- .../core/test/private/test.tree-metadata.ts | 4 +- packages/@aws-cdk/core/test/test.app.ts | 28 +-- packages/@aws-cdk/core/test/test.aspect.ts | 22 +- packages/@aws-cdk/core/test/test.assets.ts | 10 +- .../@aws-cdk/core/test/test.cfn-resource.ts | 2 +- packages/@aws-cdk/core/test/test.construct.ts | 202 +++++++++--------- packages/@aws-cdk/core/test/test.context.ts | 8 +- .../@aws-cdk/core/test/test.logical-id.ts | 6 +- packages/@aws-cdk/core/test/test.resource.ts | 14 +- packages/@aws-cdk/core/test/test.stack.ts | 10 +- packages/@aws-cdk/core/test/test.stage.ts | 6 +- packages/@aws-cdk/core/test/test.staging.ts | 2 +- packages/@aws-cdk/core/test/test.synthesis.ts | 2 +- packages/@aws-cdk/core/test/test.util.ts | 4 +- .../lib/provider-framework/provider.ts | 2 +- .../waiter-state-machine.ts | 2 +- .../integ.aws-custom-resource.ts | 2 +- .../test/provider-framework/integ.provider.ts | 2 +- .../integration-test-fixtures/s3-assert.ts | 2 +- .../integration-test-fixtures/s3-file.ts | 2 +- .../lib/actions/deploy-cdk-stack-action.ts | 4 +- packages/@aws-cdk/pipelines/lib/pipeline.ts | 6 +- .../lib/private/construct-internals.ts | 4 +- packages/@aws-cdk/pipelines/lib/stage.ts | 2 +- .../test/cross-environment-infra.test.ts | 2 +- packages/decdk/lib/declarative-stack.ts | 8 +- 260 files changed, 701 insertions(+), 709 deletions(-) diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index 7f22106e844f7..03685cfc9c413 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -115,7 +115,7 @@ export class PipelineDeployStackAction implements codepipeline.IAction { constructor(props: PipelineDeployStackActionProps) { this.stack = props.stack; - const assets = this.stack.construct.metadata.filter(md => md.type === cxschema.ArtifactMetadataEntryType.ASSET); + const assets = this.stack.node.metadata.filter(md => md.type === cxschema.ArtifactMetadataEntryType.ASSET); if (assets.length > 0) { // FIXME: Implement the necessary actions to publish assets throw new Error(`Cannot deploy the stack ${this.stack.stackName} because it references ${assets.length} asset(s)`); diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index 641e12331fdbc..918279b480b30 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -390,7 +390,7 @@ export = nodeunit.testCase({ const deployedStack = new cdk.Stack(app, 'DeployedStack'); for (let i = 0 ; i < assetCount ; i++) { - deployedStack.construct.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, {}); + deployedStack.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, {}); } test.throws(() => { diff --git a/packages/@aws-cdk/assert/lib/synth-utils.ts b/packages/@aws-cdk/assert/lib/synth-utils.ts index dfd73b371dd65..bb8d9a437afd9 100644 --- a/packages/@aws-cdk/assert/lib/synth-utils.ts +++ b/packages/@aws-cdk/assert/lib/synth-utils.ts @@ -65,7 +65,7 @@ export class SynthUtils { * Synthesizes the app in which a stack resides and returns the cloud assembly object. */ function synthesizeApp(stack: core.Stack, options: core.SynthesisOptions) { - const root = stack.construct.root; + const root = stack.node.root; if (!core.Stage.isStage(root)) { throw new Error('unexpected: all stacks must be part of a Stage or an App'); } diff --git a/packages/@aws-cdk/assets/test/test.staging.ts b/packages/@aws-cdk/assets/test/test.staging.ts index 2b155c75108af..a6841284e8731 100644 --- a/packages/@aws-cdk/assets/test/test.staging.ts +++ b/packages/@aws-cdk/assets/test/test.staging.ts @@ -23,7 +23,7 @@ export = { 'staging can be disabled through context'(test: Test) { // GIVEN const stack = new Stack(); - stack.construct.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); const sourcePath = path.join(__dirname, 'fs', 'fixtures', 'test1'); // WHEN diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index e18917afae9b2..1d41c0e07ede3 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -221,7 +221,7 @@ export class App extends Resource implements IApp, iam.IGrantable { description: props.description, environmentVariables: Lazy.anyValue({ produce: () => renderEnvironmentVariables(this.environmentVariables) }, { omitEmptyArray: true }), iamServiceRole: role.roleArn, - name: props.appName || this.construct.id, + name: props.appName || this.node.id, oauthToken: sourceCodeProviderOptions?.oauthToken?.toString(), repository: sourceCodeProviderOptions?.repository, }); diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts index 002a1e71aa157..f79d675af1e7f 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts @@ -12,7 +12,7 @@ export interface LambdaAuthorizerProps { /** * An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer. * - * @default this.construct.uniqueId + * @default this.node.uniqueId */ readonly authorizerName?: string; @@ -96,7 +96,7 @@ abstract class LambdaAuthorizer extends Authorizer implements IAuthorizer { */ protected setupPermissions() { if (!this.role) { - this.handler.addPermission(`${this.construct.uniqueId}:Permissions`, { + this.handler.addPermission(`${this.node.uniqueId}:Permissions`, { principal: new iam.ServicePrincipal('apigateway.amazonaws.com'), sourceArn: this.authorizerArn, }); @@ -120,7 +120,7 @@ abstract class LambdaAuthorizer extends Authorizer implements IAuthorizer { return Lazy.stringValue({ produce: () => { if (!this.restApiId) { - throw new Error(`Authorizer (${this.construct.path}) must be attached to a RestApi`); + throw new Error(`Authorizer (${this.node.path}) must be attached to a RestApi`); } return this.restApiId; }, @@ -167,7 +167,7 @@ export class TokenAuthorizer extends LambdaAuthorizer { const restApiId = this.lazyRestApiId(); const resource = new CfnAuthorizer(this, 'Resource', { - name: props.authorizerName ?? this.construct.uniqueId, + name: props.authorizerName ?? this.node.uniqueId, restApiId, type: 'TOKEN', authorizerUri: lambdaAuthorizerArn(props.handler), @@ -229,7 +229,7 @@ export class RequestAuthorizer extends LambdaAuthorizer { const restApiId = this.lazyRestApiId(); const resource = new CfnAuthorizer(this, 'Resource', { - name: props.authorizerName ?? this.construct.uniqueId, + name: props.authorizerName ?? this.node.uniqueId, restApiId, type: 'REQUEST', authorizerUri: lambdaAuthorizerArn(props.handler), diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index 799d6f9ccee72..404ba1855bd68 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -118,7 +118,7 @@ export class Deployment extends Resource { // and the `AWS::Lambda::Permission` resources (children under Method), // causing cyclic dependency errors. Hence, falling back to declaring // dependencies between the underlying CfnResources. - this.construct.addDependency(method.construct.defaultChild as CfnResource); + this.node.addDependency(method.node.defaultChild as CfnResource); } } @@ -150,7 +150,7 @@ class LatestDeploymentResource extends CfnDeployment { public addToLogicalId(data: unknown) { // if the construct is locked, it means we are already synthesizing and then // we can't modify the hash because we might have already calculated it. - if (this.construct.locked) { + if (this.node.locked) { throw new Error('Cannot modify the logical ID when the construct is locked'); } @@ -163,7 +163,7 @@ class LatestDeploymentResource extends CfnDeployment { if (this.api instanceof RestApi || this.api instanceof SpecRestApi) { // Ignore IRestApi that are imported // Add CfnRestApi to the logical id so a new deployment is triggered when any of its properties change. - const cfnRestApiCF = (this.api.construct.defaultChild as any)._toCloudFormation(); + const cfnRestApiCF = (this.api.node.defaultChild as any)._toCloudFormation(); hash.push(this.stack.resolve(cfnRestApiCF)); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts index 5c85e3415354f..f44ebf953dcac 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts @@ -137,7 +137,7 @@ export class DomainName extends Resource implements IDomainName { */ public addBasePathMapping(targetApi: IRestApi, options: BasePathMappingOptions = { }) { const basePath = options.basePath || '/'; - const id = `Map:${basePath}=>${targetApi.construct.uniqueId}`; + const id = `Map:${basePath}=>${targetApi.node.uniqueId}`; return new BasePathMapping(this, id, { domainName: this, restApi: targetApi, diff --git a/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts b/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts index 35d02d7c15e27..62957167fa881 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/gateway-response.ts @@ -65,7 +65,7 @@ export class GatewayResponse extends Resource implements IGatewayResponse { statusCode: props.statusCode, }); - this.construct.defaultChild = resource; + this.node.defaultChild = resource; } private buildResponseParameters(responseHeaders?: { [key: string]: string }): { [key: string]: string } | undefined { diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 7d6ec4c2530ac..c6a8ec04863c7 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -56,7 +56,7 @@ export class LambdaIntegration extends AwsIntegration { const bindResult = super.bind(method); const principal = new iam.ServicePrincipal('apigateway.amazonaws.com'); - const desc = `${method.api.construct.uniqueId}.${method.httpMethod}.${method.resource.path.replace(/\//g, '.')}`; + const desc = `${method.api.node.uniqueId}.${method.httpMethod}.${method.resource.path.replace(/\//g, '.')}`; this.handler.addPermission(`ApiPermission.${desc}`, { principal, @@ -78,7 +78,7 @@ export class LambdaIntegration extends AwsIntegration { if (this.handler instanceof lambda.Function) { // if not imported, extract the name from the CFN layer to reach // the literal value if it is given (rather than a token) - functionName = (this.handler.construct.defaultChild as lambda.CfnFunction).functionName; + functionName = (this.handler.node.defaultChild as lambda.CfnFunction).functionName; } else { // imported, just take the function name. functionName = this.handler.functionName; diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 586d0f2258a66..172eb77cd1877 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -221,7 +221,7 @@ export class Method extends Resource { const deployment = props.resource.api.latestDeployment; if (deployment) { - deployment.construct.addDependency(resource); + deployment.node.addDependency(resource); deployment.addToLogicalId({ method: { ...methodProps, diff --git a/packages/@aws-cdk/aws-apigateway/lib/model.ts b/packages/@aws-cdk/aws-apigateway/lib/model.ts index df0aa0b5d9718..088dee9b98750 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/model.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/model.ts @@ -178,7 +178,7 @@ export class Model extends Resource implements IModel { const deployment = (props.restApi instanceof RestApi) ? props.restApi.latestDeployment : undefined; if (deployment) { - deployment.construct.addDependency(resource); + deployment.node.addDependency(resource); deployment.addToLogicalId({ model: modelProps }); } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts b/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts index b31155bf5c941..ce56903f79a0d 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/requestvalidator.ts @@ -81,7 +81,7 @@ export class RequestValidator extends Resource implements IRequestValidator { const deployment = (props.restApi instanceof RestApi) ? props.restApi.latestDeployment : undefined; if (deployment) { - deployment.construct.addDependency(resource); + deployment.node.addDependency(resource); deployment.addToLogicalId({ validator: validatorProps }); } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 003c4b0575ba5..102a17a5cdc27 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -410,7 +410,7 @@ export class Resource extends ResourceBase { const deployment = props.parent.api.latestDeployment; if (deployment) { - deployment.construct.addDependency(resource); + deployment.node.addDependency(resource); deployment.addToLogicalId({ resource: resourceProps }); } @@ -488,7 +488,7 @@ export class ProxyResource extends Resource { // the root so that empty paths are proxied as well. if (this.parentResource && this.parentResource.path === '/') { // skip if the root resource already has this method defined - if (!(this.parentResource.construct.tryFindChild(httpMethod) instanceof Method)) { + if (!(this.parentResource.node.tryFindChild(httpMethod) instanceof Method)) { this.parentResource.addMethod(httpMethod, integration, options); } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 606bf536fbbf4..f027a2f7424be 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -401,7 +401,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { cloudWatchRoleArn: role.roleArn, }); - resource.construct.addDependency(apiResource); + resource.node.addDependency(apiResource); } protected configureDeployment(props: RestApiOptions) { @@ -492,7 +492,7 @@ export class SpecRestApi extends RestApiBase { endpointConfiguration: this._configureEndpoints(props), parameters: props.parameters, }); - this.construct.defaultChild = resource; + this.node.defaultChild = resource; this.restApiId = resource.ref; this.restApiRootResourceId = resource.attrRootResourceId; this.root = new RootResource(this, {}, this.restApiRootResourceId); @@ -597,7 +597,7 @@ export class RestApi extends RestApiBase { cloneFrom: props.cloneFrom ? props.cloneFrom.restApiId : undefined, parameters: props.parameters, }); - this.construct.defaultChild = resource; + this.node.defaultChild = resource; this.restApiId = resource.ref; const cloudWatchRole = props.cloudWatchRole !== undefined ? props.cloudWatchRole : true; diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index c25c744fa65ec..c6029c5840e4c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -181,7 +181,7 @@ export class UsagePlan extends Resource { const prefix = 'UsagePlanKeyResource'; // Postfixing apikey id only from the 2nd child, to keep physicalIds of UsagePlanKey for existing CDK apps unmodifed. - const id = this.construct.tryFindChild(prefix) ? `${prefix}:${apiKey.construct.uniqueId}` : prefix; + const id = this.node.tryFindChild(prefix) ? `${prefix}:${apiKey.node.uniqueId}` : prefix; new CfnUsagePlanKey(this, id, { keyId: apiKey.keyId, diff --git a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts index 2cc66b0f8885c..81f6f843b97df 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts @@ -65,7 +65,7 @@ export class VpcLink extends Resource implements IVpcLink { constructor(scope: Construct, id: string, props: VpcLinkProps = {}) { super(scope, id, { physicalName: props.vpcLinkName || - Lazy.stringValue({ produce: () => this.construct.uniqueId }), + Lazy.stringValue({ produce: () => this.node.uniqueId }), }); const cfnResource = new CfnVpcLink(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts index 60017e092f340..bea2be6c5b05f 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi-import.lit.ts @@ -116,7 +116,7 @@ class DeployStack extends NestedStack { const deployment = new Deployment(this, 'Deployment', { api: RestApi.fromRestApiId(this, 'RestApi', props.restApiId), }); - (props.methods ?? []).forEach((method) => deployment.construct.addDependency(method)); + (props.methods ?? []).forEach((method) => deployment.node.addDependency(method)); new Stage(this, 'Stage', { deployment }); } } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts index ebacaac1973c2..0f5ce4a37732d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts @@ -178,7 +178,7 @@ export = { const dep = new CfnResource(stack, 'MyResource', { type: 'foo' }); // WHEN - deployment.construct.addDependency(dep); + deployment.node.addDependency(dep); expect(stack).to(haveResource('AWS::ApiGateway::Deployment', { DependsOn: [ diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index b4a94b20bf44c..85111b4636882 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -474,7 +474,7 @@ export = { expect(stack).to(haveResource('AWS::ApiGateway::Method', { HttpMethod: 'GET', RequestModels: { - 'application/json': { Ref: stack.getLogicalId(model.construct.findChild('Resource') as cdk.CfnElement) }, + 'application/json': { Ref: stack.getLogicalId(model.node.findChild('Resource') as cdk.CfnElement) }, }, })); @@ -539,7 +539,7 @@ export = { ResponseModels: { 'application/json': 'Empty', 'text/plain': 'Error', - 'text/html': { Ref: stack.getLogicalId(htmlModel.construct.findChild('Resource') as cdk.CfnElement) }, + 'text/html': { Ref: stack.getLogicalId(htmlModel.node.findChild('Resource') as cdk.CfnElement) }, }, }, ], @@ -568,10 +568,10 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Method', { - RequestValidatorId: { Ref: stack.getLogicalId(validator.construct.findChild('Resource') as cdk.CfnElement) }, + RequestValidatorId: { Ref: stack.getLogicalId(validator.node.findChild('Resource') as cdk.CfnElement) }, })); expect(stack).to(haveResource('AWS::ApiGateway::RequestValidator', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as cdk.CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as cdk.CfnElement) }, ValidateRequestBody: true, ValidateRequestParameters: false, })); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.model.ts b/packages/@aws-cdk/aws-apigateway/test/test.model.ts index a6f4e5fa393fd..9f0907b1e66f2 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.model.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.model.ts @@ -26,7 +26,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Model', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as cdk.CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as cdk.CfnElement) }, Schema: { $schema: 'http://json-schema.org/draft-04/schema#', title: 'test', @@ -61,7 +61,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Model', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as cdk.CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as cdk.CfnElement) }, Schema: { $schema: 'http://json-schema.org/draft-04/schema#', title: 'test', diff --git a/packages/@aws-cdk/aws-apigateway/test/test.requestvalidator.ts b/packages/@aws-cdk/aws-apigateway/test/test.requestvalidator.ts index f7b9e89d65632..b264f85c0733c 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.requestvalidator.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.requestvalidator.ts @@ -22,7 +22,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::RequestValidator', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as cdk.CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as cdk.CfnElement) }, ValidateRequestBody: true, ValidateRequestParameters: false, })); @@ -49,7 +49,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::RequestValidator', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as cdk.CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as cdk.CfnElement) }, Name: 'my-model', ValidateRequestBody: false, ValidateRequestParameters: true, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index 4ae50a9910065..9736dc2e188b5 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -103,7 +103,7 @@ export = { 'defaultChild is set correctly'(test: Test) { const stack = new Stack(); const api = new apigw.RestApi(stack, 'my-api'); - test.ok(api.construct.defaultChild instanceof apigw.CfnRestApi); + test.ok(api.node.defaultChild instanceof apigw.CfnRestApi); test.done(); }, @@ -570,7 +570,7 @@ export = { const resource = new CfnResource(stack, 'DependsOnRestApi', { type: 'My::Resource' }); // WHEN - resource.construct.addDependency(api); + resource.node.addDependency(api); // THEN expect(stack).to(haveResource('My::Resource', { @@ -713,7 +713,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Model', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as CfnElement) }, Schema: { $schema: 'http://json-schema.org/draft-04/schema#', title: 'test', @@ -745,14 +745,14 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::RequestValidator', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as CfnElement) }, Name: 'Parameters', ValidateRequestBody: false, ValidateRequestParameters: true, })); expect(stack).to(haveResource('AWS::ApiGateway::RequestValidator', { - RestApiId: { Ref: stack.getLogicalId(api.construct.findChild('Resource') as CfnElement) }, + RestApiId: { Ref: stack.getLogicalId(api.node.findChild('Resource') as CfnElement) }, Name: 'Body', ValidateRequestBody: true, ValidateRequestParameters: false, diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts index cc35eeb466ace..17461f258b288 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts @@ -93,12 +93,12 @@ export class HttpApiMapping extends Resource implements IApiMapping { // ensure the dependency on the provided stage if (props.stage) { - this.construct.addDependency(props.stage); + this.node.addDependency(props.stage); } // if stage not specified, we ensure the default stage is ready before we create the api mapping if (!props.stage?.stageName && props.api.defaultStage) { - this.construct.addDependency(props.api.defaultStage!); + this.node.addDependency(props.api.defaultStage!); } this.apiMappingId = resource.ref; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 56931abf0b37e..d868b25f9eea1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -191,7 +191,7 @@ export class HttpApi extends Resource implements IHttpApi { // to ensure the domain is ready before creating the default stage if(props?.defaultDomainMapping) { - this.defaultStage.construct.addDependency(props.defaultDomainMapping.domainName); + this.defaultStage.node.addDependency(props.defaultDomainMapping.domainName); } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts index fe656e0a03947..30973a10b2ca0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts @@ -30,7 +30,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration { public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { const route = options.route; - this.props.handler.addPermission(`${route.construct.uniqueId}-Permission`, { + this.props.handler.addPermission(`${route.node.uniqueId}-Permission`, { scope: options.scope, principal: new ServicePrincipal('apigateway.amazonaws.com'), sourceArn: Stack.of(route).formatArn({ diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 09be9668a1267..2f65902a6aaee 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -124,7 +124,7 @@ export class HttpRoute extends Resource implements IHttpRoute { route: this, scope: this, }); - integration = new HttpIntegration(this, `${this.construct.id}-Integration`, { + integration = new HttpIntegration(this, `${this.node.id}-Integration`, { httpApi: props.httpApi, integrationType: config.type, integrationUri: config.uri, diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts index 9a171681b0715..a9e7de3ce9af2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -103,7 +103,7 @@ export class HttpStage extends Resource implements IStage { apiMappingKey: props.domainMapping.mappingKey, }); // ensure the dependency - this.construct.addDependency(props.domainMapping.domainName); + this.node.addDependency(props.domainMapping.domainName); } } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts index 92f639a0c45ad..fac49b523e8ea 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -81,7 +81,7 @@ export class StepScalingAction extends cdk.Construct { // properties, or the ScalingTargetId property, but not both. // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalingpolicy.html const resource = new CfnScalingPolicy(this, 'Resource', { - policyName: props.policyName || this.construct.uniqueId, + policyName: props.policyName || this.node.uniqueId, policyType: 'StepScaling', scalingTargetId: props.scalingTarget.scalableTargetId, stepScalingPolicyConfiguration: { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index 498de4e70b7ef..8a314f0b1ac22 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -132,7 +132,7 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { super(scope, id); const resource = new CfnScalingPolicy(this, 'Resource', { - policyName: props.policyName || this.construct.uniqueId, + policyName: props.policyName || this.node.uniqueId, policyType: 'TargetTrackingScaling', scalingTargetId: props.scalingTarget.scalableTargetId, targetTrackingScalingPolicyConfiguration: { diff --git a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts index 6479296b4ee33..8e39cb4657d1e 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts @@ -170,7 +170,7 @@ export class Mesh extends MeshBase { constructor(scope: cdk.Construct, id: string, props: MeshProps = {}) { super(scope, id, { - physicalName: props.meshName || cdk.Lazy.stringValue({ produce: () => this.construct.uniqueId }), + physicalName: props.meshName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); const mesh = new CfnMesh(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-appmesh/lib/route.ts b/packages/@aws-cdk/aws-appmesh/lib/route.ts index 01433c1d7f55f..8534ad70dcf14 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route.ts @@ -145,7 +145,7 @@ export class Route extends cdk.Resource implements IRoute { constructor(scope: cdk.Construct, id: string, props: RouteProps) { super(scope, id, { - physicalName: props.routeName || cdk.Lazy.stringValue({ produce: () => this.construct.uniqueId }), + physicalName: props.routeName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); this.virtualRouter = props.virtualRouter; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index 1b406878dc896..f7a2548a48e7b 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -245,7 +245,7 @@ export class VirtualNode extends VirtualNodeBase { constructor(scope: cdk.Construct, id: string, props: VirtualNodeProps) { super(scope, id, { - physicalName: props.virtualNodeName || cdk.Lazy.stringValue({ produce: () => this.construct.uniqueId }), + physicalName: props.virtualNodeName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); this.mesh = props.mesh; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts index 0249ace5e06f5..3403b47378c5c 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts @@ -145,7 +145,7 @@ export class VirtualRouter extends VirtualRouterBase { constructor(scope: cdk.Construct, id: string, props: VirtualRouterProps) { super(scope, id, { - physicalName: props.virtualRouterName || cdk.Lazy.stringValue({ produce: () => this.construct.uniqueId }), + physicalName: props.virtualRouterName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); this.mesh = props.mesh; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index 1ef3ef41eb788..9cc678ec266a9 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -106,7 +106,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { constructor(scope: cdk.Construct, id: string, props: VirtualServiceProps) { super(scope, id, { - physicalName: props.virtualServiceName || cdk.Lazy.stringValue({ produce: () => this.construct.uniqueId }), + physicalName: props.virtualServiceName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), }); if (props.virtualNode && props.virtualRouter) { diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index e19f7db7fd83a..07aa86ff45306 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -471,7 +471,7 @@ abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGrou ...props, }); - policy.construct.addDependency(this.albTargetGroup.loadBalancerAttached); + policy.node.addDependency(this.albTargetGroup.loadBalancerAttached); return policy; } @@ -595,7 +595,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }); this.connections = new ec2.Connections({ securityGroups: [this.securityGroup] }); this.securityGroups.push(this.securityGroup); - this.construct.applyAspect(new Tag(NAME_TAG, this.construct.path)); + this.node.applyAspect(new Tag(NAME_TAG, this.node.path)); this.role = props.role || new iam.Role(this, 'InstanceRole', { roleName: PhysicalName.GENERATE_IF_NEEDED, @@ -632,7 +632,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements synthesizeBlockDeviceMappings(this, props.blockDevices) : undefined), }); - launchConfig.construct.addDependency(this.role); + launchConfig.node.addDependency(this.role); // desiredCapacity just reflects what the user has supplied. const desiredCapacity = props.desiredCapacity; @@ -659,7 +659,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }); if (desiredCapacity !== undefined) { - this.construct.addWarning('desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215'); + this.node.addWarning('desiredCapacity has been configured. Be aware this will reset the size of your AutoScalingGroup on every deployment. See https://github.com/aws/aws-cdk/issues/5215'); } this.maxInstanceLifetime = props.maxInstanceLifetime; @@ -715,7 +715,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements resource: 'autoScalingGroup:*:autoScalingGroupName', resourceName: this.autoScalingGroupName, }); - this.construct.defaultChild = this.autoScalingGroup; + this.node.defaultChild = this.autoScalingGroup; this.applyUpdatePolicies(props); @@ -1259,7 +1259,7 @@ function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: Block throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1'); } } else if (volumeType !== EbsDeviceVolumeType.IO1) { - construct.construct.addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + construct.node.addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); } } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts index 841e5bc5b46c8..9c6f1d7f15322 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts @@ -116,7 +116,7 @@ export class LifecycleHook extends Resource implements ILifecycleHook { // A LifecycleHook resource is going to do a permissions test upon creation, // so we have to make sure the role has full permissions before creating the // lifecycle hook. - resource.construct.addDependency(this.role); + resource.node.addDependency(this.role); this.lifecycleHookName = resource.ref; } diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index d992fd8ba602f..52242e44bda29 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -511,8 +511,8 @@ nodeunitShim({ pauseTime: cdk.Duration.seconds(345), }, }); - asg.construct.applyAspect(new cdk.Tag('superfood', 'acai')); - asg.construct.applyAspect(new cdk.Tag('notsuper', 'caramel', { applyToLaunchedInstances: false })); + asg.node.applyAspect(new cdk.Tag('superfood', 'acai')); + asg.node.applyAspect(new cdk.Tag('notsuper', 'caramel', { applyToLaunchedInstances: false })); // THEN expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { @@ -713,7 +713,7 @@ nodeunitShim({ }); // THEN - test.ok(asg.construct.defaultChild instanceof autoscaling.CfnAutoScalingGroup); + test.ok(asg.node.defaultChild instanceof autoscaling.CfnAutoScalingGroup); test.done(); }, @@ -958,8 +958,8 @@ nodeunitShim({ }); // THEN - test.deepEqual(asg.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); - test.deepEqual(asg.construct.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + test.deepEqual(asg.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); + test.deepEqual(asg.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); }, @@ -985,8 +985,8 @@ nodeunitShim({ }); // THEN - test.deepEqual(asg.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); - test.deepEqual(asg.construct.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + test.deepEqual(asg.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); + test.deepEqual(asg.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); }, diff --git a/packages/@aws-cdk/aws-backup/lib/plan.ts b/packages/@aws-cdk/aws-backup/lib/plan.ts index c9f9cc6d65c70..99301fde7bde9 100644 --- a/packages/@aws-cdk/aws-backup/lib/plan.ts +++ b/packages/@aws-cdk/aws-backup/lib/plan.ts @@ -161,7 +161,7 @@ export class BackupPlan extends Resource implements IBackupPlan { deleteAfterDays: rule.props.deleteAfter?.toDays(), moveToColdStorageAfterDays: rule.props.moveToColdStorageAfter?.toDays(), }, - ruleName: rule.props.ruleName ?? `${this.construct.id}Rule${this.rules.length}`, + ruleName: rule.props.ruleName ?? `${this.node.id}Rule${this.rules.length}`, scheduleExpression: rule.props.scheduleExpression?.expressionString, startWindowMinutes: rule.props.startWindow?.toMinutes(), targetBackupVault: vault.backupVaultName, diff --git a/packages/@aws-cdk/aws-backup/lib/selection.ts b/packages/@aws-cdk/aws-backup/lib/selection.ts index 9e6d6bd846565..4e1ba54b472cb 100644 --- a/packages/@aws-cdk/aws-backup/lib/selection.ts +++ b/packages/@aws-cdk/aws-backup/lib/selection.ts @@ -94,7 +94,7 @@ export class BackupSelection extends Resource implements iam.IGrantable { backupPlanId: props.backupPlan.backupPlanId, backupSelection: { iamRoleArn: role.roleArn, - selectionName: props.backupSelectionName || this.construct.id, + selectionName: props.backupSelectionName || this.node.id, listOfTags: Lazy.anyValue({ produce: () => this.listOfTags, }, { omitEmptyArray: true }), @@ -126,7 +126,7 @@ export class BackupSelection extends Resource implements iam.IGrantable { } if (resource.construct) { - resource.construct.construct.applyAspect(this.backupableResourcesCollector); + resource.construct.node.applyAspect(this.backupableResourcesCollector); // Cannot push `this.backupableResourcesCollector.resources` to // `this.resources` here because it has not been evaluated yet. // Will be concatenated to `this.resources` in a `Lazy.listValue` diff --git a/packages/@aws-cdk/aws-backup/lib/vault.ts b/packages/@aws-cdk/aws-backup/lib/vault.ts index dbbf01869871b..e9f85d8b160b1 100644 --- a/packages/@aws-cdk/aws-backup/lib/vault.ts +++ b/packages/@aws-cdk/aws-backup/lib/vault.ts @@ -160,7 +160,7 @@ export class BackupVault extends Resource implements IBackupVault { private uniqueVaultName() { // Max length of 50 chars, get the last 50 chars - const id = this.construct.uniqueId; + const id = this.node.uniqueId; return id.substring(Math.max(id.length - 50, 0), id.length); } } diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index 3d9112d22a09d..5d18ae0ca635e 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -386,7 +386,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment }); if (props.computeResources && props.computeResources.vpc) { - this.construct.addDependency(props.computeResources.vpc); + this.node.addDependency(props.computeResources.vpc); } this.computeEnvironmentArn = this.getResourceArnAttribute(computeEnvironment.ref, { diff --git a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts index bb25f2de40165..36258615fbf8e 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -237,10 +237,10 @@ describe('Batch Compute Evironment', () => { ], Subnets: [ { - Ref: `${vpc.construct.uniqueId}PrivateSubnet1Subnet865FB50A`, + Ref: `${vpc.node.uniqueId}PrivateSubnet1Subnet865FB50A`, }, { - Ref: `${vpc.construct.uniqueId}PrivateSubnet2Subnet23D3396F`, + Ref: `${vpc.node.uniqueId}PrivateSubnet2Subnet23D3396F`, }, ], Tags: { diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/util.ts b/packages/@aws-cdk/aws-certificatemanager/lib/util.ts index 743b1e4faf8b7..219bac1fd6a80 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/util.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/util.ts @@ -28,7 +28,7 @@ export function getCertificateRegion(cert: ICertificate): string | undefined { const { certificateArn, stack } = cert; if (isDnsValidatedCertificate(cert)) { - const requestResource = cert.construct.findChild('CertificateRequestorResource').construct.defaultChild; + const requestResource = cert.node.findChild('CertificateRequestorResource').node.defaultChild; // @ts-ignore const { _cfnProperties: properties } = requestResource; diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.deps.ts b/packages/@aws-cdk/aws-cloudformation/test/test.deps.ts index 95cca4fa0efc2..0b39949203fe0 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.deps.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.deps.ts @@ -335,7 +335,7 @@ export = { function matrixForResourceDependencyTest(testFunction: (test: Test, addDep: (source: CfnResource, target: CfnResource) => void) => void) { return { 'construct dependency'(test: Test) { - testFunction(test, (source, target) => source.construct.addDependency(target)); + testFunction(test, (source, target) => source.node.addDependency(target)); }, 'resource dependency'(test: Test) { testFunction(test, (source, target) => source.addDependsOn(target)); 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 94f42a6ecef7f..4eedbf0e75c4c 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts @@ -62,7 +62,7 @@ export = { const assembly = app.synth(); // THEN - const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${nested.construct.uniqueId}.nested.template.json`), 'utf-8')); + const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${nested.node.uniqueId}.nested.template.json`), 'utf-8')); test.deepEqual(template, { Resources: { ResourceInNestedStack: { @@ -847,7 +847,7 @@ export = { const resource = new CfnResource(nested, 'resource', { type: 'foo' }); // WHEN - resource.construct.addMetadata('foo', 'bar'); + resource.node.addMetadata('foo', 'bar'); // THEN: the first non-nested stack records the assembly metadata const asm = app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 2598c0cb10aa0..81cb0e7470ec4 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -196,7 +196,7 @@ export class Distribution extends Resource implements IDistribution { } else { const originIndex = this.boundOrigins.length + 1; const scope = new Construct(this, `Origin${originIndex}`); - const originId = scope.construct.uniqueId; + const originId = scope.node.uniqueId; const originBindConfig = origin.bind(scope, { originId }); this.boundOrigins.push({ origin, originId, ...originBindConfig }); if (originBindConfig.failoverConfig) { @@ -223,7 +223,7 @@ export class Distribution extends Resource implements IDistribution { quantity: statusCodes.length, }, }, - id: new Construct(this, `OriginGroup${groupIndex}`).construct.uniqueId, + id: new Construct(this, `OriginGroup${groupIndex}`).node.uniqueId, members: { items: [ { originId }, diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index e93ddecaf1d79..b2fc472adfd46 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -924,7 +924,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu } const distribution = new CfnDistribution(this, 'CFDistribution', { distributionConfig }); - this.construct.defaultChild = distribution; + this.node.defaultChild = distribution; this.domainName = distribution.attrDomainName; this.distributionDomainName = distribution.attrDomainName; this.distributionId = distribution.ref; diff --git a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts index 5cf6751f50f87..c8b2fb322995f 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -491,7 +491,7 @@ nodeunitShim({ ], }); - test.ok(distribution.construct.defaultChild instanceof CfnDistribution); + test.ok(distribution.node.defaultChild instanceof CfnDistribution); test.done(); }, diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index 217b578b8244b..e41a50b1cb66d 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -295,14 +295,14 @@ export class Trail extends Resource { // Add a dependency on the bucket policy being updated, CloudTrail will test this upon creation. if (this.s3bucket.policy) { - trail.construct.addDependency(this.s3bucket.policy); + trail.node.addDependency(this.s3bucket.policy); } // If props.sendToCloudWatchLogs is set to true then the trail needs to depend on the created logsRole // so that it can create the log stream for the log group. This ensures the logsRole is created and propagated // before the trail tries to create the log stream. if (logsRole !== undefined) { - trail.construct.addDependency(logsRole); + trail.node.addDependency(logsRole); } } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts index b204d776dfb1c..4d20f5f3f1300 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts @@ -119,7 +119,7 @@ export class CompositeAlarm extends AlarmBase { } private generateUniqueId(): string { - const name = this.construct.uniqueId; + const name = this.node.uniqueId; if (name.length > 240) { return name.substring(0, 120) + name.substring(name.length - 120); } diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index c5f231c443d8e..d4f349431b544 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1007,7 +1007,7 @@ export class Project extends ProjectBase { } else { const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, - description: 'Automatic generated security group for CodeBuild ' + this.construct.uniqueId, + description: 'Automatic generated security group for CodeBuild ' + this.node.uniqueId, allowAllOutbound: props.allowAllOutbound, }); securityGroups = [securityGroup]; @@ -1060,7 +1060,7 @@ export class Project extends ProjectBase { // add an explicit dependency between the EC2 Policy and this Project - // otherwise, creating the Project fails, as it requires these permissions // to be already attached to the Project's Role - project.construct.addDependency(policy); + project.node.addDependency(policy); } private validateCodePipelineSettings(artifacts: IArtifacts) { diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 1c1d569d78ec8..7dfb132138936 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -367,7 +367,7 @@ export class Repository extends RepositoryBase { let name = options && options.name; if (!name) { - name = this.construct.path + '/' + arn; + name = this.node.path + '/' + arn; } if (this.triggers.find(prop => prop.name === name)) { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index 154a644ce5a41..074667136417f 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -189,7 +189,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy this.addPostHook(props.postHook); } - (props.alias.construct.defaultChild as lambda.CfnAlias).cfnOptions.updatePolicy = { + (props.alias.node.defaultChild as lambda.CfnAlias).cfnOptions.updatePolicy = { codeDeployLambdaAliasUpdate: { applicationName: this.application.applicationName, deploymentGroupName: resource.ref, diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts index 30308c991b094..f4d356e093204 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -170,7 +170,7 @@ export class ProfilingGroup extends ProfilingGroupBase { } private generateUniqueId(): string { - const name = this.construct.uniqueId; + const name = this.node.uniqueId; if (name.length > 240) { return name.substring(0, 120) + name.substring(name.length - 120); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index d59ece2b1d2e5..1982b0b8336bf 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -270,7 +270,7 @@ abstract class CloudFormationDeployAction extends CloudFormationAction { // pass role is not allowed for cross-account access - so, // create the deployment Role in the other account! this._deploymentRole = new iam.Role(roleStack, - `${stage.pipeline.construct.uniqueId}-${stage.stageName}-${this.actionProperties.actionName}-DeploymentRole`, { + `${stage.pipeline.node.uniqueId}-${stage.stageName}-${this.actionProperties.actionName}-DeploymentRole`, { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com'), roleName: cdk.PhysicalName.GENERATE_IF_NEEDED, }); @@ -500,7 +500,7 @@ class SingletonPolicy extends cdk.Construct implements iam.IGrantable { * @returns the SingletonPolicy for this role. */ public static forRole(role: iam.IRole): SingletonPolicy { - const found = role.construct.tryFindChild(SingletonPolicy.UUID); + const found = role.node.tryFindChild(SingletonPolicy.UUID); return (found as SingletonPolicy) || new SingletonPolicy(role); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index ea5735708e842..2fa7a67b29b93 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -123,7 +123,7 @@ export class CodeCommitSourceAction extends Action { this.props.trigger === CodeCommitTrigger.EVENTS; if (createEvent) { const branchIdDisambiguator = this.branch === 'master' ? '' : `-${this.branch}-`; - this.props.repository.onCommit(`${stage.pipeline.construct.uniqueId}${branchIdDisambiguator}EventRule`, { + this.props.repository.onCommit(`${stage.pipeline.node.uniqueId}${branchIdDisambiguator}EventRule`, { target: new targets.CodePipeline(stage.pipeline), branches: [this.branch], }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts index 23f6158e8ef1b..b9261cf34d878 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts @@ -89,7 +89,7 @@ export class EcrSourceAction extends Action { resources: [this.props.repository.repositoryArn], })); - this.props.repository.onCloudTrailImagePushed(stage.pipeline.construct.uniqueId + 'SourceEventRule', { + this.props.repository.onCloudTrailImagePushed(stage.pipeline.node.uniqueId + 'SourceEventRule', { target: new targets.CodePipeline(stage.pipeline), imageTag: this.props.imageTag, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts index 9891f79b7125e..7608aa3d94ae0 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -110,8 +110,8 @@ export class S3SourceAction extends Action { protected bound(_scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { if (this.props.trigger === S3Trigger.EVENTS) { - const id = stage.pipeline.construct.uniqueId + 'SourceEventRule' + this.props.bucketKey; - if (this.props.bucket.construct.tryFindChild(id)) { + const id = stage.pipeline.node.uniqueId + 'SourceEventRule' + this.props.bucketKey; + if (this.props.bucket.node.tryFindChild(id)) { // this means a duplicate path for the same bucket - error out throw new Error(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts index c6f8f9c6085fe..122add214cc3a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts @@ -659,7 +659,7 @@ export = { }, })); - const otherStack = app.construct.findChild('cross-account-support-stack-123456789012') as cdk.Stack; + const otherStack = app.node.findChild('cross-account-support-stack-123456789012') as cdk.Stack; expect(otherStack).to(haveResourceLike('AWS::IAM::Role', { 'RoleName': 'pipelinestack-support-123loycfnactionrole56af64af3590f311bc50', })); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index 0e284546218b7..6a7001630b3c5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -653,8 +653,8 @@ export = { test.notEqual(usEast1Support, undefined); test.equal(usEast1Support.stack.region, 'us-east-1'); test.equal(usEast1Support.stack.account, pipelineAccount); - test.ok(usEast1Support.stack.construct.id.indexOf('us-east-1') !== -1, - `expected '${usEast1Support.stack.construct.id}' to contain 'us-east-1'`); + test.ok(usEast1Support.stack.node.id.indexOf('us-east-1') !== -1, + `expected '${usEast1Support.stack.node.id}' to contain 'us-east-1'`); test.done(); }, diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 960fe23e5496e..b498c20945f83 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -262,7 +262,7 @@ export class Pipeline extends PipelineBase { }); // this will produce a DependsOn for both the role and the policy resources. - codePipeline.construct.addDependency(this.role); + codePipeline.node.addDependency(this.role); this.artifactBucket.grantReadWrite(this.role); this.pipelineName = this.getResourceNameAttribute(codePipeline.ref); @@ -460,7 +460,7 @@ export class Pipeline extends PipelineBase { if (otherStack) { // check if the stack doesn't have this magic construct already const id = `CrossRegionReplicationSupport-d823f1d8-a990-4e5c-be18-4ac698532e65-${actionRegion}`; - let crossRegionSupportConstruct = otherStack.construct.tryFindChild(id) as CrossRegionSupportConstruct; + let crossRegionSupportConstruct = otherStack.node.tryFindChild(id) as CrossRegionSupportConstruct; if (!crossRegionSupportConstruct) { crossRegionSupportConstruct = new CrossRegionSupportConstruct(otherStack, id); } @@ -480,7 +480,7 @@ export class Pipeline extends PipelineBase { const app = this.requireApp(); const supportStackId = `cross-region-stack-${pipelineAccount}:${actionRegion}`; - let supportStack = app.construct.tryFindChild(supportStackId) as CrossRegionSupportStack; + let supportStack = app.node.tryFindChild(supportStackId) as CrossRegionSupportStack; if (!supportStack) { supportStack = new CrossRegionSupportStack(app, supportStackId, { pipelineStackName: pipelineStack.stackName, @@ -516,7 +516,7 @@ export class Pipeline extends PipelineBase { private generateNameForDefaultBucketKeyAlias(): string { const prefix = 'alias/codepipeline-'; const maxAliasLength = 256; - const uniqueId = this.construct.uniqueId; + const uniqueId = this.node.uniqueId; // take the last 256 - (prefix length) characters of uniqueId const startIndex = Math.max(0, uniqueId.length - (maxAliasLength - prefix.length)); return prefix + uniqueId.substring(startIndex).toLowerCase(); @@ -598,7 +598,7 @@ export class Pipeline extends PipelineBase { // generate a role in the other stack, that the Pipeline will assume for executing this action const ret = new iam.Role(otherAccountStack, - `${this.construct.uniqueId}-${stage.stageName}-${action.actionProperties.actionName}-ActionRole`, { + `${this.node.uniqueId}-${stage.stageName}-${action.actionProperties.actionName}-ActionRole`, { assumedBy: new iam.AccountPrincipal(pipelineStack.account), roleName: PhysicalName.GENERATE_IF_NEEDED, }); @@ -652,7 +652,7 @@ export class Pipeline extends PipelineBase { if (!targetAccountStack) { const stackId = `cross-account-support-stack-${targetAccount}`; const app = this.requireApp(); - targetAccountStack = app.construct.tryFindChild(stackId) as Stack; + targetAccountStack = app.node.tryFindChild(stackId) as Stack; if (!targetAccountStack) { targetAccountStack = new Stack(app, stackId, { stackName: `${pipelineStack.stackName}-support-${targetAccount}`, @@ -858,7 +858,7 @@ export class Pipeline extends PipelineBase { } private requireApp(): App { - const app = this.construct.root; + const app = this.node.root; if (!app || !App.isApp(app)) { throw new Error('Pipeline stack which uses cross-environment actions must be part of a CDK app'); } diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index 5bc9a52fcef49..32f3604191e6a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -302,7 +302,7 @@ export = { const app = new cdk.App({ treeMetadata: false, // we can't set the context otherwise, because App will have a child }); - app.construct.setContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT, true); + app.node.setContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT, true); const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: { region: 'us-west-2', account: '123456789012' }, diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index ddbc7da3992d3..80a65675a0856 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -865,7 +865,7 @@ export class UserPool extends UserPoolBase { return undefined; } - const smsRoleExternalId = this.construct.uniqueId.substr(0, 1223); // sts:ExternalId max length of 1224 + const smsRoleExternalId = this.node.uniqueId.substr(0, 1223); // sts:ExternalId max length of 1224 const smsRole = props.smsRole ?? new Role(this, 'smsRole', { assumedBy: new ServicePrincipal('cognito-idp.amazonaws.com', { conditions: { diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 2526f9ddd1981..12dd86b8ab1bd 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -357,7 +357,7 @@ export class CustomRule extends RuleNew { } // The lambda permission must be created before the rule - this.construct.addDependency(props.lambdaFunction); + this.node.addDependency(props.lambdaFunction); const rule = new CfnConfigRule(this, 'Resource', { configRuleName: this.physicalName, diff --git a/packages/@aws-cdk/aws-docdb/lib/cluster.ts b/packages/@aws-cdk/aws-docdb/lib/cluster.ts index c8bb03243eab2..339ef8f0b51a0 100644 --- a/packages/@aws-cdk/aws-docdb/lib/cluster.ts +++ b/packages/@aws-cdk/aws-docdb/lib/cluster.ts @@ -303,7 +303,7 @@ export class DatabaseCluster extends DatabaseClusterBase { }); // HACK: Use an escape-hatch to apply a consistent removal policy to the // security group so we don't get errors when trying to delete the stack - (securityGroup.construct.defaultChild as CfnResource).applyRemovalPolicy(props.removalPolicy, { + (securityGroup.node.defaultChild as CfnResource).applyRemovalPolicy(props.removalPolicy, { applyToUpdateReplacePolicy: true, }); } @@ -389,7 +389,7 @@ export class DatabaseCluster extends DatabaseClusterBase { // We must have a dependency on the NAT gateway provider here to create // things in the right order. - instance.construct.addDependency(internetConnectivityEstablished); + instance.node.addDependency(internetConnectivityEstablished); this.instanceIdentifiers.push(instance.ref); this.instanceEndpoints.push(new Endpoint(instance.attrEndpoint, port)); @@ -413,7 +413,7 @@ export class DatabaseCluster extends DatabaseClusterBase { } const id = 'RotationSingleUser'; - const existing = this.construct.tryFindChild(id); + const existing = this.node.tryFindChild(id); if (existing) { throw new Error('A single user rotation was already added to this cluster.'); } diff --git a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts index 581716d149ef9..957ea0bd40a85 100644 --- a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts @@ -775,6 +775,6 @@ describe('DatabaseCluster', () => { function testStack() { const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' }}); - stack.construct.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); + stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); return stack; } diff --git a/packages/@aws-cdk/aws-docdb/test/instance.test.ts b/packages/@aws-cdk/aws-docdb/test/instance.test.ts index a4b3d849073f0..197e2e296fd8d 100644 --- a/packages/@aws-cdk/aws-docdb/test/instance.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/instance.test.ts @@ -173,7 +173,7 @@ class TestStack extends cdk.Stack { constructor(scope?: cdk.Construct, id?: string, props: cdk.StackProps = {}) { super(scope, id, props); - this.construct.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); + this.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); this.vpc = new ec2.Vpc(this, 'VPC'); this.cluster = new DatabaseCluster(this, 'Database', { diff --git a/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts b/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts index 03cad4f4ed397..d72ff1f42c176 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts @@ -40,7 +40,7 @@ export class GlobalTable extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: GlobalTableProps) { super(scope, id); - this.construct.addWarning('The @aws-cdk/aws-dynamodb-global module has been deprecated in favor of @aws-cdk/aws-dynamodb.Table.replicationRegions'); + this.node.addWarning('The @aws-cdk/aws-dynamodb-global module has been deprecated in favor of @aws-cdk/aws-dynamodb.Table.replicationRegions'); this._regionalTables = []; diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts index 077fdd73276b6..e68f9230b69cd 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts @@ -24,9 +24,9 @@ export = { 'global dynamo'(test: Test) { const stack = new Stack(); new GlobalTable(stack, CONSTRUCT_NAME, STACK_PROPS); - const topStack = stack.construct.findChild(CONSTRUCT_NAME) as Stack; + const topStack = stack.node.findChild(CONSTRUCT_NAME) as Stack; for ( const reg of STACK_PROPS.regions ) { - const tableStack = topStack.construct.findChild(CONSTRUCT_NAME + '-' + reg) as Stack; + const tableStack = topStack.node.findChild(CONSTRUCT_NAME + '-' + reg) as Stack; expect(tableStack).to(haveResource('AWS::DynamoDB::Table', { 'KeySchema': [ { @@ -46,7 +46,7 @@ export = { 'TableName': 'GlobalTable', })); } - const customResourceStack = stack.construct.findChild(CONSTRUCT_NAME + '-CustomResource') as Stack; + const customResourceStack = stack.node.findChild(CONSTRUCT_NAME + '-CustomResource') as Stack; expect(customResourceStack).to(haveResource('AWS::Lambda::Function', { Description: 'Lambda to make DynamoDB a global table', Handler: 'index.handler', diff --git a/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts b/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts index 357306ee82ef0..dbfd8761aff05 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts @@ -11,7 +11,7 @@ export class ReplicaProvider extends NestedStack { public static getOrCreate(scope: Construct) { const stack = Stack.of(scope); const uid = '@aws-cdk/aws-dynamodb.ReplicaProvider'; - return stack.construct.tryFindChild(uid) as ReplicaProvider || new ReplicaProvider(stack, uid); + return stack.node.tryFindChild(uid) as ReplicaProvider || new ReplicaProvider(stack, uid); } /** diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 66aecf2e402c7..2cb849d669f72 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -527,7 +527,7 @@ abstract class TableBase extends Resource implements ITable { */ public grantStream(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { if (!this.tableStreamArn) { - throw new Error(`DynamoDB Streams must be enabled on the table ${this.construct.path}`); + throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`); } return iam.Grant.addToPrincipal({ @@ -558,7 +558,7 @@ abstract class TableBase extends Resource implements ITable { */ public grantTableListStreams(grantee: iam.IGrantable): iam.Grant { if (!this.tableStreamArn) { - throw new Error(`DynamoDB Streams must be enabled on the table ${this.construct.path}`); + throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`); } return iam.Grant.addToPrincipal({ @@ -729,7 +729,7 @@ abstract class TableBase extends Resource implements ITable { } if (opts.streamActions) { if (!this.tableStreamArn) { - throw new Error(`DynamoDB Streams must be enabled on the table ${this.construct.path}`); + throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`); } const resources = [ this.tableStreamArn]; const ret = iam.Grant.addToPrincipal({ @@ -920,7 +920,7 @@ export class Table extends TableBase { }); this.tableName = this.getResourceNameAttribute(this.table.ref); - if (props.tableName) { this.construct.addMetadata('aws:cdk:hasPhysicalName', this.tableName); } + if (props.tableName) { this.node.addMetadata('aws:cdk:hasPhysicalName', this.tableName); } this.tableStreamArn = streamSpecification ? this.table.attrStreamArn : undefined; @@ -1267,7 +1267,7 @@ export class Table extends TableBase { Region: region, }, }); - currentRegion.construct.addDependency( + currentRegion.node.addDependency( onEventHandlerPolicy.policy, isCompleteHandlerPolicy.policy, ); @@ -1279,7 +1279,7 @@ export class Table extends TableBase { const createReplica = new CfnCondition(this, `StackRegionNotEquals${region}`, { expression: Fn.conditionNot(Fn.conditionEquals(region, Aws.REGION)), }); - const cfnCustomResource = currentRegion.construct.defaultChild as CfnCustomResource; + const cfnCustomResource = currentRegion.node.defaultChild as CfnCustomResource; cfnCustomResource.cfnOptions.condition = createReplica; } @@ -1295,7 +1295,7 @@ export class Table extends TableBase { // have multiple table updates at the same time. The `isCompleteHandler` // of the provider waits until the replica is in an ACTIVE state. if (previousRegion) { - currentRegion.construct.addDependency(previousRegion); + currentRegion.node.addDependency(previousRegion); } previousRegion = currentRegion; } @@ -1349,7 +1349,7 @@ export class Table extends TableBase { switch (encryptionType) { case TableEncryption.CUSTOMER_MANAGED: const encryptionKey = props.encryptionKey ?? new kms.Key(this, 'Key', { - description: `Customer-managed key auto-created for encrypting DynamoDB table at ${this.construct.path}`, + description: `Customer-managed key auto-created for encrypting DynamoDB table at ${this.node.path}`, enableKeyRotation: true, }); @@ -1454,7 +1454,7 @@ class SourceTableAttachedPolicy extends Construct implements iam.IGrantable { public readonly policy: iam.IPolicy; public constructor(sourceTable: Table, role: iam.IRole) { - super(sourceTable, `SourceTableAttachedPolicy-${role.construct.uniqueId}`); + super(sourceTable, `SourceTableAttachedPolicy-${role.node.uniqueId}`); const policy = new iam.Policy(this, 'Resource', { roles: [role] }); this.policy = policy; diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index 379613c699b92..754a30150ace5 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -324,7 +324,7 @@ test('when specifying every property', () => { partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY, }); - table.construct.applyAspect(new Tag('Environment', 'Production')); + table.node.applyAspect(new Tag('Environment', 'Production')); expect(stack).toHaveResource('AWS::DynamoDB::Table', { @@ -357,7 +357,7 @@ test('when specifying sse with customer managed CMK', () => { encryption: TableEncryption.CUSTOMER_MANAGED, partitionKey: TABLE_PARTITION_KEY, }); - table.construct.applyAspect(new Tag('Environment', 'Production')); + table.node.applyAspect(new Tag('Environment', 'Production')); expect(stack).toHaveResource('AWS::DynamoDB::Table', { 'SSESpecification': { @@ -383,7 +383,7 @@ test('when specifying only encryptionKey', () => { encryptionKey, partitionKey: TABLE_PARTITION_KEY, }); - table.construct.applyAspect(new Tag('Environment', 'Production')); + table.node.applyAspect(new Tag('Environment', 'Production')); expect(stack).toHaveResource('AWS::DynamoDB::Table', { 'SSESpecification': { @@ -410,7 +410,7 @@ test('when specifying sse with customer managed CMK with encryptionKey provided encryptionKey, partitionKey: TABLE_PARTITION_KEY, }); - table.construct.applyAspect(new Tag('Environment', 'Production')); + table.node.applyAspect(new Tag('Environment', 'Production')); expect(stack).toHaveResource('AWS::DynamoDB::Table', { 'SSESpecification': { diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts index 6c8d3f2899f80..3304c8defec2e 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts @@ -58,7 +58,7 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL removalPolicy: RemovalPolicy.DESTROY, }); -tableWithGlobalAndLocalSecondaryIndex.construct.applyAspect(new Tag('Environment', 'Production')); +tableWithGlobalAndLocalSecondaryIndex.node.applyAspect(new Tag('Environment', 'Production')); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_1, diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.ts index 6b3777f439a4d..b1f3dca8b75a3 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.sse.ts @@ -58,7 +58,7 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL removalPolicy: RemovalPolicy.DESTROY, }); -tableWithGlobalAndLocalSecondaryIndex.construct.applyAspect(new Tag('Environment', 'Production')); +tableWithGlobalAndLocalSecondaryIndex.node.applyAspect(new Tag('Environment', 'Production')); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_1, partitionKey: GSI_PARTITION_KEY, diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts index d2fe0f276d9fe..35c81b6486d3b 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts @@ -56,7 +56,7 @@ const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL removalPolicy: RemovalPolicy.DESTROY, }); -tableWithGlobalAndLocalSecondaryIndex.construct.applyAspect(new Tag('Environment', 'Production')); +tableWithGlobalAndLocalSecondaryIndex.node.applyAspect(new Tag('Environment', 'Production')); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_1, partitionKey: GSI_PARTITION_KEY, diff --git a/packages/@aws-cdk/aws-ec2/lib/instance.ts b/packages/@aws-cdk/aws-ec2/lib/instance.ts index 4e27914aa5ea2..79a6bb262d053 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance.ts @@ -267,7 +267,7 @@ export class Instance extends Resource implements IInstance { } this.connections = new Connections({ securityGroups: [this.securityGroup] }); this.securityGroups.push(this.securityGroup); - Tag.add(this, NAME_TAG, props.instanceName || this.construct.path); + Tag.add(this, NAME_TAG, props.instanceName || this.node.path); this.role = props.role || new iam.Role(this, 'InstanceRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), @@ -291,13 +291,13 @@ export class Instance extends Resource implements IInstance { if (selected.length === 1) { subnet = selected[0]; } else { - this.construct.addError(`Need exactly 1 subnet to match AZ '${props.availabilityZone}', found ${selected.length}. Use a different availabilityZone.`); + this.node.addError(`Need exactly 1 subnet to match AZ '${props.availabilityZone}', found ${selected.length}. Use a different availabilityZone.`); } } else { if (subnets.length > 0) { subnet = subnets[0]; } else { - this.construct.addError(`Did not find any subnets matching '${JSON.stringify(props.vpcSubnets)}', please use a different selection.`); + this.node.addError(`Did not find any subnets matching '${JSON.stringify(props.vpcSubnets)}', please use a different selection.`); } } if (!subnet) { @@ -322,10 +322,10 @@ export class Instance extends Resource implements IInstance { blockDeviceMappings: props.blockDevices !== undefined ? synthesizeBlockDeviceMappings(this, props.blockDevices) : undefined, privateIpAddress: props.privateIpAddress, }); - this.instance.construct.addDependency(this.role); + this.instance.node.addDependency(this.role); this.osType = imageConfig.osType; - this.construct.defaultChild = this.instance; + this.node.defaultChild = this.instance; this.instanceId = this.instance.ref; this.instanceAvailabilityZone = this.instance.attrAvailabilityZone; diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 712c7d3cb2aa3..e5dc208be10f0 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -70,7 +70,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { } public get uniqueId() { - return this.construct.uniqueId; + return this.node.uniqueId; } public addIngressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { @@ -81,7 +81,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { const [scope, id] = determineRuleScope(this, peer, connection, 'from', remoteRule); // Skip duplicates - if (scope.construct.tryFindChild(id) === undefined) { + if (scope.node.tryFindChild(id) === undefined) { new CfnSecurityGroupIngress(scope, id, { groupId: this.securityGroupId, ...peer.toIngressRuleConfig(), @@ -99,7 +99,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { const [scope, id] = determineRuleScope(this, peer, connection, 'to', remoteRule); // Skip duplicates - if (scope.construct.tryFindChild(id) === undefined) { + if (scope.node.tryFindChild(id) === undefined) { new CfnSecurityGroupEgress(scope, id, { groupId: this.securityGroupId, ...peer.toEgressRuleConfig(), @@ -363,7 +363,7 @@ export class SecurityGroup extends SecurityGroupBase { physicalName: props.securityGroupName, }); - const groupDescription = props.description || this.construct.path; + const groupDescription = props.description || this.node.path; this.allowAllOutbound = props.allowAllOutbound !== false; @@ -404,7 +404,7 @@ export class SecurityGroup extends SecurityGroupBase { // In the case of "allowAllOutbound", we don't add any more rules. There // is only one rule which allows all traffic and that subsumes any other // rule. - this.construct.addWarning('Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customize rules, set allowAllOutbound=false on the SecurityGroup'); + this.node.addWarning('Ignoring Egress rule since \'allowAllOutbound\' is set to true; To add customize rules, set allowAllOutbound=false on the SecurityGroup'); return; } else { // Otherwise, if the bogus rule exists we can now remove it because the diff --git a/packages/@aws-cdk/aws-ec2/lib/user-data.ts b/packages/@aws-cdk/aws-ec2/lib/user-data.ts index 78ee18fb7c824..617563e6cd05a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/user-data.ts +++ b/packages/@aws-cdk/aws-ec2/lib/user-data.ts @@ -172,7 +172,7 @@ class LinuxUserData extends UserData { public addSignalOnExitCommand( resource: Resource ): void { const stack = Stack.of(resource); - const resourceID = stack.getLogicalId(resource.construct.defaultChild as CfnElement); + const resourceID = stack.getLogicalId(resource.node.defaultChild as CfnElement); this.addOnExitCommands(`/opt/aws/bin/cfn-signal --stack ${stack.stackName} --resource ${resourceID} --region ${stack.region} -e $exitCode || echo 'Failed to send Cloudformation Signal'`); } @@ -230,7 +230,7 @@ class WindowsUserData extends UserData { public addSignalOnExitCommand( resource: Resource ): void { const stack = Stack.of(resource); - const resourceID = stack.getLogicalId(resource.construct.defaultChild as CfnElement); + const resourceID = stack.getLogicalId(resource.node.defaultChild as CfnElement); this.addOnExitCommands(`cfn-signal --stack ${stack.stackName} --resource ${resourceID} --region ${stack.region} --success ($success.ToString().ToLower())`); } diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index 91c0cd186960d..4f5a765b96bc5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -27,7 +27,7 @@ export function defaultSubnetName(type: SubnetType) { * All subnet names look like NAME <> "Subnet" <> INDEX */ export function subnetGroupNameFromConstructId(subnet: ISubnet) { - return subnet.construct.id.replace(/Subnet\d+$/, ''); + return subnet.node.id.replace(/Subnet\d+$/, ''); } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index ce62244e499c3..e33c32a9d63b6 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -176,7 +176,7 @@ export function synthesizeBlockDeviceMappings(construct: Construct, blockDevices throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1'); } } else if (volumeType !== EbsDeviceVolumeType.IO1) { - construct.construct.addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + construct.node.addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); } } @@ -556,7 +556,7 @@ abstract class VolumeBase extends Resource implements IVolume { private calculateResourceTagValue(constructs: Construct[]): string { const md5 = crypto.createHash('md5'); - constructs.forEach(construct => md5.update(construct.construct.uniqueId)); + constructs.forEach(construct => md5.update(construct.node.uniqueId)); return md5.digest('hex'); } } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index ff53488411938..daaba4cd8a05f 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -377,7 +377,7 @@ abstract class VpcBase extends Resource implements IVpc { const routeTableIds = allRouteTableIds(flatten(vpnRoutePropagation.map(s => this.selectSubnets(s).subnets))); if (routeTableIds.length === 0) { - this.construct.addError(`enableVpnGateway: no subnets matching selection: '${JSON.stringify(vpnRoutePropagation)}'. Select other subnets to add routes to.`); + this.node.addError(`enableVpnGateway: no subnets matching selection: '${JSON.stringify(vpnRoutePropagation)}'. Select other subnets to add routes to.`); } const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { @@ -387,7 +387,7 @@ abstract class VpcBase extends Resource implements IVpc { // The AWS::EC2::VPNGatewayRoutePropagation resource cannot use the VPN gateway // until it has successfully attached to the VPC. // See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpn-gatewayrouteprop.html - routePropagation.construct.addDependency(attachment); + routePropagation.node.addDependency(attachment); } /** @@ -1176,7 +1176,7 @@ export class Vpc extends VpcBase { this.vpcDefaultSecurityGroup = this.resource.attrDefaultSecurityGroup; this.vpcIpv6CidrBlocks = this.resource.attrIpv6CidrBlocks; - this.construct.applyAspect(new Tag(NAME_TAG, this.construct.path)); + this.node.applyAspect(new Tag(NAME_TAG, this.node.path)); this.availabilityZones = stack.availabilityZones; @@ -1363,8 +1363,8 @@ export class Vpc extends VpcBase { // These values will be used to recover the config upon provider import const includeResourceTypes = [CfnSubnet.CFN_RESOURCE_TYPE_NAME]; - subnet.construct.applyAspect(new Tag(SUBNETNAME_TAG, subnetConfig.name, {includeResourceTypes})); - subnet.construct.applyAspect(new Tag(SUBNETTYPE_TAG, subnetTypeTagValue(subnetConfig.subnetType), {includeResourceTypes})); + subnet.node.applyAspect(new Tag(SUBNETNAME_TAG, subnetConfig.name, {includeResourceTypes})); + subnet.node.applyAspect(new Tag(SUBNETTYPE_TAG, subnetTypeTagValue(subnetConfig.subnetType), {includeResourceTypes})); }); } } @@ -1482,7 +1482,7 @@ export class Subnet extends Resource implements ISubnet { Object.defineProperty(this, VPC_SUBNET_SYMBOL, { value: true }); - this.construct.applyAspect(new Tag(NAME_TAG, this.construct.path)); + this.node.applyAspect(new Tag(NAME_TAG, this.node.path)); this.availabilityZone = props.availabilityZone; const subnet = new CfnSubnet(this, 'Subnet', { @@ -1500,7 +1500,7 @@ export class Subnet extends Resource implements ISubnet { // was just created. However, the ACL can be replaced at a later time. this._networkAcl = NetworkAcl.fromNetworkAclId(this, 'Acl', subnet.attrNetworkAclAssociationId); this.subnetNetworkAclAssociationId = Lazy.stringValue({ produce: () => this._networkAcl.networkAclId }); - this.construct.defaultChild = subnet; + this.node.defaultChild = subnet; const table = new CfnRouteTable(this, 'RouteTable', { vpcId: props.vpcId, @@ -1529,7 +1529,7 @@ export class Subnet extends Resource implements ISubnet { destinationCidrBlock: '0.0.0.0/0', gatewayId, }); - route.construct.addDependency(gatewayAttachment); + route.node.addDependency(gatewayAttachment); // Since the 'route' depends on the gateway attachment, just // depending on the route is enough. @@ -1587,7 +1587,7 @@ export class Subnet extends Resource implements ISubnet { const scope = Construct.isConstruct(networkAcl) ? networkAcl : this; const other = Construct.isConstruct(networkAcl) ? this : networkAcl; - new SubnetNetworkAclAssociation(scope, id + other.construct.uniqueId, { + new SubnetNetworkAclAssociation(scope, id + other.node.uniqueId, { networkAcl, subnet: this, }); @@ -1891,10 +1891,10 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat if (!attrs.routeTableId) { const ref = Token.isUnresolved(attrs.subnetId) - ? `at '${scope.construct.path}/${id}'` + ? `at '${scope.node.path}/${id}'` : `'${attrs.subnetId}'`; // eslint-disable-next-line max-len - scope.construct.addWarning(`No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); + scope.node.addWarning(`No routeTableId was provided to the subnet ${ref}. Attempting to read its .routeTable.routeTableId will return null/undefined. (More info: https://github.com/aws/aws-cdk/pull/3171)`); } this._availabilityZone = attrs.availabilityZone; @@ -1916,7 +1916,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat public associateNetworkAcl(id: string, networkAcl: INetworkAcl): void { const scope = Construct.isConstruct(networkAcl) ? networkAcl : this; const other = Construct.isConstruct(networkAcl) ? this : networkAcl; - new SubnetNetworkAclAssociation(scope, id + other.construct.uniqueId, { + new SubnetNetworkAclAssociation(scope, id + other.node.uniqueId, { networkAcl, subnet: this, }); diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index ed451b1a774e7..2a78f3cdf516e 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -237,8 +237,8 @@ nodeunitShim({ }); // THEN - test.deepEqual(instance.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); - test.deepEqual(instance.construct.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + test.deepEqual(instance.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); + test.deepEqual(instance.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); }, @@ -264,8 +264,8 @@ nodeunitShim({ }); // THEN - test.deepEqual(instance.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); - test.deepEqual(instance.construct.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + test.deepEqual(instance.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); + test.deepEqual(instance.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); }, diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts index 5f2baf4eec680..a1c8e39c28141 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts @@ -393,13 +393,13 @@ nodeunitShim({ const stack = new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); // Setup context for stack AZs - stack.construct.setContext( + stack.node.setContext( ContextProvider.getKey(stack, { provider: cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER, }).key, ['us-east-1a', 'us-east-1b', 'us-east-1c']); // Setup context for endpoint service AZs - stack.construct.setContext( + stack.node.setContext( ContextProvider.getKey(stack, { provider: cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER, props: { @@ -438,7 +438,7 @@ nodeunitShim({ const stack = new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); // Setup context for stack AZs - stack.construct.setContext( + stack.node.setContext( ContextProvider.getKey(stack, { provider: cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER, }).key, @@ -477,13 +477,13 @@ nodeunitShim({ const stack = new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); // Setup context for stack AZs - stack.construct.setContext( + stack.node.setContext( ContextProvider.getKey(stack, { provider: cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER, }).key, ['us-east-1a', 'us-east-1b', 'us-east-1c']); // Setup context for endpoint service AZs - stack.construct.setContext( + stack.node.setContext( ContextProvider.getKey(stack, { provider: cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER, props: { diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index d2156a067e899..113a8eb7edd56 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -807,7 +807,7 @@ nodeunitShim({ const vpc = new Vpc(stack, 'VpcNetwork'); - test.ok(vpc.publicSubnets[0].construct.defaultChild instanceof CfnSubnet); + test.ok(vpc.publicSubnets[0].node.defaultChild instanceof CfnSubnet); test.done(); }, @@ -1033,8 +1033,8 @@ nodeunitShim({ const vpc = new Vpc(stack, 'TheVPC'); // overwrite to set propagate - vpc.construct.applyAspect(new Tag('BusinessUnit', 'Marketing', {includeResourceTypes: [CfnVPC.CFN_RESOURCE_TYPE_NAME]})); - vpc.construct.applyAspect(new Tag('VpcType', 'Good')); + vpc.node.applyAspect(new Tag('BusinessUnit', 'Marketing', {includeResourceTypes: [CfnVPC.CFN_RESOURCE_TYPE_NAME]})); + vpc.node.applyAspect(new Tag('VpcType', 'Good')); expect(stack).to(haveResource('AWS::EC2::VPC', hasTags(toCfnTags(allTags)))); const taggables = ['Subnet', 'InternetGateway', 'NatGateway', 'RouteTable']; const propTags = toCfnTags(tags); @@ -1049,12 +1049,12 @@ nodeunitShim({ const stack = getTestStack(); const vpc = new Vpc(stack, 'TheVPC'); for (const subnet of vpc.publicSubnets) { - const tag = {Key: 'Name', Value: subnet.construct.path}; + const tag = {Key: 'Name', Value: subnet.node.path}; expect(stack).to(haveResource('AWS::EC2::NatGateway', hasTags([tag]))); expect(stack).to(haveResource('AWS::EC2::RouteTable', hasTags([tag]))); } for (const subnet of vpc.privateSubnets) { - const tag = {Key: 'Name', Value: subnet.construct.path}; + const tag = {Key: 'Name', Value: subnet.node.path}; expect(stack).to(haveResource('AWS::EC2::RouteTable', hasTags([tag]))); } test.done(); @@ -1065,7 +1065,7 @@ nodeunitShim({ const vpc = new Vpc(stack, 'TheVPC'); const tag = {Key: 'Late', Value: 'Adder'}; expect(stack).notTo(haveResource('AWS::EC2::VPC', hasTags([tag]))); - vpc.construct.applyAspect(new Tag(tag.Key, tag.Value)); + vpc.node.applyAspect(new Tag(tag.Key, tag.Value)); expect(stack).to(haveResource('AWS::EC2::VPC', hasTags([tag]))); test.done(); }, 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 07a297e9642b6..09c4a033a8476 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -112,7 +112,7 @@ export class DockerImageAsset extends Construct implements assets.IAsset { }); if (props.repositoryName) { - this.construct.addWarning('DockerImageAsset.repositoryName is deprecated. Override "core.Stack.addDockerImageAsset" to control asset locations'); + 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 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 d3affe4b26799..54930ec176e14 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 @@ -50,7 +50,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.deepEqual(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).buildArgs, { a: 'b' }); test.done(); }, @@ -69,7 +69,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.deepEqual(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).target, 'a-target'); test.done(); }, @@ -86,7 +86,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.deepEqual(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).file, 'Dockerfile.Custom'); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index 341307af9b88f..3869d38d8826a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -380,9 +380,9 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { */ protected getDefaultCluster(scope: cdk.Construct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs - const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.construct.id : ''}`; + const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; const stack = cdk.Stack.of(scope); - return stack.construct.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); + return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts index f813f1431a966..5764b3fcdaeb5 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts @@ -385,9 +385,9 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends Constru this.listeners.push(listener); } const domainName = this.createDomainName(lb, lbProps.domainName, lbProps.domainZone); - new CfnOutput(this, `LoadBalancerDNS${lb.construct.id}`, { value: lb.loadBalancerDnsName }); + new CfnOutput(this, `LoadBalancerDNS${lb.node.id}`, { value: lb.loadBalancerDnsName }); for (const protocol of protocolType) { - new CfnOutput(this, `ServiceURL${lb.construct.id}${protocol.toLowerCase()}`, { value: protocol.toLowerCase() + '://' + domainName }); + new CfnOutput(this, `ServiceURL${lb.node.id}${protocol.toLowerCase()}`, { value: protocol.toLowerCase() + '://' + domainName }); } } // set up default load balancer and listener. @@ -412,9 +412,9 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends Constru */ protected getDefaultCluster(scope: Construct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs. - const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.construct.id : ''}`; + const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; const stack = Stack.of(scope); - return stack.construct.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); + return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } protected createAWSLogDriver(prefix: string): AwsLogDriver { @@ -426,7 +426,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends Constru return this.listener; } for (const listener of this.listeners) { - if (listener.construct.id === name) { + if (listener.node.id === name) { return listener; } } @@ -474,7 +474,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends Constru const enableLogging = enableLoggingProp !== undefined ? enableLoggingProp : true; const logDriver = logDriverProp !== undefined ? logDriverProp : enableLogging - ? this.createAWSLogDriver(this.construct.id) : undefined; + ? this.createAWSLogDriver(this.node.id) : undefined; return logDriver; } @@ -558,7 +558,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends Constru throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name'); } - const record = new ARecord(this, `DNS${loadBalancer.construct.id}`, { + const record = new ARecord(this, `DNS${loadBalancer.node.id}`, { zone, recordName: name, target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)), diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 070fb96a4f868..567e952140b5b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -314,9 +314,9 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct { */ protected getDefaultCluster(scope: cdk.Construct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs - const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.construct.id : ''}`; + const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; const stack = cdk.Stack.of(scope); - return stack.construct.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); + return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts index 559451645a688..54ec00ef64b1a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts @@ -304,7 +304,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends Construct { this.listeners.push(listener); } this.createDomainName(lb, lbProps.domainName, lbProps.domainZone); - new CfnOutput(this, `LoadBalancerDNS${lb.construct.id}`, { value: lb.loadBalancerDnsName }); + new CfnOutput(this, `LoadBalancerDNS${lb.node.id}`, { value: lb.loadBalancerDnsName }); } // set up default load balancer and listener. this.loadBalancer = this.loadBalancers[0]; @@ -323,9 +323,9 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends Construct { */ protected getDefaultCluster(scope: Construct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs. - const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.construct.id : ''}`; + const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; const stack = Stack.of(scope); - return stack.construct.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); + return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } protected createAWSLogDriver(prefix: string): AwsLogDriver { @@ -337,7 +337,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends Construct { return this.listener; } for (const listener of this.listeners) { - if (listener.construct.id === name) { + if (listener.node.id === name) { return listener; } } @@ -380,7 +380,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends Construct { const enableLogging = enableLoggingProp !== undefined ? enableLoggingProp : true; const logDriver = logDriverProp !== undefined ? logDriverProp : enableLogging - ? this.createAWSLogDriver(this.construct.id) : undefined; + ? this.createAWSLogDriver(this.node.id) : undefined; return logDriver; } @@ -427,7 +427,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends Construct { throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name'); } - new ARecord(this, `DNS${loadBalancer.construct.id}`, { + new ARecord(this, `DNS${loadBalancer.node.id}`, { zone, recordName: name, target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)), diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts index f5a3f4c2b0e64..cd9e2f85f4633 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts @@ -256,7 +256,7 @@ export abstract class QueueProcessingServiceBase extends Construct { this.logDriver = props.logDriver !== undefined ? props.logDriver : enableLogging - ? this.createAWSLogDriver(this.construct.id) + ? this.createAWSLogDriver(this.node.id) : undefined; // Add the queue name to environment variables @@ -304,9 +304,9 @@ export abstract class QueueProcessingServiceBase extends Construct { */ protected getDefaultCluster(scope: Construct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs - const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.construct.id : ''}`; + const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; const stack = Stack.of(scope); - return stack.construct.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); + return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts index 5d972bfad47e6..47f14de90e59a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts @@ -161,9 +161,9 @@ export abstract class ScheduledTaskBase extends Construct { */ protected getDefaultCluster(scope: Construct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs - const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.construct.id : ''}`; + const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; const stack = Stack.of(scope); - return stack.construct.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); + return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index 46e37fd4fdd09..f38cdbba5e3ca 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -100,7 +100,7 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; const logDriver = taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.construct.id) : undefined; + ? this.createAWSLogDriver(this.node.id) : undefined; const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; const container = this.taskDefinition.addContainer(containerName, { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts index 8a1fb21af8995..f6738f52ac687 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts @@ -98,7 +98,7 @@ export class NetworkLoadBalancedEc2Service extends NetworkLoadBalancedServiceBas const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; const logDriver = taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.construct.id) : undefined; + ? this.createAWSLogDriver(this.node.id) : undefined; const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; const container = this.taskDefinition.addContainer(containerName, { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts index ab4f89b5f851f..7f0776dc812d4 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts @@ -107,7 +107,7 @@ export class ScheduledEc2Task extends ScheduledTaskBase { command: taskImageOptions.command, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, - logging: taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : this.createAWSLogDriver(this.construct.id), + logging: taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : this.createAWSLogDriver(this.node.id), }); } else { throw new Error('You must specify a taskDefinition or image'); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index b148eebbcd78f..baa8a191410dc 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -122,7 +122,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; const logDriver = taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.construct.id) : undefined; + ? this.createAWSLogDriver(this.node.id) : undefined; const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; const container = this.taskDefinition.addContainer(containerName, { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts index 91fe54ce83734..c86e279ab2ea1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts @@ -118,7 +118,7 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; const logDriver = taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.construct.id) : undefined; + ? this.createAWSLogDriver(this.node.id) : undefined; const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; const container = this.taskDefinition.addContainer(containerName, { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts index d4a79b3315bf8..e2c3dec274de8 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts @@ -103,7 +103,7 @@ export class ScheduledFargateTask extends ScheduledTaskBase { command: taskImageOptions.command, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, - logging: taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : this.createAWSLogDriver(this.construct.id), + logging: taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : this.createAWSLogDriver(this.node.id), }); } else { throw new Error('You must specify one of: taskDefinition or image'); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts index 152f41ff1e92f..745bad2c660fc 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s-v2.ts @@ -502,9 +502,9 @@ export = { }); // THEN - test.equal(ecsService.loadBalancer.construct.id, 'lb1'); - test.equal(ecsService.listener.construct.id, 'listener1'); - test.equal(ecsService.targetGroup.construct.id, 'ECSTargetGroupweb80Group'); + test.equal(ecsService.loadBalancer.node.id, 'lb1'); + test.equal(ecsService.listener.node.id, 'listener1'); + test.equal(ecsService.targetGroup.node.id, 'ECSTargetGroupweb80Group'); test.done(); }, @@ -1241,9 +1241,9 @@ export = { }); // THEN - test.equal(ecsService.loadBalancer.construct.id, 'lb1'); - test.equal(ecsService.listener.construct.id, 'listener1'); - test.equal(ecsService.targetGroup.construct.id, 'ECSTargetGroupweb80Group'); + test.equal(ecsService.loadBalancer.node.id, 'lb1'); + test.equal(ecsService.listener.node.id, 'listener1'); + test.equal(ecsService.targetGroup.node.id, 'ECSTargetGroupweb80Group'); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts index 9e0ac7a1e2403..f4ecc0f0a5616 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts @@ -1,7 +1,6 @@ import { Vpc } from '@aws-cdk/aws-ec2'; import { Cluster, ContainerImage } from '@aws-cdk/aws-ecs'; import { ApplicationProtocol } from '@aws-cdk/aws-elasticloadbalancingv2'; -import { HostedZone } from '@aws-cdk/aws-route53'; import { App, Stack } from '@aws-cdk/core'; import { ApplicationLoadBalancedFargateService } from '../../lib'; @@ -21,10 +20,13 @@ new ApplicationLoadBalancedFargateService(stack, 'myService', { protocol: ApplicationProtocol.HTTPS, enableECSManagedTags: true, domainName: 'test.example.com', - domainZone: HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { + domainZone: { hostedZoneId: 'fakeId', - zoneName: 'example.com', - }), + zoneName: 'example.com.', + hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', + stack, + node: stack.node, + }, }); app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index aed0414695b64..4b30dfa721480 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -3,7 +3,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2'; import * as iam from '@aws-cdk/aws-iam'; -import * as route53 from '@aws-cdk/aws-route53'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as ecsPatterns from '../../lib'; @@ -371,10 +370,13 @@ export = { cluster, protocol: ApplicationProtocol.HTTPS, domainName: 'domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'MyHostedZone', { + domainZone: { hostedZoneId: 'fakeId', zoneName: 'domain.com', - }), + hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', + stack, + node: stack.node, + }, taskImageOptions: { containerPort: 2015, image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), @@ -406,10 +408,13 @@ export = { cluster, protocol: ApplicationProtocol.HTTPS, domainName: 'test.domain.com', - domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'MyHostedZone', { + domainZone: { hostedZoneId: 'fakeId', zoneName: 'domain.com.', - }), + hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', + stack, + node: stack.node, + }, taskImageOptions: { containerPort: 2015, image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'), diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index eadff8f528b8a..3f00c03bc70af 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -356,7 +356,7 @@ export abstract class BaseService extends Resource }); if (props.deploymentController?.type === DeploymentControllerType.EXTERNAL) { - this.construct.addWarning('taskDefinition and launchType are blanked out when using external deployment controller.'); + this.node.addWarning('taskDefinition and launchType are blanked out when using external deployment controller.'); } this.serviceArn = this.getResourceArnAttribute(this.resource.ref, { @@ -675,7 +675,7 @@ export abstract class BaseService extends Resource // Service creation can only happen after the load balancer has // been associated with our target group(s), so add ordering dependency. - this.resource.construct.addDependency(targetGroup.loadBalancerAttached); + this.resource.node.addDependency(targetGroup.loadBalancerAttached); const targetType = this.taskDefinition.networkMode === NetworkMode.AWS_VPC ? elbv2.TargetType.IP : elbv2.TargetType.INSTANCE; return { targetType }; diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 8be911a2a0cfd..94bc5bbef39b4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -266,7 +266,7 @@ export class TaskDefinition extends TaskDefinitionBase { constructor(scope: Construct, id: string, props: TaskDefinitionProps) { super(scope, id); - this.family = props.family || this.construct.uniqueId; + this.family = props.family || this.node.uniqueId; this.compatibility = props.compatibility; if (props.volumes) { diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index cdcf56e8a494c..26da3b453bf46 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -377,7 +377,7 @@ export class ContainerDefinition extends cdk.Construct { this.taskDefinition = props.taskDefinition; this.memoryLimitSpecified = props.memoryLimitMiB !== undefined || props.memoryReservationMiB !== undefined; this.linuxParameters = props.linuxParameters; - this.containerName = this.construct.id; + this.containerName = this.node.id; this.imageConfig = props.image.bind(this, this); if (props.logging) { @@ -389,7 +389,7 @@ export class ContainerDefinition extends cdk.Construct { this.secrets = []; for (const [name, secret] of Object.entries(props.secrets)) { if (this.taskDefinition.isFargateCompatible && secret.hasField) { - throw new Error(`Cannot specify secret JSON field for a task using the FARGATE launch type: '${name}' in container '${this.construct.id}'`); + throw new Error(`Cannot specify secret JSON field for a task using the FARGATE launch type: '${name}' in container '${this.node.id}'`); } secret.grantRead(this.taskDefinition.obtainExecutionRole()); this.secrets.push({ diff --git a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts index b9419720d409c..95d3675d63e11 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts @@ -37,7 +37,7 @@ export class RepositoryImage extends ContainerImage { public bind(scope: Construct, containerDefinition: ContainerDefinition): ContainerImageConfig { // name could be a Token - in that case, skip validation altogether if (!Token.isUnresolved(this.imageName) && ECR_IMAGE_REGEX.test(this.imageName)) { - scope.construct.addWarning("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + scope.node.addWarning("Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); } if (this.props.credentials) { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index fb2a10c6823ec..6b74bcf656e9a 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -443,7 +443,7 @@ export = { }); // THEN - test.deepEqual(service.construct.metadata[0].data, 'taskDefinition and launchType are blanked out when using external deployment controller.'); + test.deepEqual(service.node.metadata[0].data, 'taskDefinition and launchType are blanked out when using external deployment controller.'); expect(stack).to(haveResource('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', 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 0559394e98e1a..a22c4801fcebb 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 @@ -519,7 +519,7 @@ export = { }); // THEN - test.deepEqual(container.construct.metadata[0].data, "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + test.deepEqual(container.node.metadata[0].data, "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); test.done(); }, @@ -538,7 +538,7 @@ export = { }); // THEN - test.deepEqual(container.construct.metadata[0].data, "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); + test.deepEqual(container.node.metadata[0].data, "Proper policies need to be attached before pulling from ECR repository, or use 'fromEcrRepository'."); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index 1ad1150804c67..991901512a6d2 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -321,7 +321,7 @@ export = { }); // THEN - test.deepEqual(service.construct.metadata[0].data, 'taskDefinition and launchType are blanked out when using external deployment controller.'); + test.deepEqual(service.node.metadata[0].data, 'taskDefinition and launchType are blanked out when using external deployment controller.'); expect(stack).to(haveResource('AWS::ECS::Service', { Cluster: { Ref: 'EcsCluster97242B84', diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index bf8b1aaff76e5..854503f388f7d 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -255,7 +255,7 @@ export class FileSystem extends Resource implements IFileSystem { filesystem.applyRemovalPolicy(props.removalPolicy); this.fileSystemId = filesystem.ref; - Tag.add(this, 'Name', props.fileSystemName || this.construct.path); + Tag.add(this, 'Name', props.fileSystemName || this.node.path); const securityGroup = (props.securityGroup || new ec2.SecurityGroup(this, 'EfsSecurityGroup', { vpc: props.vpc, diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index 370c6bdff566e..5104c069ec061 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -210,7 +210,7 @@ test('auto-named if none provided', () => { // THEN expectCDK(stack).to(haveResource('AWS::EFS::FileSystem', { FileSystemTags: [ - {Key: 'Name', Value: fileSystem.construct.path}, + {Key: 'Name', Value: fileSystem.node.path}, ], })); }); diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts index 19fa809081a66..0c5fcc2fb2e92 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -336,7 +336,7 @@ export class Cluster extends Resource implements ICluster { physicalName: props.clusterName, }); - this.construct.addWarning('The @aws-cdk/aws-eks-legacy module will no longer be released as part of the AWS CDK starting March 1st, 2020. Please refer to https://github.com/aws/aws-cdk/issues/5544 for upgrade instructions'); + this.node.addWarning('The @aws-cdk/aws-eks-legacy module will no longer be released as part of the AWS CDK starting March 1st, 2020. Please refer to https://github.com/aws/aws-cdk/issues/5544 for upgrade instructions'); const stack = Stack.of(this); @@ -636,11 +636,11 @@ export class Cluster extends Resource implements ICluster { // message (if token): "could not auto-tag public/private subnet with tag..." // message (if not token): "count not auto-tag public/private subnet xxxxx with tag..." const subnetID = Token.isUnresolved(subnet.subnetId) ? '' : ` ${subnet.subnetId}`; - this.construct.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); + this.node.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); continue; } - subnet.construct.applyAspect(new Tag(tag, '1')); + subnet.node.applyAspect(new Tag(tag, '1')); } }; diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts index 03170db25fbca..714fc97e4da0d 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts @@ -84,7 +84,7 @@ export class HelmChart extends Construct { provider: CustomResourceProvider.lambda(handler), resourceType: HelmChart.RESOURCE_TYPE, properties: { - Release: props.release || this.construct.uniqueId.slice(-63).toLowerCase(), // Helm has a 63 character limit for the name + Release: props.release || this.node.uniqueId.slice(-63).toLowerCase(), // Helm has a 63 character limit for the name Chart: props.chart, Version: props.version, Values: (props.values ? stack.toJsonString(props.values) : undefined), @@ -99,7 +99,7 @@ export class HelmChart extends Construct { return undefined; } - let handler = cluster.construct.tryFindChild('HelmChartHandler') as lambda.IFunction; + let handler = cluster.node.tryFindChild('HelmChartHandler') as lambda.IFunction; if (!handler) { handler = new lambda.Function(cluster, 'HelmChartHandler', { code: lambda.Code.fromAsset(path.join(__dirname, 'helm-chart')), diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts b/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts index 79491964f295e..73168622e4ff4 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts @@ -27,7 +27,7 @@ export class KubectlLayer extends Construct implements lambda.ILayerVersion { public static getOrCreate(scope: Construct, props: KubectlLayerProps = {}): KubectlLayer { const stack = Stack.of(scope); const id = 'kubectl-layer-' + (props.version ? props.version : '8C2542BC-BF2B-4DFE-B765-E181FD30A9A0'); - const exists = stack.construct.tryFindChild(id) as KubectlLayer; + const exists = stack.node.tryFindChild(id) as KubectlLayer; if (exists) { return exists; } @@ -48,7 +48,7 @@ export class KubectlLayer extends Construct implements lambda.ILayerVersion { constructor(scope: Construct, id: string, props: KubectlLayerProps = {}) { super(scope, id); - const uniqueId = crypto.createHash('md5').update(this.construct.path).digest('hex'); + const uniqueId = crypto.createHash('md5').update(this.node.path).digest('hex'); const version = props.version || KUBECTL_APP_VERSION; this.stack.templateOptions.transforms = [ 'AWS::Serverless-2016-10-31' ]; // required for AWS::Serverless diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts b/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts index 89206b26a928d..e889663520f32 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts @@ -7,7 +7,7 @@ export function renderUserData(clusterName: string, autoScalingGroup: autoscalin const stack = Stack.of(autoScalingGroup); // determine logical id of ASG so we can signal cloudformation - const cfn = autoScalingGroup.construct.defaultChild as autoscaling.CfnAutoScalingGroup; + const cfn = autoScalingGroup.node.defaultChild as autoscaling.CfnAutoScalingGroup; const asgLogicalId = cfn.logicalId; const extraArgs = new Array(); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts index 3df44f8147841..f9c1a592586ca 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts @@ -19,7 +19,7 @@ export class ClusterResourceProvider extends NestedStack { public static getOrCreate(scope: Construct) { const stack = Stack.of(scope); const uid = '@aws-cdk/aws-eks.ClusterResourceProvider'; - return stack.construct.tryFindChild(uid) as ClusterResourceProvider || new ClusterResourceProvider(stack, uid); + return stack.node.tryFindChild(uid) as ClusterResourceProvider || new ClusterResourceProvider(stack, uid); } /** diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index c6f037c075140..c885feaa476d2 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -162,7 +162,7 @@ export class ClusterResource extends Construct { }, }); - resource.construct.addDependency(this.creationRole); + resource.node.addDependency(this.creationRole); this.ref = resource.ref; this.attrEndpoint = Token.asString(resource.getAtt('Endpoint')); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 65d24f12101be..dbc34c53353af 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -637,7 +637,7 @@ export class Cluster extends Resource implements ICluster { // the security group and vpc must exist in order to properly delete the cluster (since we run `kubectl delete`). // this ensures that. - this._clusterResource.construct.addDependency(this.kubctlProviderSecurityGroup, this.vpc); + this._clusterResource.node.addDependency(this.kubctlProviderSecurityGroup, this.vpc); // see https://github.com/aws/aws-cdk/issues/9027 this._clusterResource.creationRole.addToPolicy(new iam.PolicyStatement({ @@ -659,7 +659,7 @@ export class Cluster extends Resource implements ICluster { }); // add the cluster resource itself as a dependency of the barrier - this._kubectlReadyBarrier.construct.addDependency(this._clusterResource); + this._kubectlReadyBarrier.node.addDependency(this._clusterResource); this.clusterName = this.getResourceNameAttribute(resource.ref); this.clusterArn = this.getResourceArnAttribute(resource.attrArn, clusterArnComponents(this.physicalName)); @@ -988,7 +988,7 @@ export class Cluster extends Resource implements ICluster { // add all profiles as a dependency of the "kubectl-ready" barrier because all kubectl- // resources can only be deployed after all fargate profiles are created. if (this._kubectlReadyBarrier) { - this._kubectlReadyBarrier.construct.addDependency(fargateProfile); + this._kubectlReadyBarrier.node.addDependency(fargateProfile); } return this._fargateProfiles; @@ -1009,7 +1009,7 @@ export class Cluster extends Resource implements ICluster { const uid = '@aws-cdk/aws-eks.KubectlProvider'; // singleton - let provider = this.stack.construct.tryFindChild(uid) as KubectlProvider; + let provider = this.stack.node.tryFindChild(uid) as KubectlProvider; if (!provider) { // create the provider. @@ -1046,7 +1046,7 @@ export class Cluster extends Resource implements ICluster { throw new Error('unexpected: kubectl enabled clusters should have a kubectl-ready barrier resource'); } - resourceScope.construct.addDependency(this._kubectlReadyBarrier); + resourceScope.node.addDependency(this._kubectlReadyBarrier); return provider; } @@ -1130,11 +1130,11 @@ export class Cluster extends Resource implements ICluster { // message (if token): "could not auto-tag public/private subnet with tag..." // message (if not token): "count not auto-tag public/private subnet xxxxx with tag..." const subnetID = Token.isUnresolved(subnet.subnetId) ? '' : ` ${subnet.subnetId}`; - this.construct.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); + this.node.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); continue; } - subnet.construct.applyAspect(new Tag(tag, '1')); + subnet.node.applyAspect(new Tag(tag, '1')); } }; diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts index bef0ce6cf6bfc..428d96ae24b36 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts @@ -189,7 +189,7 @@ export class FargateProfile extends Construct implements ITaggable { const clusterFargateProfiles = props.cluster._attachFargateProfile(this); if (clusterFargateProfiles.length > 1) { const previousProfile = clusterFargateProfiles[clusterFargateProfiles.length - 2]; - resource.construct.addDependency(previousProfile); + resource.node.addDependency(previousProfile); } // map the fargate pod execution role to the relevant groups in rbac diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 5207b94ea4ba8..b46f0f8bbe09d 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -107,7 +107,7 @@ export class HelmChart extends Construct { properties: { ClusterName: props.cluster.clusterName, RoleArn: props.cluster._kubectlCreationRole.roleArn, - Release: props.release ?? this.construct.uniqueId.slice(-53).toLowerCase(), // Helm has a 53 character limit for the name + Release: props.release ?? this.node.uniqueId.slice(-53).toLowerCase(), // Helm has a 53 character limit for the name Chart: props.chart, Version: props.version, Wait: wait || undefined, // props are stringified so we encode “false” as undefined diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts index fc2710c13b401..84efa5325854a 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts @@ -29,7 +29,7 @@ export class KubectlLayer extends Construct implements lambda.ILayerVersion { public static getOrCreate(scope: Construct, props: KubectlLayerProps = {}): KubectlLayer { const stack = Stack.of(scope); const id = 'kubectl-layer-' + (props.version ? props.version : '8C2542BC-BF2B-4DFE-B765-E181FD30A9A0'); - const exists = stack.construct.tryFindChild(id) as KubectlLayer; + const exists = stack.node.tryFindChild(id) as KubectlLayer; if (exists) { return exists; } @@ -50,7 +50,7 @@ export class KubectlLayer extends Construct implements lambda.ILayerVersion { constructor(scope: Construct, id: string, props: KubectlLayerProps = {}) { super(scope, id); - const uniqueId = crypto.createHash('md5').update(this.construct.path).digest('hex'); + const uniqueId = crypto.createHash('md5').update(this.node.path).digest('hex'); const version = props.version || KUBECTL_APP_VERSION; this.stack.templateOptions.transforms = [ 'AWS::Serverless-2016-10-31' ]; // required for AWS::Serverless diff --git a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts index f9dca4ace1bb7..3e2ba0feb9abd 100644 --- a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts @@ -361,11 +361,11 @@ export class LegacyCluster extends Resource implements ICluster { // message (if token): "could not auto-tag public/private subnet with tag..." // message (if not token): "count not auto-tag public/private subnet xxxxx with tag..." const subnetID = Token.isUnresolved(subnet.subnetId) ? '' : ` ${subnet.subnetId}`; - this.construct.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); + this.node.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); continue; } - subnet.construct.applyAspect(new Tag(tag, '1')); + subnet.node.applyAspect(new Tag(tag, '1')); } }; diff --git a/packages/@aws-cdk/aws-eks/lib/service-account.ts b/packages/@aws-cdk/aws-eks/lib/service-account.ts index 665f6418b5278..83da66fbfef73 100644 --- a/packages/@aws-cdk/aws-eks/lib/service-account.ts +++ b/packages/@aws-cdk/aws-eks/lib/service-account.ts @@ -57,7 +57,7 @@ export class ServiceAccount extends Construct implements IPrincipal { super(scope, id); const { cluster } = props; - this.serviceAccountName = props.name ?? this.construct.uniqueId.toLowerCase(); + this.serviceAccountName = props.name ?? this.node.uniqueId.toLowerCase(); this.serviceAccountNamespace = props.namespace ?? 'default'; /* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role. diff --git a/packages/@aws-cdk/aws-eks/lib/user-data.ts b/packages/@aws-cdk/aws-eks/lib/user-data.ts index 5cad1fc2a57df..cf38cf7ee9761 100644 --- a/packages/@aws-cdk/aws-eks/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/user-data.ts @@ -8,7 +8,7 @@ export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: const stack = Stack.of(autoScalingGroup); // determine logical id of ASG so we can signal cloudformation - const cfn = autoScalingGroup.construct.defaultChild as autoscaling.CfnAutoScalingGroup; + const cfn = autoScalingGroup.node.defaultChild as autoscaling.CfnAutoScalingGroup; const asgLogicalId = cfn.logicalId; const extraArgs = new Array(); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index 38b9ab11dc606..6340be221a298 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -99,7 +99,7 @@ class EksClusterStack extends TestStack { }); // make sure namespace is deployed before the chart - nginxIngress.construct.addDependency(nginxNamespace); + nginxIngress.node.addDependency(nginxNamespace); // add a service account connected to a IAM role cluster.addServiceAccount('MyServiceAccount'); diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 19d05afb89853..576e4081e83c0 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -1386,7 +1386,7 @@ export = { }); // the kubectl provider is inside a nested stack. - const nested = stack.construct.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; expect(nested).to(haveResource('AWS::Lambda::Function', { Environment: { Variables: { @@ -1427,7 +1427,7 @@ export = { region: 'us-east-1', }, }); - stack.construct.setContext(`vpc-provider:account=${stack.account}:filter.vpc-id=${vpcId}:region=${stack.region}:returnAsymmetricSubnets=true`, { + stack.node.setContext(`vpc-provider:account=${stack.account}:filter.vpc-id=${vpcId}:region=${stack.region}:returnAsymmetricSubnets=true`, { vpcId: vpcId, vpcCidrBlock: '10.0.0.0/16', subnetGroups: [ @@ -1467,7 +1467,7 @@ export = { endpointAccess: eks.EndpointAccess.PRIVATE, }); - const nested = stack.construct.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; const template = expect(nested).value; test.deepEqual(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds, [ @@ -1488,7 +1488,7 @@ export = { })]}], }); - const nested = stack.construct.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; const template = expect(nested).value; test.deepEqual(template.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds, [ @@ -1588,7 +1588,7 @@ export = { }); // the kubectl provider is inside a nested stack. - const nested = stack.construct.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; expect(nested).to(haveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ @@ -1652,7 +1652,7 @@ export = { }); // the kubectl provider is inside a nested stack. - const nested = stack.construct.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; test.equal(16, expect(nested).value.Resources.Handler886CB40B.Properties.VpcConfig.SubnetIds.length); test.done(); @@ -1701,7 +1701,7 @@ export = { }); // the kubectl provider is inside a nested stack. - const nested = stack.construct.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; expect(nested).to(haveResource('AWS::Lambda::Function', { VpcConfig: { SecurityGroupIds: [ diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index 3e8096a61d297..d4c36abe15221 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -248,7 +248,7 @@ export class LoadBalancer extends Resource implements IConnectable { crossZone: (props.crossZone === undefined || props.crossZone) ? true : false, }); if (props.internetFacing) { - this.elb.construct.addDependency(selectedSubnets.internetConnectivityEstablished); + this.elb.node.addDependency(selectedSubnets.internetConnectivityEstablished); } ifUndefined(props.listeners, []).forEach(b => this.addListener(b)); diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts index 550a21f520ec2..fdbfca95ae4eb 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts @@ -7,7 +7,7 @@ import { ILoadBalancerTarget, LoadBalancer, LoadBalancingProtocol } from '../lib export = { 'test specifying nonstandard port works'(test: Test) { const stack = new Stack(undefined, undefined, { env: { account: '1234', region: 'test' }}); - stack.construct.setContext('availability-zones:1234:test', ['test-1a', 'test-1b']); + stack.node.setContext('availability-zones:1234:test', ['test-1a', 'test-1b']); const vpc = new Vpc(stack, 'VCP'); const lb = new LoadBalancer(stack, 'LB', { vpc }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts index aa4172256a44e..f9ab2c015b382 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/test/integ.cognito.lit.ts @@ -44,7 +44,7 @@ class CognitoStack extends Stack { ], }, }); - const cfnClient = userPoolClient.construct.defaultChild as cognito.CfnUserPoolClient; + const cfnClient = userPoolClient.node.defaultChild as cognito.CfnUserPoolClient; cfnClient.addPropertyOverride('RefreshTokenValidity', 1); cfnClient.addPropertyOverride('SupportedIdentityProviders', ['COGNITO']); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index 3234c5b249532..a8de884e9f2c0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -298,7 +298,7 @@ export class ApplicationListenerRule extends cdk.Construct { // Instead, signal this through a warning. // @deprecate: upon the next major version bump, replace this with a `throw` if (this.action) { - this.construct.addWarning('An Action already existed on this ListenerRule and was replaced. Configure exactly one default Action.'); + this.node.addWarning('An Action already existed on this ListenerRule and was replaced. Configure exactly one default Action.'); } action.bind(this, this.listener, this); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index be195010d03bf..a9e469394b8c3 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -69,7 +69,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic this.ipAddressType = props.ipAddressType ?? IpAddressType.IPV4; this.securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, - description: `Automatically created Security Group for ELB ${this.construct.uniqueId}`, + description: `Automatically created Security Group for ELB ${this.node.uniqueId}`, allowAllOutbound: false, }); this.connections = new ec2.Connections({ securityGroups: [this.securityGroup] }); @@ -562,12 +562,12 @@ class ImportedApplicationLoadBalancer extends Resource implements IApplicationLo public get loadBalancerCanonicalHostedZoneId(): string { if (this.props.loadBalancerCanonicalHostedZoneId) { return this.props.loadBalancerCanonicalHostedZoneId; } // eslint-disable-next-line max-len - throw new Error(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Application Load Balancer ${this.construct.path} from attributes`); + throw new Error(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Application Load Balancer ${this.node.path} from attributes`); } public get loadBalancerDnsName(): string { if (this.props.loadBalancerDnsName) { return this.props.loadBalancerDnsName; } // eslint-disable-next-line max-len - throw new Error(`'loadBalancerDnsName' was not provided when constructing Application Load Balancer ${this.construct.path} from attributes`); + throw new Error(`'loadBalancerDnsName' was not provided when constructing Application Load Balancer ${this.node.path} from attributes`); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 66e251538ff39..9d6561356f5e9 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -374,11 +374,11 @@ export interface IApplicationTargetGroup extends ITargetGroup { class ImportedApplicationTargetGroup extends ImportedTargetGroupBase implements IApplicationTargetGroup { public registerListener(_listener: IApplicationListener, _associatingConstruct?: IConstruct) { // Nothing to do, we know nothing of our members - this.construct.addWarning('Cannot register listener on imported target group -- security groups might need to be updated manually'); + this.node.addWarning('Cannot register listener on imported target group -- security groups might need to be updated manually'); } public registerConnectable(_connectable: ec2.IConnectable, _portRange?: ec2.Port | undefined): void { - this.construct.addWarning('Cannot register connectable on imported target group -- security groups might need to be updated manually'); + this.node.addWarning('Cannot register connectable on imported target group -- security groups might need to be updated manually'); } public addTarget(...targets: IApplicationLoadBalancerTarget[]) { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index 4c197a6b71347..28e23cc56a257 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -70,13 +70,13 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa public get loadBalancerCanonicalHostedZoneId(): string { if (attrs.loadBalancerCanonicalHostedZoneId) { return attrs.loadBalancerCanonicalHostedZoneId; } // eslint-disable-next-line max-len - throw new Error(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Network Load Balancer ${this.construct.path} from attributes`); + throw new Error(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Network Load Balancer ${this.node.path} from attributes`); } public get loadBalancerDnsName(): string { if (attrs.loadBalancerDnsName) { return attrs.loadBalancerDnsName; } // eslint-disable-next-line max-len - throw new Error(`'loadBalancerDnsName' was not provided when constructing Network Load Balancer ${this.construct.path} from attributes`); + throw new Error(`'loadBalancerDnsName' was not provided when constructing Network Load Balancer ${this.node.path} from attributes`); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 767af7db4eb66..d9405bff1f729 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -50,7 +50,7 @@ export abstract class BaseListener extends Resource { // Instead, signal this through a warning. // @deprecate: upon the next major version bump, replace this with a `throw` if (this.defaultAction) { - this.construct.addWarning('A default Action already existed on this Listener and was replaced. Configure exactly one default Action.'); + this.node.addWarning('A default Action already existed on this Listener and was replaced. Configure exactly one default Action.'); } this.defaultAction = action; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 6e6693b3081db..7c7447d19eba0 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -142,7 +142,7 @@ export abstract class BaseLoadBalancer extends Resource { ...additionalProps, }); if (internetFacing) { - resource.construct.addDependency(internetConnectivityEstablished); + resource.node.addDependency(internetConnectivityEstablished); } if (baseProps.deletionProtection) { this.setAttribute('deletion_protection.enabled', 'true'); } @@ -180,7 +180,7 @@ export abstract class BaseLoadBalancer extends Resource { bucket.grantPut(new iam.AccountPrincipal(account), `${(prefix ? prefix + '/' : '')}AWSLogs/${Stack.of(this).account}/*`); // make sure the bucket's policy is created before the ALB (see https://github.com/aws/aws-cdk/issues/1633) - this.construct.addDependency(bucket); + this.node.addDependency(bucket); } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index affdbc2b8f5bd..f35642e6d4c9e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -313,7 +313,7 @@ export abstract class TargetGroupBase extends cdk.Construct implements ITargetGr const ret = super.validate(); if (this.targetType === undefined && this.targetsJson.length === 0) { - this.construct.addWarning("When creating an empty TargetGroup, you should specify a 'targetType' (this warning may become an error in the future)."); + this.node.addWarning("When creating an empty TargetGroup, you should specify a 'targetType' (this warning may become an error in the future)."); } if (this.targetType !== TargetType.LAMBDA && this.vpc === undefined) { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 86cec342bd85f..188fd80ea4eea 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -1359,6 +1359,6 @@ export = { class ResourceWithLBDependency extends cdk.CfnResource { constructor(scope: cdk.Construct, id: string, targetGroup: elbv2.ITargetGroup) { super(scope, id, { type: 'Test::Resource' }); - this.construct.addDependency(targetGroup.loadBalancerAttached); + this.node.addDependency(targetGroup.loadBalancerAttached); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.ts index 53778415690d5..5ce09e6f82ef3 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.dualstack.ts @@ -38,7 +38,7 @@ const ipv6Block = new ec2.CfnVPCCidrBlock( // Get the vpc's internet gateway so we can create default routes for the // public subnets. const internetGateway = valueOrDie( - vpc.construct.children.find(c => c instanceof ec2.CfnInternetGateway), + vpc.node.children.find(c => c instanceof ec2.CfnInternetGateway), new Error('Couldnt find an internet gateway'), ); @@ -54,7 +54,7 @@ vpc.publicSubnets.forEach((subnet, idx) => { // Find a CfnSubnet (raw cloudformation resources) child to the public // subnet nodes. const cfnSubnet = valueOrDie( - subnet.construct.children.find(c => c instanceof ec2.CfnSubnet), + subnet.node.children.find(c => c instanceof ec2.CfnSubnet), new Error('Couldnt find a CfnSubnet'), ); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts index b3fac7df3efae..2b6ad8cff5c90 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts @@ -24,7 +24,7 @@ const group = listener.addTargets('Target', { targets: [new elbv2.IpTarget('10.0.1.1')], }); -group.construct.addDependency(vpc.internetConnectivityEstablished); +group.node.addDependency(vpc.internetConnectivityEstablished); // The target's security group must allow being routed by the LB and the clients. diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts index 16eba5fb4766e..a94421e357154 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts @@ -339,6 +339,6 @@ export = { class ResourceWithLBDependency extends cdk.CfnResource { constructor(scope: cdk.Construct, id: string, targetGroup: elbv2.ITargetGroup) { super(scope, id, { type: 'Test::Resource' }); - this.construct.addDependency(targetGroup.loadBalancerAttached); + this.node.addDependency(targetGroup.loadBalancerAttached); } } diff --git a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts index b40ae306d106a..9eade462aae13 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts @@ -81,7 +81,7 @@ export class AwsApi implements events.IRuleTarget { * result from an EventBridge event. */ public bind(rule: events.IRule, id?: string): events.RuleTargetConfig { - const handler = new lambda.SingletonFunction(rule as events.Rule, `${rule.construct.id}${id}Handler`, { + const handler = new lambda.SingletonFunction(rule as events.Rule, `${rule.node.id}${id}Handler`, { code: lambda.Code.fromAsset(path.join(__dirname, 'aws-api-handler')), runtime: lambda.Runtime.NODEJS_12_X, handler: 'index.handler', diff --git a/packages/@aws-cdk/aws-events-targets/lib/batch.ts b/packages/@aws-cdk/aws-events-targets/lib/batch.ts index 2e45b693137fd..69f9a52fdbb35 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/batch.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/batch.ts @@ -59,7 +59,7 @@ export class BatchJob implements events.IRuleTarget { public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { const batchParameters: events.CfnRule.BatchParametersProperty = { jobDefinition: this.jobDefinition.jobDefinitionArn, - jobName: this.props.jobName ?? rule.construct.uniqueId, + jobName: this.props.jobName ?? rule.node.uniqueId, arrayProperties: this.props.size ? { size: this.props.size } : undefined, retryStrategy: this.props.attempts ? { attempts: this.props.attempts } : undefined, }; diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index 7c48c4ce8216d..77fdae5d37208 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -115,7 +115,7 @@ export class EcsTask implements events.IRuleTarget { // Security groups are only configurable with the "awsvpc" network mode. if (this.taskDefinition.networkMode !== ecs.NetworkMode.AWS_VPC) { if (props.securityGroup !== undefined || props.securityGroups !== undefined) { - this.taskDefinition.construct.addWarning('security groups are ignored when network mode is not awsvpc'); + this.taskDefinition.node.addWarning('security groups are ignored when network mode is not awsvpc'); } return; } @@ -123,7 +123,7 @@ export class EcsTask implements events.IRuleTarget { this.securityGroups = props.securityGroups; return; } - let securityGroup = props.securityGroup || this.taskDefinition.construct.tryFindChild('SecurityGroup') as ec2.ISecurityGroup; + let securityGroup = props.securityGroup || this.taskDefinition.node.tryFindChild('SecurityGroup') as ec2.ISecurityGroup; securityGroup = securityGroup || new ec2.SecurityGroup(this.taskDefinition, 'SecurityGroup', { vpc: this.props.cluster.vpc }); this.securityGroup = securityGroup; // Maintain backwards-compatibility for customers that read the generated security group. this.securityGroups = [securityGroup]; diff --git a/packages/@aws-cdk/aws-events-targets/lib/util.ts b/packages/@aws-cdk/aws-events-targets/lib/util.ts index a116aab8849cd..ddcd83adb5f1b 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/util.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/util.ts @@ -11,7 +11,7 @@ import { Construct, IConstruct } from '@aws-cdk/core'; */ export function singletonEventRole(scope: IConstruct, policyStatements: iam.PolicyStatement[]): iam.IRole { const id = 'EventsRole'; - const existing = scope.construct.tryFindChild(id) as iam.IRole; + const existing = scope.node.tryFindChild(id) as iam.IRole; if (existing) { return existing; } const role = new iam.Role(scope as Construct, id, { @@ -27,7 +27,7 @@ export function singletonEventRole(scope: IConstruct, policyStatements: iam.Poli * Allows a Lambda function to be called from a rule */ export function addLambdaPermission(rule: events.IRule, handler: lambda.IFunction): void { - const permissionId = `AllowEventRule${rule.construct.uniqueId}`; + const permissionId = `AllowEventRule${rule.node.uniqueId}`; if (!handler.permissionsNode.tryFindChild(permissionId)) { handler.addPermission(permissionId, { action: 'lambda:InvokeFunction', diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts index 7a3ff8a30e1a7..3e2b5e524ba3b 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts @@ -95,7 +95,7 @@ describe('CodePipeline event target', () => { const role = new iam.Role(stack, 'MyExampleRole', { assumedBy: new iam.AnyPrincipal(), }); - const roleResource = role.construct.defaultChild as CfnElement; + const roleResource = role.node.defaultChild as CfnElement; roleResource.overrideLogicalId('MyRole'); // to make it deterministic in the assertion below rule.addTarget(new targets.CodePipeline(pipeline, { diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index 1697cec293e1a..366c259685527 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -218,7 +218,7 @@ export class EventBus extends Resource implements IEventBus { constructor(scope: Construct, id: string, props?: EventBusProps) { const { eventBusName, eventSourceName } = EventBus.eventBusProps( - Lazy.stringValue({ produce: () => this.construct.uniqueId }), + Lazy.stringValue({ produce: () => this.node.uniqueId }), props, ); diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index a7d630087ce48..1b19b5f1174a9 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -221,11 +221,11 @@ export class Rule extends Resource implements IRule { // (EventBridge verifies whether you have permissions to the targets on rule creation), // but it's common for the target stack to depend on the source stack // (that's the case with CodePipeline, for example) - const sourceApp = this.construct.root; + const sourceApp = this.node.root; if (!sourceApp || !App.isApp(sourceApp)) { throw new Error('Event stack which uses cross-account targets must be part of a CDK app'); } - const targetApp = targetProps.targetResource.construct.root; + const targetApp = targetProps.targetResource.node.root; if (!targetApp || !App.isApp(targetApp)) { throw new Error('Target stack which uses cross-account event targets must be part of a CDK app'); } @@ -233,7 +233,7 @@ export class Rule extends Resource implements IRule { throw new Error('Event stack and target stack must belong to the same CDK app'); } const stackId = `EventBusPolicy-${sourceAccount}-${targetRegion}-${targetAccount}`; - let eventBusPolicyStack: Stack = sourceApp.construct.tryFindChild(stackId) as Stack; + let eventBusPolicyStack: Stack = sourceApp.node.tryFindChild(stackId) as Stack; if (!eventBusPolicyStack) { eventBusPolicyStack = new Stack(sourceApp, stackId, { env: { @@ -275,7 +275,7 @@ export class Rule extends Resource implements IRule { } } - new CopyRule(targetStack, `${this.construct.uniqueId}-${id}`, { + new CopyRule(targetStack, `${this.node.uniqueId}-${id}`, { targets: [target], eventPattern: this.eventPattern, schedule: this.scheduleExpression ? Schedule.expression(this.scheduleExpression) : undefined, diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index a6c9cc106e475..e6ae493b60ab7 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -361,7 +361,7 @@ export = { const t1: IRuleTarget = { bind: (eventRule: IRule) => { receivedRuleArn = eventRule.ruleArn; - receivedRuleId = eventRule.construct.uniqueId; + receivedRuleId = eventRule.node.uniqueId; return { id: '', @@ -375,7 +375,7 @@ export = { rule.addTarget(t1); test.deepEqual(stack.resolve(receivedRuleArn), stack.resolve(rule.ruleArn)); - test.deepEqual(receivedRuleId, rule.construct.uniqueId); + test.deepEqual(receivedRuleId, rule.node.uniqueId); test.done(); }, @@ -678,7 +678,7 @@ export = { ], })); - const eventBusPolicyStack = app.construct.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; + const eventBusPolicyStack = app.node.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; expect(eventBusPolicyStack).to(haveResourceLike('AWS::Events::EventBusPolicy', { 'Action': 'events:PutEvents', 'StatementId': `Allow-account-${sourceAccount}`, diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index c8a13826295d4..3ed844d2d600a 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -284,7 +284,7 @@ export class Table extends Resource implements ITable { resource: 'table', resourceName: `${this.database.databaseName}/${this.tableName}`, }); - this.construct.defaultChild = tableResource; + this.node.defaultChild = tableResource; } /** diff --git a/packages/@aws-cdk/aws-glue/test/table.test.ts b/packages/@aws-cdk/aws-glue/test/table.test.ts index 5989bd075120d..a35d48e3d93dd 100644 --- a/packages/@aws-cdk/aws-glue/test/table.test.ts +++ b/packages/@aws-cdk/aws-glue/test/table.test.ts @@ -220,7 +220,7 @@ test('compressed table', () => { }); -test('table.construct.defaultChild', () => { +test('table.node.defaultChild', () => { // GIVEN const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { @@ -240,7 +240,7 @@ test('table.construct.defaultChild', () => { }); // THEN - ok(table.construct.defaultChild instanceof glue.CfnTable); + ok(table.node.defaultChild instanceof glue.CfnTable); }); test('encrypted table: SSE-S3', () => { diff --git a/packages/@aws-cdk/aws-iam/lib/grant.ts b/packages/@aws-cdk/aws-iam/lib/grant.ts index d533ea2c3902d..c0cb065f7ee2c 100644 --- a/packages/@aws-cdk/aws-iam/lib/grant.ts +++ b/packages/@aws-cdk/aws-iam/lib/grant.ts @@ -263,11 +263,11 @@ export class Grant implements cdk.IDependable { /** * Make sure this grant is applied before the given constructs are deployed * - * The same as construct.construct.addDependency(grant), but slightly nicer to read. + * The same as construct.node.addDependency(grant), but slightly nicer to read. */ public applyBefore(...constructs: cdk.IConstruct[]) { for (const construct of constructs) { - construct.construct.addDependency(this); + construct.node.addDependency(this); } } } diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index dd5855242bb7e..23cde93dc101c 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -417,7 +417,7 @@ export class Role extends Resource implements IRole { */ public withoutPolicyUpdates(): IRole { if (!this.immutableRole) { - this.immutableRole = new ImmutableRole(this.construct.scope as Construct, `ImmutableRole${this.construct.id}`, this); + this.immutableRole = new ImmutableRole(this.node.scope as Construct, `ImmutableRole${this.node.id}`, this); } return this.immutableRole; diff --git a/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts b/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts index 2486ff9f9bd60..da30dbf08227e 100644 --- a/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts +++ b/packages/@aws-cdk/aws-iam/lib/unknown-principal.ts @@ -34,13 +34,13 @@ export class UnknownPrincipal implements IPrincipal { } public get policyFragment(): PrincipalPolicyFragment { - throw new Error(`Cannot get policy fragment of ${this.resource.construct.path}, resource imported without a role`); + throw new Error(`Cannot get policy fragment of ${this.resource.node.path}, resource imported without a role`); } public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult { const stack = Stack.of(this.resource); const repr = JSON.stringify(stack.resolve(statement)); - this.resource.construct.addWarning(`Add statement to this resource's role: ${repr}`); + this.resource.node.addWarning(`Add statement to this resource's role: ${repr}`); // Pretend we did the work. The human will do it for us, eventually. return { statementAdded: true, policyDependable: new ConcreteDependable() }; } diff --git a/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts b/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts index 847120c86d05b..4aa2ce2e9ddb8 100644 --- a/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts +++ b/packages/@aws-cdk/aws-iam/test/escape-hatch.test.ts @@ -14,7 +14,7 @@ describe('IAM escape hatches', () => { userName: 'MyUserName', }); - const cfn = user.construct.findChild('Resource') as iam.CfnUser; + const cfn = user.node.findChild('Resource') as iam.CfnUser; cfn.addPropertyOverride('UserName', 'OverriddenUserName'); expect(stack).toMatchTemplate({ @@ -33,7 +33,7 @@ describe('IAM escape hatches', () => { // GIVEN const stack = new Stack(); const user = new iam.User(stack, 'user', { userName: 'MyUserName' }); - const cfn = user.construct.findChild('Resource') as iam.CfnUser; + const cfn = user.node.findChild('Resource') as iam.CfnUser; // WHEN cfn.addPropertyOverride('Hello.World', 'Boom'); @@ -58,7 +58,7 @@ describe('IAM escape hatches', () => { // GIVEN const stack = new Stack(); const user = new iam.User(stack, 'user', { userName: 'MyUserName' }); - const cfn = user.construct.findChild('Resource') as iam.CfnUser; + const cfn = user.node.findChild('Resource') as iam.CfnUser; cfn.cfnOptions.updatePolicy = { useOnlineResharding: true }; // WHEN diff --git a/packages/@aws-cdk/aws-iam/test/policy.test.ts b/packages/@aws-cdk/aws-iam/test/policy.test.ts index 9c85bf9d7e56c..5bd50db9fd64f 100644 --- a/packages/@aws-cdk/aws-iam/test/policy.test.ts +++ b/packages/@aws-cdk/aws-iam/test/policy.test.ts @@ -262,7 +262,7 @@ describe('IAM policy', () => { }); // WHEN - res.construct.addDependency(pol); + res.node.addDependency(pol); // THEN expect(stack).toMatchTemplate({ @@ -288,7 +288,7 @@ describe('IAM policy', () => { }); // WHEN - res.construct.addDependency(pol); + res.node.addDependency(pol); // THEN expect(stack).toHaveResource('Some::Resource', { @@ -325,7 +325,7 @@ describe('IAM policy', () => { function createPolicyWithLogicalId(stack: Stack, logicalId: string): void { const policy = new Policy(stack, logicalId); - const cfnPolicy = policy.construct.defaultChild as CfnPolicy; + const cfnPolicy = policy.node.defaultChild as CfnPolicy; cfnPolicy.overrideLogicalId(logicalId); // force a particular logical ID // add statements & principal to satisfy validation diff --git a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts index 5a18d395ac32f..295cae174fe6a 100644 --- a/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.from-role-arn.test.ts @@ -535,7 +535,7 @@ function somePolicy(policyStack: Stack, policyName: string) { const someRole = new Role(policyStack, 'SomeExampleRole', { assumedBy: new AnyPrincipal(), }); - const roleResource = someRole.construct.defaultChild as CfnElement; + const roleResource = someRole.node.defaultChild as CfnElement; roleResource.overrideLogicalId('SomeRole'); // force a particular logical ID in the Ref expression return new Policy(policyStack, 'MyPolicy', { diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index ce53655e4abf0..31650f4c9da63 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -299,7 +299,7 @@ export class Stream extends StreamBase { if (!props.encryption && !props.encryptionKey) { const conditionName = 'AwsCdkKinesisEncryptedStreamsUnsupportedRegions'; - const existing = Stack.of(this).construct.tryFindChild(conditionName); + const existing = Stack.of(this).node.tryFindChild(conditionName); // create a single condition for the Stack if (!existing) { @@ -338,7 +338,7 @@ export class Stream extends StreamBase { if (encryptionType === StreamEncryption.KMS) { const encryptionKey = props.encryptionKey || new kms.Key(this, 'Key', { - description: `Created by ${this.construct.path}`, + description: `Created by ${this.node.path}`, }); const streamEncryption: CfnStream.StreamEncryptionProperty = { diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index 56d33990415ba..a428ea1c05f73 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -160,9 +160,9 @@ export = { p.addArnPrincipal('arn'); key.addToResourcePolicy(p); - key.construct.applyAspect(new Tag('tag1', 'value1')); - key.construct.applyAspect(new Tag('tag2', 'value2')); - key.construct.applyAspect(new Tag('tag3', '')); + key.node.applyAspect(new Tag('tag1', 'value1')); + key.node.applyAspect(new Tag('tag2', 'value2')); + key.node.applyAspect(new Tag('tag3', '')); expect(stack).to(exactlyMatchTemplate({ Resources: { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts index 98baa1d8fb472..2f3f3bcdc15fb 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts @@ -10,9 +10,9 @@ export class ApiEventSource implements lambda.IEventSource { } public bind(target: lambda.IFunction): void { - const id = `${target.construct.uniqueId}:ApiEventSourceA7A86A4F`; + const id = `${target.node.uniqueId}:ApiEventSourceA7A86A4F`; const stack = Stack.of(target); - let api = stack.construct.tryFindChild(id) as apigw.RestApi; + let api = stack.node.tryFindChild(id) as apigw.RestApi; if (!api) { api = new apigw.RestApi(stack, id, { defaultIntegration: new apigw.LambdaIntegration(target), diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts index c85ced6106ebf..ce9a3d8a5c998 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts @@ -21,10 +21,10 @@ export class DynamoEventSource extends StreamEventSource { public bind(target: lambda.IFunction) { if (!this.table.tableStreamArn) { - throw new Error(`DynamoDB Streams must be enabled on the table ${this.table.construct.path}`); + throw new Error(`DynamoDB Streams must be enabled on the table ${this.table.node.path}`); } - const eventSourceMapping = target.addEventSourceMapping(`DynamoDBEventSource:${this.table.construct.uniqueId}`, + const eventSourceMapping = target.addEventSourceMapping(`DynamoDBEventSource:${this.table.node.uniqueId}`, this.enrichMappingOptions({eventSourceArn: this.table.tableStreamArn}), ); this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts index dcfc8235e80eb..1a2de4c390155 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts @@ -23,7 +23,7 @@ export class KinesisEventSource extends StreamEventSource { } public bind(target: lambda.IFunction) { - const eventSourceMapping = target.addEventSourceMapping(`KinesisEventSource:${this.stream.construct.uniqueId}`, + const eventSourceMapping = target.addEventSourceMapping(`KinesisEventSource:${this.stream.node.uniqueId}`, this.enrichMappingOptions({eventSourceArn: this.stream.streamArn}), ); this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts index 148fd359a6295..9badef3e6f8bc 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts @@ -27,7 +27,7 @@ export class SqsEventSource implements lambda.IEventSource { } public bind(target: lambda.IFunction) { - const eventSourceMapping = target.addEventSourceMapping(`SqsEventSource:${this.queue.construct.uniqueId}`, { + const eventSourceMapping = target.addEventSourceMapping(`SqsEventSource:${this.queue.node.uniqueId}`, { batchSize: this.props.batchSize, eventSourceArn: this.queue.queueArn, }); diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index ea2b13454ad18..39ad7d665fcb7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -287,7 +287,6 @@ export abstract class FunctionBase extends Resource implements IFunction { return { statementAdded: true, policyDependable: this._functionNode().findChild(identifier) } as iam.AddToResourcePolicyResult; }, node: this.node, - construct: this.construct, }, }); } @@ -309,8 +308,8 @@ export abstract class FunctionBase extends Resource implements IFunction { } public configureAsyncInvoke(options: EventInvokeConfigOptions): void { - if (this.construct.tryFindChild('EventInvokeConfig') !== undefined) { - throw new Error(`An EventInvokeConfig has already been configured for the function at ${this.construct.path}`); + if (this.node.tryFindChild('EventInvokeConfig') !== undefined) { + throw new Error(`An EventInvokeConfig has already been configured for the function at ${this.node.path}`); } new EventInvokeConfig(this, 'EventInvokeConfig', { @@ -368,8 +367,8 @@ export abstract class QualifiedFunctionBase extends FunctionBase { } public configureAsyncInvoke(options: EventInvokeConfigOptions): void { - if (this.construct.tryFindChild('EventInvokeConfig') !== undefined) { - throw new Error(`An EventInvokeConfig has already been configured for the qualified function at ${this.construct.path}`); + if (this.node.tryFindChild('EventInvokeConfig') !== undefined) { + throw new Error(`An EventInvokeConfig has already been configured for the qualified function at ${this.node.path}`); } new EventInvokeConfig(this, 'EventInvokeConfig', { diff --git a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts index e3e069e5fa7bc..fcc7cb4b75e2f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-hash.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-hash.ts @@ -5,7 +5,7 @@ import { Function as LambdaFunction } from './function'; export function calculateFunctionHash(fn: LambdaFunction) { const stack = Stack.of(fn); - const functionResource = fn.construct.defaultChild as CfnResource; + const functionResource = fn.node.defaultChild as CfnResource; // render the cloudformation resource from this function const config = stack.resolve((functionResource as any)._toCloudFormation()); diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index bee7d8287ce81..5d0fd05c00b17 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -344,7 +344,7 @@ export class Function extends FunctionBase { // override the version's logical ID with a lazy string which includes the // hash of the function itself, so a new version resource is created when // the function configuration changes. - const cfn = this._currentVersion.construct.defaultChild as CfnResource; + const cfn = this._currentVersion.node.defaultChild as CfnResource; const originalLogicalId = this.stack.resolve(cfn.logicalId) as string; cfn.overrideLogicalId(Lazy.stringValue({ produce: _ => { @@ -604,7 +604,7 @@ export class Function extends FunctionBase { reservedConcurrentExecutions: props.reservedConcurrentExecutions, }); - resource.construct.addDependency(this.role); + resource.node.addDependency(this.role); this.functionName = this.getResourceNameAttribute(resource.ref); this.functionArn = this.getResourceArnAttribute(resource.attrArn, { @@ -652,7 +652,7 @@ export class Function extends FunctionBase { if (props.filesystem) { const config = props.filesystem.config; if (config.dependency) { - this.construct.addDependency(...config.dependency); + this.node.addDependency(...config.dependency); } resource.addPropertyOverride('FileSystemConfigs', @@ -755,7 +755,7 @@ export class Function extends FunctionBase { logGroupName: `/aws/lambda/${this.functionName}`, retention: logs.RetentionDays.INFINITE, }); - this._logGroup = logs.LogGroup.fromLogGroupArn(this, `${this.construct.id}-LogGroup`, logretention.logGroupArn); + this._logGroup = logs.LogGroup.fromLogGroupArn(this, `${this.node.id}-LogGroup`, logretention.logGroupArn); } return this._logGroup; } @@ -814,7 +814,7 @@ export class Function extends FunctionBase { } else { const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, - description: 'Automatic security group for Lambda Function ' + this.construct.uniqueId, + description: 'Automatic security group for Lambda Function ' + this.node.uniqueId, allowAllOutbound: props.allowAllOutbound, }); securityGroups = [securityGroup]; diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 379a1aac981f6..f8515dc84e841 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -70,7 +70,7 @@ export class SingletonFunction extends FunctionBase { * as a singleton across the stack. Use this method instead to declare dependencies. */ public addDependency(...up: cdk.IDependable[]) { - this.lambdaFunction.construct.addDependency(...up); + this.lambdaFunction.node.addDependency(...up); } /** @@ -78,7 +78,7 @@ export class SingletonFunction extends FunctionBase { * node.addDependency(). Use this method instead to declare this as a dependency of another construct. */ public dependOn(down: cdk.IConstruct) { - down.construct.addDependency(this.lambdaFunction); + down.node.addDependency(this.lambdaFunction); } /** @@ -91,7 +91,7 @@ export class SingletonFunction extends FunctionBase { private ensureLambda(props: SingletonFunctionProps): IFunction { const constructName = (props.lambdaPurpose || 'SingletonLambda') + slugify(props.uuid); - const existing = cdk.Stack.of(this).construct.tryFindChild(constructName); + const existing = cdk.Stack.of(this).node.tryFindChild(constructName); if (existing) { // Just assume this is true return existing as FunctionBase; diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/test.code.ts index 408ec9d00ca79..354771c697a0c 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.code.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.code.ts @@ -62,7 +62,7 @@ export = { 'adds code asset metadata'(test: Test) { // GIVEN const stack = new cdk.Stack(); - stack.construct.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); const location = path.join(__dirname, 'my-lambda-handler'); diff --git a/packages/@aws-cdk/aws-lambda/test/test.layers.ts b/packages/@aws-cdk/aws-lambda/test/test.layers.ts index b12cab6465653..9a1b2664aa268 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.layers.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.layers.ts @@ -77,7 +77,7 @@ export = testCase({ 'asset metadata is added to the cloudformation resource'(test: Test) { // GIVEN const stack = new cdk.Stack(); - stack.construct.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); // WHEN new lambda.LayerVersion(stack, 'layer', { diff --git a/packages/@aws-cdk/aws-logs-destinations/lib/kinesis.ts b/packages/@aws-cdk/aws-logs-destinations/lib/kinesis.ts index 1ea3ef56dcfeb..e4f57f5d4b33f 100644 --- a/packages/@aws-cdk/aws-logs-destinations/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-logs-destinations/lib/kinesis.ts @@ -14,7 +14,7 @@ export class KinesisDestination implements logs.ILogSubscriptionDestination { // Following example from https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#DestinationKinesisExample // Create a role to be assumed by CWL that can write to this stream and pass itself. const id = 'CloudWatchLogsCanPutRecords'; - const role = scope.construct.tryFindChild(id) as iam.IRole || new iam.Role(scope, id, { + const role = scope.node.tryFindChild(id) as iam.IRole || new iam.Role(scope, id, { assumedBy: new iam.ServicePrincipal('logs.amazonaws.com'), }); this.stream.grantWrite(role); diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index de2a56950ece0..2318fb1d9762e 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -341,7 +341,7 @@ export class DatabaseCluster extends DatabaseClusterBase { // Cannot test whether the subnets are in different AZs, but at least we can test the amount. if (subnetIds.length < 2) { - this.construct.addError(`Cluster requires at least 2 subnets, got ${subnetIds.length}`); + this.node.addError(`Cluster requires at least 2 subnets, got ${subnetIds.length}`); } const subnetGroup = new CfnDBSubnetGroup(this, 'Subnets', { @@ -520,7 +520,7 @@ export class DatabaseCluster extends DatabaseClusterBase { // We must have a dependency on the NAT gateway provider here to create // things in the right order. - instance.construct.addDependency(internetConnected); + instance.node.addDependency(internetConnected); this.instanceIdentifiers.push(instance.ref); this.instanceEndpoints.push(new Endpoint(instance.attrEndpointAddress, portAttribute)); @@ -542,7 +542,7 @@ export class DatabaseCluster extends DatabaseClusterBase { } const id = 'RotationSingleUser'; - const existing = this.construct.tryFindChild(id); + const existing = this.node.tryFindChild(id); if (existing) { throw new Error('A single user rotation was already added to this cluster.'); } diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 6550222ac538d..0890b26b705d2 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -503,12 +503,12 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData const { subnetIds } = props.vpc.selectSubnets(props.vpcPlacement); const subnetGroup = new CfnDBSubnetGroup(this, 'SubnetGroup', { - dbSubnetGroupDescription: `Subnet group for ${this.construct.id} database`, + dbSubnetGroupDescription: `Subnet group for ${this.node.id} database`, subnetIds, }); const securityGroups = props.securityGroups || [new ec2.SecurityGroup(this, 'SecurityGroup', { - description: `Security group for ${this.construct.id} database`, + description: `Security group for ${this.node.id} database`, vpc: props.vpc, })]; @@ -704,7 +704,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa } const id = 'RotationSingleUser'; - const existing = this.construct.tryFindChild(id); + const existing = this.node.tryFindChild(id); if (existing) { throw new Error('A single user rotation was already added to this instance.'); } diff --git a/packages/@aws-cdk/aws-rds/lib/proxy.ts b/packages/@aws-cdk/aws-rds/lib/proxy.ts index 970ffcade1ac9..eff37a0b4307c 100644 --- a/packages/@aws-cdk/aws-rds/lib/proxy.ts +++ b/packages/@aws-cdk/aws-rds/lib/proxy.ts @@ -69,9 +69,9 @@ export class ProxyTarget { if (this.dbCluster && this.dbInstance) { throw new Error('Proxy cannot target both database cluster and database instance.'); } else if (this.dbCluster) { - engine = (this.dbCluster.construct.defaultChild as CfnDBCluster).engine; + engine = (this.dbCluster.node.defaultChild as CfnDBCluster).engine; } else if (this.dbInstance) { - engine = (this.dbInstance.construct.defaultChild as CfnDBInstance).engine; + engine = (this.dbInstance.node.defaultChild as CfnDBInstance).engine; } let engineFamily; diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index da015de8a5862..85d2a6d2b75d4 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -1114,6 +1114,6 @@ export = { function testStack() { const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' }}); - stack.construct.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); + stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); return stack; } diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index 489047df1b067..48caa7aabf1db 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -505,7 +505,7 @@ export class Cluster extends ClusterBase { } const id = 'RotationSingleUser'; - const existing = this.construct.tryFindChild(id); + const existing = this.node.tryFindChild(id); if (existing) { throw new Error('A single user rotation was already added to this cluster.'); } diff --git a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts index 7984b6877e97f..385a2f53208b5 100644 --- a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts @@ -324,6 +324,6 @@ test('throws when trying to add single user rotation multiple times', () => { function testStack() { const stack = new cdk.Stack(undefined, undefined, { env: { account: '12345', region: 'us-test-1' } }); - stack.construct.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); + stack.node.setContext('availability-zones:12345:us-test-1', ['us-test-1a', 'us-test-1b']); return stack; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts index c78c599a0c492..217ba72cfc079 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts @@ -22,7 +22,7 @@ export class CloudFrontTarget implements route53.IAliasRecordTarget { const scopeStack = Stack.of(scope); let mapping = - (scopeStack.construct.tryFindChild(mappingName) as CfnMapping) ?? + (scopeStack.node.tryFindChild(mappingName) as CfnMapping) ?? new CfnMapping(scopeStack, mappingName, { mapping: { ['aws']: { diff --git a/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts index f01a8843bcf9e..40b0902cd236a 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts @@ -8,7 +8,7 @@ import * as cdk from '@aws-cdk/core'; export class InterfaceVpcEndpointTarget implements route53.IAliasRecordTarget { private readonly cfnVpcEndpoint: ec2.CfnVPCEndpoint; constructor(private readonly vpcEndpoint: ec2.IInterfaceVpcEndpoint) { - this.cfnVpcEndpoint = this.vpcEndpoint.construct.findChild('Resource') as ec2.CfnVPCEndpoint; + this.cfnVpcEndpoint = this.vpcEndpoint.node.findChild('Resource') as ec2.CfnVPCEndpoint; } public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts index 93d28fe930bef..98778fe9d5107 100644 --- a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts @@ -32,7 +32,7 @@ export = { const stack2 = new cdk.Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' }, }); - stack2.construct.setContext(missing[0].key, fakeZone); + stack2.node.setContext(missing[0].key, fakeZone); // WHEN const zoneRef = HostedZone.fromLookup(stack2, 'MyZoneProvider', filter); @@ -70,7 +70,7 @@ export = { const stack2 = new cdk.Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' }, }); - stack2.construct.setContext(missing[0].key, fakeZone); + stack2.node.setContext(missing[0].key, fakeZone); const zone = HostedZone.fromLookup(stack2, 'MyZoneProvider', filter); diff --git a/packages/@aws-cdk/aws-route53/test/test.util.ts b/packages/@aws-cdk/aws-route53/test/test.util.ts index 6afb828aae570..006527d3ad869 100644 --- a/packages/@aws-cdk/aws-route53/test/test.util.ts +++ b/packages/@aws-cdk/aws-route53/test/test.util.ts @@ -26,7 +26,6 @@ export = { hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', stack, node: stack.node, - construct: stack.construct, }); // THEN @@ -46,7 +45,6 @@ export = { hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', stack, node: stack.node, - construct: stack.construct, }); // THEN @@ -66,7 +64,6 @@ export = { hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', stack, node: stack.node, - construct: stack.construct, }); // THEN @@ -86,7 +83,6 @@ export = { hostedZoneArn: 'arn:aws:route53:::hostedzone/fakeId', stack, node: stack.node, - construct: stack.construct, }); // THEN diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index 63f4b3750b990..e457de127d442 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -176,7 +176,7 @@ export class Asset extends cdk.Construct implements cdk.IAsset { * (e.g. "Code" for AWS::Lambda::Function) */ public addResourceMetadata(resource: cdk.CfnResource, resourceProperty: string) { - if (!this.construct.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { + if (!this.node.tryGetContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT)) { return; // not enabled } diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts index 7038155bde688..d08005a39f692 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -24,7 +24,7 @@ test('simple use case', () => { // verify that metadata contains an "aws:cdk:asset" entry with // the correct information - const entry = stack.construct.metadata.find(m => m.type === 'aws:cdk:asset'); + const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); expect(entry).toBeTruthy(); // verify that now the template contains parameters for this asset @@ -74,7 +74,7 @@ test('"file" assets', () => { const stack = new cdk.Stack(); const filePath = path.join(__dirname, 'file-asset.txt'); new Asset(stack, 'MyAsset', { path: filePath }); - const entry = stack.construct.metadata.find(m => m.type === 'aws:cdk:asset'); + const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); expect(entry).toBeTruthy(); // synthesize first so "prepare" is called @@ -196,7 +196,7 @@ test('isZipArchive indicates if the asset represents a .zip file (either explici test('addResourceMetadata can be used to add CFN metadata to resources', () => { // GIVEN const stack = new cdk.Stack(); - stack.construct.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); const location = path.join(__dirname, 'sample-asset-directory'); const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/source.ts b/packages/@aws-cdk/aws-s3-deployment/lib/source.ts index ec06a5a66c31c..6f0f877662891 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/source.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/source.ts @@ -77,7 +77,7 @@ export class Source { } let id = 1; - while (scope.construct.tryFindChild(`Asset${id}`)) { + while (scope.node.tryFindChild(`Asset${id}`)) { id++; } const asset = new s3_assets.Asset(scope, `Asset${id}`, { diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts index 5cfb1cc918841..a1ea5ac5ac42c 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts @@ -11,7 +11,7 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { } public bind(_scope: Construct, bucket: s3.IBucket): s3.BucketNotificationDestinationConfig { - const permissionId = `AllowBucketNotificationsFrom${bucket.construct.uniqueId}`; + const permissionId = `AllowBucketNotificationsFrom${bucket.node.uniqueId}`; if (this.fn.permissionsNode.tryFindChild(permissionId) === undefined) { this.fn.addPermission(permissionId, { diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index fefd5b4b68b62..cadf2609692f0 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -39,7 +39,7 @@ test('when notification are added, a custom resource is provisioned + a lambda h test('when notification are added, you can tag the lambda', () => { const stack = new cdk.Stack(); - stack.construct.applyAspect(new cdk.Tag('Lambda', 'AreTagged')); + stack.node.applyAspect(new cdk.Tag('Lambda', 'AreTagged')); const bucket = new s3.Bucket(stack, 'MyBucket'); diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 523b93bb84f71..acb1949b1cb2e 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1425,7 +1425,7 @@ export class Bucket extends BucketBase { if (encryptionType === BucketEncryption.KMS) { const encryptionKey = props.encryptionKey || new kms.Key(this, 'Key', { - description: `Created by ${this.construct.path}`, + description: `Created by ${this.node.path}`, }); const bucketEncryption = { @@ -1623,7 +1623,7 @@ export class Bucket extends BucketBase { return this.inventories.map((inventory, index) => { const format = inventory.format ?? InventoryFormat.CSV; const frequency = inventory.frequency ?? InventoryFrequency.WEEKLY; - const id = inventory.inventoryId ?? `${this.construct.id}Inventory${index}`; + const id = inventory.inventoryId ?? `${this.node.id}Inventory${index}`; if (inventory.destination.bucket instanceof Bucket) { inventory.destination.bucket.addToResourcePolicy(new iam.PolicyStatement({ diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts index 1403ddcb2e7b4..4ceced2e93e2d 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource-handler.ts @@ -30,7 +30,7 @@ export class NotificationsResourceHandler extends cdk.Construct { // well-known logical id to ensure stack singletonity const logicalId = 'BucketNotificationsHandler050a0587b7544547bf325f094a3db834'; - let lambda = root.construct.tryFindChild(logicalId) as NotificationsResourceHandler; + let lambda = root.node.tryFindChild(logicalId) as NotificationsResourceHandler; if (!lambda) { lambda = new NotificationsResourceHandler(root, logicalId); } @@ -83,7 +83,7 @@ export class NotificationsResourceHandler extends cdk.Construct { }, }); - resource.construct.addDependency(role); + resource.node.addDependency(role); this.functionArn = resource.getAtt('Arn').toString(); } diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts index 99de445c4977c..fc5d83f8106d7 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts @@ -63,7 +63,7 @@ export class BucketNotifications extends cdk.Construct { // for example, the SNS topic policy must be created /before/ the notification resource. // otherwise, S3 won't be able to confirm the subscription. if (targetProps.dependencies) { - resource.construct.addDependency(...targetProps.dependencies); + resource.node.addDependency(...targetProps.dependencies); } // based on the target type, add the the correct configurations array diff --git a/packages/@aws-cdk/aws-s3/test/test.aspect.ts b/packages/@aws-cdk/aws-s3/test/test.aspect.ts index 8a74ff3b1cc7c..ba2049b0f993e 100644 --- a/packages/@aws-cdk/aws-s3/test/test.aspect.ts +++ b/packages/@aws-cdk/aws-s3/test/test.aspect.ts @@ -11,7 +11,7 @@ export = { new s3.Bucket(stack, 'MyBucket'); // WHEN - stack.construct.applyAspect(new BucketVersioningChecker()); + stack.node.applyAspect(new BucketVersioningChecker()); // THEN const assembly = SynthUtils.synthesize(stack); @@ -29,7 +29,7 @@ export = { }); // WHEN - stack.construct.applyAspect(new BucketVersioningChecker()); + stack.node.applyAspect(new BucketVersioningChecker()); // THEN const assembly = SynthUtils.synthesize(stack); @@ -44,7 +44,7 @@ class BucketVersioningChecker implements cdk.IAspect { if (node instanceof s3.CfnBucket) { if (!node.versioningConfiguration || (!cdk.Tokenization.isResolvable(node.versioningConfiguration) && node.versioningConfiguration.status !== 'Enabled')) { - node.construct.addError('Bucket versioning is not enabled'); + node.node.addError('Bucket versioning is not enabled'); } } } diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index 7c87816854a86..16ff1056534bc 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -211,7 +211,7 @@ export class SecretRotation extends Construct { } // Max length of 64 chars, get the last 64 chars - const uniqueId = this.construct.uniqueId; + const uniqueId = this.node.uniqueId; const rotationFunctionName = uniqueId.substring(Math.max(uniqueId.length - 64, 0), uniqueId.length); const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 4ca105f58d59a..1a256fb66aefd 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -315,7 +315,7 @@ export class Secret extends SecretBase { */ public attach(target: ISecretAttachmentTarget): ISecret { const id = 'Attachment'; - const existing = this.construct.tryFindChild(id); + const existing = this.node.tryFindChild(id); if (existing) { throw new Error('Secret is already attached to a target.'); diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts index defbf3a1cd87c..2e9eb90f105d6 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts @@ -65,7 +65,7 @@ export class AliasTargetInstance extends InstanceBase { AWS_ALIAS_DNS_NAME: props.dnsName, ...props.customAttributes, }, - instanceId: props.instanceId || this.construct.uniqueId, + instanceId: props.instanceId || this.node.uniqueId, serviceId: props.service.serviceId, }); diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts index c7567de4c8bc8..00e7d6a126934 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts @@ -50,7 +50,7 @@ export abstract class InstanceBase extends Resource implements IInstance { */ protected uniqueInstanceId() { // Max length of 64 chars, get the last 64 chars - const id = this.construct.uniqueId; + const id = this.node.uniqueId; return id.substring(Math.max(id.length - 64, 0), id.length); } } diff --git a/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts b/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts index b819672bc4755..3fa7d418e2ac4 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts @@ -68,10 +68,10 @@ export class Lambda implements ses.IReceiptRuleAction { // Ensure permission is deployed before rule const permission = this.props.function.permissionsNode.tryFindChild(permissionId) as lambda.CfnPermission; if (permission) { // The Lambda could be imported - rule.construct.addDependency(permission); + rule.node.addDependency(permission); } else { // eslint-disable-next-line max-len - rule.construct.addWarning('This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); + rule.node.addWarning('This rule is using a Lambda action with an imported function. Ensure permission is given to SES to invoke that function.'); } return { diff --git a/packages/@aws-cdk/aws-ses-actions/lib/s3.ts b/packages/@aws-cdk/aws-ses-actions/lib/s3.ts index 2e4129690fd80..24c8590511701 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/s3.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/s3.ts @@ -61,11 +61,11 @@ export class S3 implements ses.IReceiptRuleAction { }); this.props.bucket.addToResourcePolicy(s3Statement); - const policy = this.props.bucket.construct.tryFindChild('Policy') as s3.BucketPolicy; + const policy = this.props.bucket.node.tryFindChild('Policy') as s3.BucketPolicy; if (policy) { // The bucket could be imported - rule.construct.addDependency(policy); + rule.node.addDependency(policy); } else { - rule.construct.addWarning('This rule is using a S3 action with an imported bucket. Ensure permission is given to SES to write to that bucket.'); + rule.node.addWarning('This rule is using a S3 action with an imported bucket. Ensure permission is given to SES to write to that bucket.'); } // Allow SES to use KMS master key diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index 50bb1883777ec..943813184ed1f 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -27,14 +27,14 @@ export class LambdaSubscription implements sns.ITopicSubscription { throw new Error('The supplied lambda Function object must be an instance of Construct'); } - this.fn.addPermission(`AllowInvoke:${topic.construct.uniqueId}`, { + this.fn.addPermission(`AllowInvoke:${topic.node.uniqueId}`, { sourceArn: topic.topicArn, principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); return { subscriberScope: this.fn, - subscriberId: topic.construct.id, + subscriberId: topic.node.id, endpoint: this.fn.functionArn, protocol: sns.SubscriptionProtocol.LAMBDA, filterPolicy: this.props.filterPolicy, diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 7ff0c6921571b..39c8362d60c4f 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -48,7 +48,7 @@ export class SqsSubscription implements sns.ITopicSubscription { return { subscriberScope: this.queue, - subscriberId: topic.construct.uniqueId, + subscriberId: topic.node.uniqueId, endpoint: this.queue.queueArn, protocol: sns.SubscriptionProtocol.SQS, rawMessageDelivery: this.props.rawMessageDelivery, diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index 84fee88c6821a..07b96a34629d2 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -73,8 +73,8 @@ export abstract class TopicBase extends Resource implements ITopic { // We use the subscriber's id as the construct id. There's no meaning // to subscribing the same subscriber twice on the same topic. - if (scope.construct.tryFindChild(id)) { - throw new Error(`A subscription with id "${id}" already exists under the scope ${scope.construct.path}`); + if (scope.node.tryFindChild(id)) { + throw new Error(`A subscription with id "${id}" already exists under the scope ${scope.node.path}`); } new Subscription(scope, id, { @@ -125,8 +125,8 @@ export abstract class TopicBase extends Resource implements ITopic { const re = /TokenSubscription:([\d]*)/gm; // Search through the construct and all of its children // for previous subscriptions that match our regex pattern - for (const source of scope.construct.findAll()) { - const m = re.exec(source.construct.id); // Use regex to find a match + for (const source of scope.node.findAll()) { + const m = re.exec(source.node.id); // Use regex to find a match if (m !== null) { // if we found a match const matchSuffix = parseInt(m[1], 10); // get the suffix for that match (as integer) if (matchSuffix >= nextSuffix) { // check if the match suffix is larger or equal to currently proposed suffix diff --git a/packages/@aws-cdk/aws-sqs/lib/queue.ts b/packages/@aws-cdk/aws-sqs/lib/queue.ts index b8840e9c9af89..d335da2c9120f 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue.ts @@ -302,7 +302,7 @@ export class Queue extends QueueBase { if (encryption === QueueEncryption.KMS) { const masterKey = props.encryptionMasterKey || new kms.Key(this, 'Key', { - description: `Created by ${this.construct.path}`, + description: `Created by ${this.node.path}`, }); return { diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index 27491975dd81f..bc73278bfc267 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -390,7 +390,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { public static valueForTypedStringParameter(scope: Construct, parameterName: string, type = ParameterType.STRING, version?: number): string { const stack = Stack.of(scope); const id = makeIdentityForImportedValue(parameterName); - const exists = stack.construct.tryFindChild(id) as IStringParameter; + const exists = stack.node.tryFindChild(id) as IStringParameter; if (exists) { return exists.stringValue; } @@ -406,7 +406,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { public static valueForSecureStringParameter(scope: Construct, parameterName: string, version: number): string { const stack = Stack.of(scope); const id = makeIdentityForImportedValue(parameterName); - const exists = stack.construct.tryFindChild(id) as IStringParameter; + const exists = stack.node.tryFindChild(id) as IStringParameter; if (exists) { return exists.stringValue; } return this.fromSecureStringParameterAttributes(stack, id, { parameterName, version }).stringValue; diff --git a/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts b/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts index 2fb19bd2a9367..358d92abbce25 100644 --- a/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts +++ b/packages/@aws-cdk/aws-ssm/test/integ.parameter-arns.ts @@ -19,7 +19,7 @@ const params = [ ]; for (const p of params) { - new CfnOutput(stack, `${p.construct.id}Arn`, { value: p.parameterArn }); + new CfnOutput(stack, `${p.node.id}Arn`, { value: p.parameterArn }); } app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts index b38efbf39865f..12d1c71665577 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts @@ -89,7 +89,7 @@ export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask for (const override of this.props.containerOverrides || []) { const name = override.containerDefinition.containerName; if (!cdk.Token.isUnresolved(name)) { - const cont = this.props.taskDefinition.construct.tryFindChild(name); + const cont = this.props.taskDefinition.node.tryFindChild(name); if (!cont) { throw new Error(`Overrides mention container with name '${name}', but no such container in task definition`); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts index 371fc65a461c6..d06d204641653 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts @@ -260,7 +260,7 @@ export class EcsRunTask extends sfn.TaskStateBase implements ec2.IConnectable { for (const override of this.props.containerOverrides ?? []) { const name = override.containerDefinition.containerName; if (!cdk.Token.isUnresolved(name)) { - const cont = this.props.taskDefinition.construct.tryFindChild(name); + const cont = this.props.taskDefinition.node.tryFindChild(name); if (!cont) { throw new Error(`Overrides mention container with name '${name}', but no such container in task definition`); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 5a011fbf0bd74..ef97f45e6f8b3 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -185,7 +185,7 @@ export class Activity extends Resource implements IActivity { } private generateName(): string { - const name = this.construct.uniqueId; + const name = this.node.uniqueId; if (name.length > 80) { return name.substring(0, 40) + name.substring(name.length - 40); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts index 5a9e8884d22b0..929aed5b22219 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts @@ -19,7 +19,7 @@ export abstract class StateMachineFragment extends cdk.Construct implements ICha public abstract readonly endStates: INextable[]; public get id() { - return this.construct.id; + return this.node.id; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 663f3346892fa..a6632a7e5d35d 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -305,7 +305,7 @@ export class StateMachine extends StateMachineBase { loggingConfiguration, }); - resource.construct.addDependency(this.role); + resource.node.addDependency(this.role); for (const statement of graph.policyStatements) { this.addToRolePolicy(statement); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 8c4701764c7cf..d586a3e472d98 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -70,7 +70,7 @@ export abstract class State extends cdk.Construct implements IChainable { if (isPrefixable(el)) { el.addPrefix(prefix); } - queue.push(...el.construct.children); + queue.push(...el.node.children); } } @@ -186,7 +186,7 @@ export abstract class State extends cdk.Construct implements IChainable { } public get id() { - return this.construct.id; + return this.node.id; } /** diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 40625bd8b7b0a..845aa001ba3d2 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -297,7 +297,7 @@ describe('CDK Include', () => { const cfnTemplate = includeTestTemplate(stack, 'resource-attribute-depends-on.json'); const cfnBucket2 = cfnTemplate.getResource('Bucket2'); - expect(cfnBucket2.construct.dependencies).toHaveLength(1); + expect(cfnBucket2.node.dependencies).toHaveLength(1); // we always render dependsOn as an array, even if it's a single string expect(stack).toHaveResourceLike('AWS::S3::Bucket', { "Properties": { @@ -313,7 +313,7 @@ describe('CDK Include', () => { const cfnTemplate = includeTestTemplate(stack, 'resource-attribute-depends-on-array.json'); const cfnBucket2 = cfnTemplate.getResource('Bucket2'); - expect(cfnBucket2.construct.dependencies).toHaveLength(2); + expect(cfnBucket2.node.dependencies).toHaveLength(2); expect(stack).toHaveResourceLike('AWS::S3::Bucket', { "Properties": { "BucketName": "bucket2", diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index d6a5d9a902032..d2a240166995c 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -214,7 +214,7 @@ you. If you need to add an ordering dependency that is not automatically inferred, you do so by adding a dependency relationship using -`constructA.construct.addDependency(constructB)`. This will add a dependency +`constructA.node.addDependency(constructB)`. This will add a dependency relationship between all resources in the scope of `constructA` and all resources in the scope of `constructB`. @@ -230,7 +230,7 @@ bAndC.add(constructB); bAndC.add(constructC); // Take the dependency -constructA.construct.addDependency(bAndC); +constructA.node.addDependency(bAndC); ``` ### Stack Dependencies @@ -319,7 +319,7 @@ examples ensures that only a single SNS topic is defined: function getOrCreate(scope: Construct): sns.Topic { const stack = Stack.of(this); const uniqueid = 'GloballyUniqueIdForSingleton'; - return stack.construct.tryFindChild(uniqueid) as sns.Topic ?? new sns.Topic(stack, uniqueid); + return stack.node.tryFindChild(uniqueid) as sns.Topic ?? new sns.Topic(stack, uniqueid); } ``` @@ -675,7 +675,7 @@ accessing those through the `cfnOptions` property: ```ts const rawBucket = new s3.CfnBucket(this, 'Bucket', { /* ... */ }); // -or- -const rawBucket = bucket.construct.defaultChild as s3.CfnBucket; +const rawBucket = bucket.node.defaultChild as s3.CfnBucket; // then rawBucket.cfnOptions.condition = new CfnCondition(this, 'EnableBucket', { /* ... */ }); @@ -734,7 +734,7 @@ const stage = Fn.conditionIf(isProd.logicalId, 'Beta', 'Prod').toString(); // Make Bucket creation condition to IsProduction by accessing // and overriding the CloudFormation resource const bucket = new s3.Bucket(this, 'Bucket'); -const cfnBucket = bucket.construct.defaultChild as s3.CfnBucket; +const cfnBucket = bucket.node.defaultChild as s3.CfnBucket; cfnBucket.cfnOptions.condition = isProd; ``` diff --git a/packages/@aws-cdk/core/lib/annotations.ts b/packages/@aws-cdk/core/lib/annotations.ts index a90ab995441a1..8f13e09987035 100644 --- a/packages/@aws-cdk/core/lib/annotations.ts +++ b/packages/@aws-cdk/core/lib/annotations.ts @@ -55,6 +55,6 @@ export class Annotations { * @param message The message itself */ private addMessage(level: string, message: string) { - this.scope.construct.addMetadata(level, message); + this.scope.node.addMetadata(level, message); } } \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/app.ts b/packages/@aws-cdk/core/lib/app.ts index ba87fe7a86197..50996a72ff85a 100644 --- a/packages/@aws-cdk/core/lib/app.ts +++ b/packages/@aws-cdk/core/lib/app.ts @@ -98,11 +98,11 @@ export class App extends Stage { this.loadContext(props.context); if (props.stackTraces === false) { - this.construct.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); + this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); } if (props.runtimeInfo === false) { - this.construct.setContext(cxapi.DISABLE_VERSION_REPORTING, true); + this.node.setContext(cxapi.DISABLE_VERSION_REPORTING, true); } const autoSynth = props.autoSynth !== undefined ? props.autoSynth : cxapi.OUTDIR_ENV in process.env; @@ -120,7 +120,7 @@ export class App extends Stage { private loadContext(defaults: { [key: string]: string } = { }) { // prime with defaults passed through constructor for (const [ k, v ] of Object.entries(defaults)) { - this.construct.setContext(k, v); + this.node.setContext(k, v); } // read from environment @@ -130,7 +130,7 @@ export class App extends Stage { : { }; for (const [ k, v ] of Object.entries(contextFromEnvironment)) { - this.construct.setContext(k, v); + this.node.setContext(k, v); } } } diff --git a/packages/@aws-cdk/core/lib/aspect.ts b/packages/@aws-cdk/core/lib/aspect.ts index 8bf84d4d3f22f..3bca67a12aa6d 100644 --- a/packages/@aws-cdk/core/lib/aspect.ts +++ b/packages/@aws-cdk/core/lib/aspect.ts @@ -45,7 +45,7 @@ export class Aspects { */ public add(aspect: IAspect) { // TODO(2.0): this._aspects.push(aspect); - this.scope.construct._actualNode.applyAspect(aspect); + this.scope.node._actualNode.applyAspect(aspect); } /** @@ -53,6 +53,6 @@ export class Aspects { */ public get aspects(): IAspect[] { // TODO(2.0): return [ ...this._aspects ]; - return [ ...(this.scope.construct._actualNode as any)._aspects ]; // clone + return [ ...(this.scope.node._actualNode as any)._aspects ]; // clone } } \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts index 8a3ae1452b464..d581f3df6bc84 100644 --- a/packages/@aws-cdk/core/lib/asset-staging.ts +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -5,9 +5,9 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import { AssetHashType, AssetOptions } from './assets'; import { BundlingOptions } from './bundling'; -import { Construct } from './construct-compat'; import { FileSystem, FingerprintOptions } from './fs'; import { Stage } from './stage'; +import { Construct } from './construct-compat'; const STAGING_TMP = '.cdk.staging'; @@ -95,7 +95,7 @@ export class AssetStaging extends Construct { this.assetHash = this.calculateHash(props); - const stagingDisabled = this.construct.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); + const stagingDisabled = this.node.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); if (stagingDisabled) { this.stagedPath = this.bundleDir ?? this.sourcePath; } else { @@ -180,7 +180,7 @@ export class AssetStaging extends Construct { let localBundling: boolean | undefined; try { - process.stderr.write(`Bundling asset ${this.construct.path}...\n`); + process.stderr.write(`Bundling asset ${this.node.path}...\n`); localBundling = options.local?.tryBundle(bundleDir, options); if (!localBundling) { @@ -193,7 +193,7 @@ export class AssetStaging extends Construct { }); } } catch (err) { - throw new Error(`Failed to bundle asset ${this.construct.path}: ${err}`); + throw new Error(`Failed to bundle asset ${this.node.path}: ${err}`); } if (FileSystem.isEmpty(bundleDir)) { diff --git a/packages/@aws-cdk/core/lib/cfn-element.ts b/packages/@aws-cdk/core/lib/cfn-element.ts index 2e6562b247f79..7aadf278f13cd 100644 --- a/packages/@aws-cdk/core/lib/cfn-element.ts +++ b/packages/@aws-cdk/core/lib/cfn-element.ts @@ -58,10 +58,10 @@ export abstract class CfnElement extends Construct { this.stack = Stack.of(this); this.logicalId = Lazy.stringValue({ produce: () => this.synthesizeLogicalId() }, { - displayHint: `${notTooLong(this.construct.path)}.LogicalID`, + displayHint: `${notTooLong(this.node.path)}.LogicalID`, }); - this.construct.addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); + this.node.addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); } /** @@ -78,7 +78,7 @@ export abstract class CfnElement extends Construct { * node +internal+ entries filtered. */ public get creationStack(): string[] { - const trace = this.construct.metadata.find(md => md.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID)!.trace; + const trace = this.node.metadata.find(md => md.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID)!.trace; if (!trace) { return []; } diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index 4714f3d99cf3d..3f2784f890073 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -50,7 +50,7 @@ export class CfnOutput extends CfnElement { super(scope, id); if (props.value === undefined) { - throw new Error(`Missing value for CloudFormation output at path "${this.construct.path}"`); + throw new Error(`Missing value for CloudFormation output at path "${this.node.path}"`); } this._description = props.description; diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index a58e54f097be4..5872399e0efee 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -221,7 +221,7 @@ export class CfnParser { if (!depResource) { throw new Error(`Resource '${logicalId}' depends on '${dep}' that doesn't exist`); } - resource.construct.addDependency(depResource); + resource.node.addDependency(depResource); } } diff --git a/packages/@aws-cdk/core/lib/cfn-resource.ts b/packages/@aws-cdk/core/lib/cfn-resource.ts index 45ad083e5591d..9af3aa5c16a21 100644 --- a/packages/@aws-cdk/core/lib/cfn-resource.ts +++ b/packages/@aws-cdk/core/lib/cfn-resource.ts @@ -92,8 +92,8 @@ export class CfnResource extends CfnRefElement { // if aws:cdk:enable-path-metadata is set, embed the current construct's // path in the CloudFormation template, so it will be possible to trace // back to the actual construct path. - if (this.construct.tryGetContext(cxapi.PATH_METADATA_ENABLE_CONTEXT)) { - this.addMetadata(cxapi.PATH_METADATA_KEY, this.construct.path); + if (this.node.tryGetContext(cxapi.PATH_METADATA_ENABLE_CONTEXT)) { + this.addMetadata(cxapi.PATH_METADATA_KEY, this.node.path); } } @@ -238,7 +238,7 @@ export class CfnResource extends CfnRefElement { return; } - addDependency(this, target, `"${this.construct.path}" depends on "${target.construct.path}"`); + addDependency(this, target, `"${this.node.path}" depends on "${target.node.path}"`); } /** @@ -312,7 +312,7 @@ export class CfnResource extends CfnRefElement { return ret; } catch (e) { // Change message - e.message = `While synthesizing ${this.construct.path}: ${e.message}`; + e.message = `While synthesizing ${this.node.path}: ${e.message}`; // Adjust stack trace (make it look like node built it, too...) const trace = this.creationStack; if (trace) { @@ -330,7 +330,7 @@ export class CfnResource extends CfnRefElement { function renderDependsOn(dependsOn: Set) { return Array .from(dependsOn) - .sort((x, y) => x.construct.path.localeCompare(y.construct.path)) + .sort((x, y) => x.node.path.localeCompare(y.node.path)) .map(r => r.logicalId); } diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index 572fa9f33fe8f..306e299016f7c 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -28,11 +28,6 @@ export interface IConstruct extends constructs.IConstruct, IDependable { * The construct tree node for this construct. */ readonly node: ConstructNode; - - /** - * The construct tree node for this construct. - */ - readonly construct: ConstructNode; } /** @@ -66,18 +61,9 @@ export class Construct extends constructs.Construct implements IConstruct { /** * The construct tree node associated with this construct. - * - * @deprecate `Construct.node` is being deprecated in favor of - * `Construct.construct`. This API will be removed in the next major version - * of the AWS CDK, please migrate your code to use `construct` instead. */ public readonly node: ConstructNode; - /** - * Construct API. - */ - public readonly construct: ConstructNode; - constructor(scope: Construct, id: string) { super(scope, id, { nodeFactory: { @@ -91,17 +77,16 @@ export class Construct extends constructs.Construct implements IConstruct { } Object.defineProperty(this, CONSTRUCT_SYMBOL, { value: true }); - this.construct = ConstructNode._unwrap(constructs.Node.of(this)); - this.node = this.construct; + this.node = ConstructNode._unwrap(constructs.Node.of(this)); const disableTrace = - this.construct.tryGetContext(cxapi.DISABLE_METADATA_STACK_TRACE) || - this.construct.tryGetContext(constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA) || + this.node.tryGetContext(cxapi.DISABLE_METADATA_STACK_TRACE) || + this.node.tryGetContext(constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA) || process.env.CDK_DISABLE_STACK_TRACE; if (disableTrace) { - this.construct.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); - this.construct.setContext(constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA, true); + this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); + this.node.setContext(constructs.ConstructMetadata.DISABLE_STACK_TRACE_IN_METADATA, true); process.env.CDK_DISABLE_STACK_TRACE = '1'; } } diff --git a/packages/@aws-cdk/core/lib/context-provider.ts b/packages/@aws-cdk/core/lib/context-provider.ts index 9d176709cb778..6a01a7c5a6506 100644 --- a/packages/@aws-cdk/core/lib/context-provider.ts +++ b/packages/@aws-cdk/core/lib/context-provider.ts @@ -95,7 +95,7 @@ export class ContextProvider { } const { key, props } = this.getKey(scope, options); - const value = scope.construct.tryGetContext(key); + const value = scope.node.tryGetContext(key); const providerError = extractProviderError(value); // if context is missing or an error occurred during context retrieval, diff --git a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts index 21decaf371076..8b24d068235f9 100644 --- a/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts +++ b/packages/@aws-cdk/core/lib/custom-resource-provider/custom-resource-provider.ts @@ -91,7 +91,7 @@ export class CustomResourceProvider extends Construct { public static getOrCreate(scope: Construct, uniqueid: string, props: CustomResourceProviderProps) { const id = `${uniqueid}CustomResourceProvider`; const stack = Stack.of(scope); - const provider = stack.construct.tryFindChild(id) as CustomResourceProvider + const provider = stack.node.tryFindChild(id) as CustomResourceProvider ?? new CustomResourceProvider(stack, id, props); return provider.serviceToken; diff --git a/packages/@aws-cdk/core/lib/deps.ts b/packages/@aws-cdk/core/lib/deps.ts index 55726d7dac46e..e0ef970b6e359 100644 --- a/packages/@aws-cdk/core/lib/deps.ts +++ b/packages/@aws-cdk/core/lib/deps.ts @@ -36,7 +36,7 @@ export function addDependency(source: T, target: T, reason?: const targetStage = Stage.of(targetStack); if (sourceStage !== targetStage) { // eslint-disable-next-line max-len - throw new Error(`You cannot add a dependency from '${source.construct.path}' (in ${describeStage(sourceStage)}) to '${target.construct.path}' (in ${describeStage(targetStage)}): dependency cannot cross stage boundaries`); + throw new Error(`You cannot add a dependency from '${source.node.path}' (in ${describeStage(sourceStage)}) to '${target.node.path}' (in ${describeStage(targetStage)}): dependency cannot cross stage boundaries`); } // find the deepest common stack between the two elements @@ -70,7 +70,7 @@ export function addDependency(source: T, target: T, reason?: // `source` is a direct or indirect nested stack of `target`, and this is not // possible (nested stacks cannot depend on their parents). if (commonStack === target) { - throw new Error(`Nested stack '${sourceStack.construct.path}' cannot depend on a parent stack '${targetStack.construct.path}': ${reason}`); + throw new Error(`Nested stack '${sourceStack.node.path}' cannot depend on a parent stack '${targetStack.node.path}': ${reason}`); } // we have a common stack from which we can reach both `source` and `target` @@ -103,5 +103,5 @@ export function addDependency(source: T, target: T, reason?: function describeStage(assembly: Stage | undefined): string { if (!assembly) { return 'an unrooted construct tree'; } if (!assembly.parentStage) { return 'the App'; } - return `Stage '${assembly.construct.path}'`; + return `Stage '${assembly.node.path}'`; } diff --git a/packages/@aws-cdk/core/lib/nested-stack.ts b/packages/@aws-cdk/core/lib/nested-stack.ts index cd9e6be048d44..7e52427467039 100644 --- a/packages/@aws-cdk/core/lib/nested-stack.ts +++ b/packages/@aws-cdk/core/lib/nested-stack.ts @@ -110,7 +110,7 @@ export class NestedStack extends Stack { Object.defineProperty(this, NESTED_STACK_SYMBOL, { value: true }); // this is the file name of the synthesized template file within the cloud assembly - this.templateFile = `${this.construct.uniqueId}.nested.template.json`; + this.templateFile = `${this.node.uniqueId}.nested.template.json`; this.parameters = props.parameters || {}; @@ -223,7 +223,7 @@ function findParentStack(scope: Construct): Stack { throw new Error('Nested stacks cannot be defined as a root construct'); } - const parentStack = scope.construct.scopes.reverse().find(p => Stack.isStack(p)); + const parentStack = scope.node.scopes.reverse().find(p => Stack.isStack(p)); if (!parentStack) { throw new Error('Nested stacks must be defined within scope of another non-nested stack'); } diff --git a/packages/@aws-cdk/core/lib/private/cfn-reference.ts b/packages/@aws-cdk/core/lib/private/cfn-reference.ts index a20457d7f6e6d..491232e344840 100644 --- a/packages/@aws-cdk/core/lib/private/cfn-reference.ts +++ b/packages/@aws-cdk/core/lib/private/cfn-reference.ts @@ -102,6 +102,11 @@ export class CfnReference extends Reference { const consumingStack = Stack.of(context.scope); const token = this.replacementTokens.get(consumingStack); + // if (!token && this.isCrossStackReference(consumingStack) && !context.preparing) { + // eslint-disable-next-line max-len + // throw new Error(`Cross-stack reference (${context.scope.node.path} -> ${this.target.node.path}) has not been assigned a value--call prepare() first`); + // } + if (token) { return token.resolve(context); } else { @@ -133,7 +138,7 @@ export class CfnReference extends Reference { */ public toString(): string { return Token.asString(this, { - displayHint: `${this.target.construct.id}.${this.displayName}`, + displayHint: `${this.target.node.id}.${this.displayName}`, }); } } diff --git a/packages/@aws-cdk/core/lib/private/physical-name-generator.ts b/packages/@aws-cdk/core/lib/private/physical-name-generator.ts index 993c63c1db211..dbd0a2c8af772 100644 --- a/packages/@aws-cdk/core/lib/private/physical-name-generator.ts +++ b/packages/@aws-cdk/core/lib/private/physical-name-generator.ts @@ -8,16 +8,16 @@ import { TokenMap } from './token-map'; export function generatePhysicalName(resource: IResource): string { const stack = Stack.of(resource); const stackPart = new PrefixNamePart(stack.stackName, 25); - const idPart = new SuffixNamePart(resource.construct.uniqueId, 24); + const idPart = new SuffixNamePart(resource.node.uniqueId, 24); const region: string = stack.region; if (Token.isUnresolved(region) || !region) { - throw new Error(`Cannot generate a physical name for ${resource.construct.path}, because the region is un-resolved or missing`); + throw new Error(`Cannot generate a physical name for ${resource.node.path}, because the region is un-resolved or missing`); } const account: string = stack.account; if (Token.isUnresolved(account) || !account) { - throw new Error(`Cannot generate a physical name for ${resource.construct.path}, because the account is un-resolved or missing`); + throw new Error(`Cannot generate a physical name for ${resource.node.path}, because the account is un-resolved or missing`); } const parts = [stackPart, idPart] diff --git a/packages/@aws-cdk/core/lib/private/prepare-app.ts b/packages/@aws-cdk/core/lib/private/prepare-app.ts index a8d1d33ed04d1..ad900acf803c0 100644 --- a/packages/@aws-cdk/core/lib/private/prepare-app.ts +++ b/packages/@aws-cdk/core/lib/private/prepare-app.ts @@ -17,7 +17,7 @@ import { resolveReferences } from './refs'; */ export function prepareApp(root: IConstruct) { // apply dependencies between resources in depending subtrees - for (const dependency of root.construct.dependencies) { + for (const dependency of root.node.dependencies) { const targetCfnResources = findCfnResources(dependency.target); const sourceCfnResources = findCfnResources(dependency.source); @@ -71,7 +71,7 @@ function findAllNestedStacks(root: IConstruct) { // create a list of all nested stacks in depth-first post order this means // that we first prepare the leaves and then work our way up. - for (const stack of root.construct.findAll(ConstructOrder.POSTORDER /* <== important */)) { + for (const stack of root.node.findAll(ConstructOrder.POSTORDER /* <== important */)) { if (includeStack(stack)) { result.push(stack); } @@ -84,7 +84,7 @@ function findAllNestedStacks(root: IConstruct) { * Find all resources in a set of constructs */ function findCfnResources(root: IConstruct): CfnResource[] { - return root.construct.findAll().filter(CfnResource.isCfnResource); + return root.node.findAll().filter(CfnResource.isCfnResource); } interface INestedStackPrivateApi { diff --git a/packages/@aws-cdk/core/lib/private/refs.ts b/packages/@aws-cdk/core/lib/private/refs.ts index 43c6c4e0a8422..c852f0b94b96b 100644 --- a/packages/@aws-cdk/core/lib/private/refs.ts +++ b/packages/@aws-cdk/core/lib/private/refs.ts @@ -46,14 +46,14 @@ function resolveValue(consumer: Stack, reference: CfnReference): IResolvable { } // unsupported: stacks from different apps - if (producer.construct.root !== consumer.construct.root) { + if (producer.node.root !== consumer.node.root) { throw new Error('Cannot reference across apps. Consuming and producing stacks must be defined within the same CDK app.'); } // unsupported: stacks are not in the same environment if (producer.environment !== consumer.environment) { throw new Error( - `Stack "${consumer.construct.path}" cannot consume a cross reference from stack "${producer.construct.path}". ` + + `Stack "${consumer.node.path}" cannot consume a cross reference from stack "${producer.node.path}". ` + 'Cross stack references are only supported for stacks deployed to the same environment or between nested stacks and their parent stack'); } @@ -99,7 +99,7 @@ function resolveValue(consumer: Stack, reference: CfnReference): IResolvable { // will take care of applying the dependency at the right level (e.g. the // top-level stacks). consumer.addDependency(producer, - `${consumer.construct.path} -> ${reference.target.construct.path}.${reference.displayName}`); + `${consumer.node.path} -> ${reference.target.node.path}.${reference.displayName}`); return createImportValue(reference); } @@ -109,7 +109,7 @@ function resolveValue(consumer: Stack, reference: CfnReference): IResolvable { */ function findAllReferences(root: IConstruct) { const result = new Array<{ source: CfnElement, value: CfnReference }>(); - for (const consumer of root.construct.findAll()) { + for (const consumer of root.node.findAll()) { // include only CfnElements (i.e. resources) if (!CfnElement.isCfnElement(consumer)) { @@ -180,7 +180,7 @@ function createImportValue(reference: Reference): Intrinsic { throw new Error(`unresolved token in generated export name: ${JSON.stringify(exportingStack.resolve(exportName))}`); } - const output = exportsScope.construct.tryFindChild(id) as CfnOutput; + const output = exportsScope.node.tryFindChild(id) as CfnOutput; if (!output) { new CfnOutput(exportsScope, id, { value: Token.asString(reference), exportName }); } @@ -192,7 +192,7 @@ function createImportValue(reference: Reference): Intrinsic { function getCreateExportsScope(stack: Stack) { const exportsName = 'Exports'; - let stackExports = stack.construct.tryFindChild(exportsName) as Construct; + let stackExports = stack.node.tryFindChild(exportsName) as Construct; if (stackExports === undefined) { stackExports = new Construct(stack, exportsName); } @@ -201,13 +201,13 @@ function getCreateExportsScope(stack: Stack) { } function generateExportName(stackExports: Construct, id: string) { - const stackRelativeExports = stackExports.construct.tryGetContext(cxapi.STACK_RELATIVE_EXPORTS_CONTEXT); + const stackRelativeExports = stackExports.node.tryGetContext(cxapi.STACK_RELATIVE_EXPORTS_CONTEXT); const stack = Stack.of(stackExports); const components = [ - ...stackExports.construct.scopes - .slice(stackRelativeExports ? stack.construct.scopes.length : 2) - .map(c => c.construct.id), + ...stackExports.node.scopes + .slice(stackRelativeExports ? stack.node.scopes.length : 2) + .map(c => c.node.id), id, ]; const prefix = stack.stackName ? stack.stackName + ':' : ''; @@ -225,8 +225,8 @@ function generateExportName(stackExports: Construct, id: string) { */ function createNestedStackParameter(nested: Stack, reference: CfnReference, value: IResolvable) { // we call "this.resolve" to ensure that tokens do not creep in (for example, if the reference display name includes tokens) - const paramId = nested.resolve(`reference-to-${reference.target.construct.uniqueId}.${reference.displayName}`); - let param = nested.construct.tryFindChild(paramId) as CfnParameter; + const paramId = nested.resolve(`reference-to-${reference.target.node.uniqueId}.${reference.displayName}`); + let param = nested.node.tryFindChild(paramId) as CfnParameter; if (!param) { param = new CfnParameter(nested, paramId, { type: 'String' }); @@ -246,8 +246,8 @@ function createNestedStackParameter(nested: Stack, reference: CfnReference, valu * intrinsic that can be used to reference this output in the parent stack. */ function createNestedStackOutput(producer: Stack, reference: Reference): CfnReference { - const outputId = `${reference.target.construct.uniqueId}${reference.displayName}`; - let output = producer.construct.tryFindChild(outputId) as CfnOutput; + const outputId = `${reference.target.node.uniqueId}${reference.displayName}`; + let output = producer.node.tryFindChild(outputId) as CfnOutput; if (!output) { output = new CfnOutput(producer, outputId, { value: Token.asString(reference) }); } diff --git a/packages/@aws-cdk/core/lib/private/synthesis.ts b/packages/@aws-cdk/core/lib/private/synthesis.ts index d335c6181dfa6..b4d2368ae6c99 100644 --- a/packages/@aws-cdk/core/lib/private/synthesis.ts +++ b/packages/@aws-cdk/core/lib/private/synthesis.ts @@ -46,7 +46,7 @@ export function synthesize(root: IConstruct, options: SynthesisOptions = { }): c * (They will in turn recurse again) */ function synthNestedAssemblies(root: IConstruct, options: StageSynthesisOptions) { - for (const child of root.construct.children) { + for (const child of root.node.children) { if (Stage.isStage(child)) { child.synth(options); } else { @@ -68,7 +68,7 @@ function invokeAspects(root: IConstruct) { recurse(root, []); function recurse(construct: IConstruct, inheritedAspects: constructs.IAspect[]) { - const node = construct.construct; + const node = construct.node; const aspects = Aspects.of(construct); const allAspectsHere = [...inheritedAspects ?? [], ...aspects.aspects]; const nodeAspectsCount = aspects.aspects.length; @@ -93,7 +93,7 @@ function invokeAspects(root: IConstruct) { invoked.push(aspect); } - for (const child of construct.construct.children) { + for (const child of construct.node.children) { if (!Stage.isStage(child)) { recurse(child, allAspectsHere); } @@ -147,7 +147,7 @@ function validateTree(root: IConstruct) { }); if (errors.length > 0) { - const errorList = errors.map(e => `[${e.source.construct.path}] ${e.message}`).join('\n '); + const errorList = errors.map(e => `[${e.source.node.path}] ${e.message}`).join('\n '); throw new Error(`Validation failed with the following errors:\n ${errorList}`); } } @@ -160,7 +160,7 @@ function visit(root: IConstruct, order: 'pre' | 'post', cb: (x: IProtectedConstr cb(root as IProtectedConstructMethods); } - for (const child of root.construct.children) { + for (const child of root.node.children) { if (Stage.isStage(child)) { continue; } visit(child, order, cb); } diff --git a/packages/@aws-cdk/core/lib/private/tree-metadata.ts b/packages/@aws-cdk/core/lib/private/tree-metadata.ts index 5e87c96b027e1..eb18252a0e8bd 100644 --- a/packages/@aws-cdk/core/lib/private/tree-metadata.ts +++ b/packages/@aws-cdk/core/lib/private/tree-metadata.ts @@ -29,11 +29,11 @@ export class TreeMetadata extends Construct { const lookup: { [path: string]: Node } = { }; const visit = (construct: IConstruct): Node => { - const children = construct.construct.children.map((c) => { + const children = construct.node.children.map((c) => { try { return visit(c); } catch (e) { - Annotations.of(this).addWarning(`Failed to render tree metadata for node [${c.construct.id}]. Reason: ${e}`); + Annotations.of(this).addWarning(`Failed to render tree metadata for node [${c.node.id}]. Reason: ${e}`); return undefined; } }); @@ -42,8 +42,8 @@ export class TreeMetadata extends Construct { .reduce((map, child) => Object.assign(map, { [child!.id]: child }), {}); const node: Node = { - id: construct.construct.id || 'App', - path: construct.construct.path, + id: construct.node.id || 'App', + path: construct.node.path, children: Object.keys(childrenMap).length === 0 ? undefined : childrenMap, attributes: this.synthAttributes(construct), }; @@ -55,7 +55,7 @@ export class TreeMetadata extends Construct { const tree = { version: 'tree-0.1', - tree: visit(this.construct.root), + tree: visit(this.node.root), }; const builder = session.assembly; diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index 129deed73d04f..6574dbe331da0 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -96,7 +96,7 @@ export abstract class Resource extends Construct implements IResource { public _enableCrossEnvironment(): void { if (!this._allowCrossEnvironment) { // error out - a deploy-time name cannot be used across environments - throw new Error(`Cannot use resource '${this.construct.path}' in a cross-environment fashion, ` + + throw new Error(`Cannot use resource '${this.node.path}' in a cross-environment fashion, ` + "the resource's physical name must be explicit set or use `PhysicalName.GENERATE_IF_NEEDED`"); } diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts index 29a139020c1b2..5a57c423779df 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/_shared.ts @@ -23,7 +23,7 @@ export function addStackArtifactToAssembly( // level and are not needed in the cloud assembly. // TODO: move these to the cloud assembly artifact properties instead of metadata if (stack.tags.hasTags()) { - stack.construct.addMetadata(cxschema.ArtifactMetadataEntryType.STACK_TAGS, stack.tags.renderTags()); + stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.STACK_TAGS, stack.tags.renderTags()); } const deps = [ @@ -77,12 +77,12 @@ function collectStackMetadata(stack: Stack) { return; } - if (node.construct.metadata.length > 0) { + if (node.node.metadata.length > 0) { // Make the path absolute - output[ConstructNode.PATH_SEP + node.construct.path] = node.construct.metadata.map(md => stack.resolve(md) as cxschema.MetadataEntry); + output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md) as cxschema.MetadataEntry); } - for (const child of node.construct.children) { + for (const child of node.node.children) { visit(child); } } @@ -92,11 +92,11 @@ function collectStackMetadata(stack: Stack) { return node; } - if (!node.construct.scope) { + if (!node.node.scope) { return undefined; } - return findParentStack(node.construct.scope); + return findParentStack(node.node.scope); } } diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index dd873b6d13e6a..5a373cd3ed061 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -207,7 +207,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { public bind(stack: Stack): void { this._stack = stack; - const qualifier = this.props.qualifier ?? stack.construct.tryGetContext(BOOTSTRAP_QUALIFIER_CONTEXT) ?? DefaultStackSynthesizer.DEFAULT_QUALIFIER; + const qualifier = this.props.qualifier ?? stack.node.tryGetContext(BOOTSTRAP_QUALIFIER_CONTEXT) ?? DefaultStackSynthesizer.DEFAULT_QUALIFIER; // Function to replace placeholders in the input string as much as possible // diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts index 850d1f08d2135..06facd881626f 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/legacy.ts @@ -102,7 +102,7 @@ export class LegacyStackSynthesizer implements IStackSynthesizer { assertBound(this.stack); // check if we have an override from context - const repositoryNameOverride = this.stack.construct.tryGetContext(ASSETS_ECR_REPOSITORY_NAME_OVERRIDE_CONTEXT_KEY); + const repositoryNameOverride = this.stack.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; @@ -121,7 +121,7 @@ export class LegacyStackSynthesizer implements IStackSynthesizer { file: asset.dockerFile, }; - this.stack.construct.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); + this.stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); this.addedImageAssets.add(assetId); } @@ -134,7 +134,7 @@ export class LegacyStackSynthesizer implements IStackSynthesizer { private doAddFileAsset(asset: FileAssetSource): FileAssetLocation { assertBound(this.stack); - let params = this.assetParameters.construct.tryFindChild(asset.sourceHash) as FileAssetParameters; + let params = this.assetParameters.node.tryFindChild(asset.sourceHash) as FileAssetParameters; if (!params) { params = new FileAssetParameters(this.assetParameters, asset.sourceHash); @@ -149,7 +149,7 @@ export class LegacyStackSynthesizer implements IStackSynthesizer { artifactHashParameter: params.artifactHashParameter.logicalId, }; - this.stack.construct.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); + this.stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); } const bucketName = params.bucketNameParameter.valueAsString; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index b30b008ab520d..1c7bbf4b50e07 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -168,11 +168,11 @@ export class Stack extends Construct implements ITaggable { return c; } - if (!c.construct.scope) { - throw new Error(`No stack could be identified for the construct at path ${construct.construct.path}`); + if (!c.node.scope) { + throw new Error(`No stack could be identified for the construct at path ${construct.node.path}`); } - return _lookup(c.construct.scope); + return _lookup(c.node.scope); } } @@ -359,13 +359,13 @@ export class Stack extends Construct implements ITaggable { // Also use the new behavior if we are using the new CI/CD-ready synthesizer; that way // people only have to flip one flag. // eslint-disable-next-line max-len - this.artifactId = this.construct.tryGetContext(cxapi.ENABLE_STACK_NAME_DUPLICATES_CONTEXT) || this.construct.tryGetContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT) + this.artifactId = this.node.tryGetContext(cxapi.ENABLE_STACK_NAME_DUPLICATES_CONTEXT) || this.node.tryGetContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT) ? this.generateStackArtifactId() : this.stackName; this.templateFile = `${this.artifactId}.template.json`; - this.synthesizer = props.synthesizer ?? (this.construct.tryGetContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT) + this.synthesizer = props.synthesizer ?? (this.node.tryGetContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT) ? new DefaultStackSynthesizer() : new LegacyStackSynthesizer()); this.synthesizer.bind(this); @@ -593,7 +593,7 @@ export class Stack extends Construct implements ITaggable { // denominator is 2 AZs across all AWS regions. const agnostic = Token.isUnresolved(this.account) || Token.isUnresolved(this.region); if (agnostic) { - return this.construct.tryGetContext(cxapi.AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY) || [ + return this.node.tryGetContext(cxapi.AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY) || [ Fn.select(0, Fn.getAzs()), Fn.select(1, Fn.getAzs()), ]; @@ -685,12 +685,12 @@ export class Stack extends Construct implements ITaggable { const cycle = target.stackDependencyReasons(this); if (cycle !== undefined) { // eslint-disable-next-line max-len - throw new Error(`'${target.construct.path}' depends on '${this.construct.path}' (${cycle.join(', ')}). Adding this dependency (${reason}) would create a cyclic reference.`); + throw new Error(`'${target.node.path}' depends on '${this.node.path}' (${cycle.join(', ')}). Adding this dependency (${reason}) would create a cyclic reference.`); } - let dep = this._stackDependencies[target.construct.uniqueId]; + let dep = this._stackDependencies[target.node.uniqueId]; if (!dep) { - dep = this._stackDependencies[target.construct.uniqueId] = { + dep = this._stackDependencies[target.node.uniqueId] = { stack: target, reasons: [], }; @@ -700,7 +700,7 @@ export class Stack extends Construct implements ITaggable { if (process.env.CDK_DEBUG_DEPS) { // eslint-disable-next-line no-console - console.error(`[CDK_DEBUG_DEPS] stack "${this.construct.path}" depends on "${target.construct.path}" because: ${reason}`); + console.error(`[CDK_DEBUG_DEPS] stack "${this.node.path}" depends on "${target.node.path}" because: ${reason}`); } } @@ -771,9 +771,9 @@ export class Stack extends Construct implements ITaggable { * @param cfnElement The element for which the logical ID is allocated. */ protected allocateLogicalId(cfnElement: CfnElement): string { - const scopes = cfnElement.construct.scopes; + const scopes = cfnElement.node.scopes; const stackIndex = scopes.indexOf(cfnElement.stack); - const pathComponents = scopes.slice(stackIndex + 1).map(x => x.construct.id); + const pathComponents = scopes.slice(stackIndex + 1).map(x => x.node.id); return makeUniqueId(pathComponents); } @@ -925,7 +925,7 @@ export class Stack extends Construct implements ITaggable { * Stack artifact ID is unique within the App's Cloud Assembly. */ private generateStackArtifactId() { - return this.generateStackId(this.construct.root); + return this.generateStackId(this.node.root); } /** @@ -933,7 +933,7 @@ export class Stack extends Construct implements ITaggable { */ private generateStackId(container: IConstruct | undefined) { const rootPath = rootPathTo(this, container); - const ids = rootPath.map(c => c.construct.id); + const ids = rootPath.map(c => c.node.id); // In unit tests our Stack (which is the only component) may not have an // id, so in that case just pretend it's "Stack". @@ -1050,7 +1050,7 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] { into.push(node); } - for (const child of node.construct.children) { + for (const child of node.node.children) { // Don't recurse into a substack if (Stack.isStack(child)) { continue; } @@ -1066,7 +1066,7 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] { * If no ancestor is given or the ancestor is not found, return the entire root path. */ export function rootPathTo(construct: IConstruct, ancestor?: IConstruct): IConstruct[] { - const scopes = construct.construct.scopes; + const scopes = construct.node.scopes; for (let i = scopes.length - 2; i >= 0; i--) { if (scopes[i] === ancestor) { return scopes.slice(i + 1); diff --git a/packages/@aws-cdk/core/lib/stage.ts b/packages/@aws-cdk/core/lib/stage.ts index fd8013cb6045b..a8a0d1d18523a 100644 --- a/packages/@aws-cdk/core/lib/stage.ts +++ b/packages/@aws-cdk/core/lib/stage.ts @@ -73,7 +73,7 @@ export class Stage extends Construct { * @experimental */ public static of(construct: IConstruct): Stage | undefined { - return construct.construct.scopes.reverse().slice(1).find(Stage.isStage); + return construct.node.scopes.reverse().slice(1).find(Stage.isStage); } /** @@ -159,8 +159,8 @@ export class Stage extends Construct { * @experimental */ public get artifactId() { - if (!this.construct.path) { return ''; } - return `assembly-${this.construct.path.replace(/\//g, '-').replace(/^-+|-+$/g, '')}`; + if (!this.node.path) { return ''; } + return `assembly-${this.node.path.replace(/\//g, '-').replace(/^-+|-+$/g, '')}`; } /** @@ -171,7 +171,7 @@ export class Stage extends Construct { */ public synth(options: StageSynthesisOptions = { }): cxapi.CloudAssembly { if (!this.assembly || options.force) { - const runtimeInfo = this.construct.tryGetContext(cxapi.DISABLE_VERSION_REPORTING) ? undefined : collectRuntimeInformation(); + const runtimeInfo = this.node.tryGetContext(cxapi.DISABLE_VERSION_REPORTING) ? undefined : collectRuntimeInformation(); this.assembly = synthesize(this, { skipValidation: options.skipValidation, runtimeInfo, @@ -191,7 +191,7 @@ export class Stage extends Construct { // to write sub-assemblies (which must happen before we actually get to this app's // synthesize() phase). return this.parentStage - ? this.parentStage._assemblyBuilder.createNestedAssembly(this.artifactId, this.construct.path) + ? this.parentStage._assemblyBuilder.createNestedAssembly(this.artifactId, this.node.path) : new cxapi.CloudAssemblyBuilder(outdir); } } diff --git a/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts b/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts index c13231d1eac95..ca3d8e4839241 100644 --- a/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts +++ b/packages/@aws-cdk/core/test/custom-resource-provider/test.custom-resource-provider.ts @@ -23,7 +23,7 @@ export = { // The asset hash constantly changes, so in order to not have to chase it, just look // it up from the output. - const staging = stack.construct.tryFindChild('Custom:MyResourceTypeCustomResourceProvider')?.construct.tryFindChild('Staging') as AssetStaging; + const staging = stack.node.tryFindChild('Custom:MyResourceTypeCustomResourceProvider')?.node.tryFindChild('Staging') as AssetStaging; const assetHash = staging.sourceHash; const paramNames = Object.keys(cfn.Parameters); const bucketParam = paramNames[0]; diff --git a/packages/@aws-cdk/core/test/private/test.tree-metadata.ts b/packages/@aws-cdk/core/test/private/test.tree-metadata.ts index 9d3c19a811085..3ef3966b2fd95 100644 --- a/packages/@aws-cdk/core/test/private/test.tree-metadata.ts +++ b/packages/@aws-cdk/core/test/private/test.tree-metadata.ts @@ -279,9 +279,9 @@ export = { const treeArtifact = assembly.tree(); test.ok(treeArtifact); - const treenode = app.construct.findChild('Tree'); + const treenode = app.node.findChild('Tree'); - const warn = treenode.construct.metadata.find((md) => { + const warn = treenode.node.metadata.find((md) => { return md.type === cxschema.ArtifactMetadataEntryType.WARN && /Forcing an inspect error/.test(md.data as string) && /mycfnresource/.test(md.data as string); diff --git a/packages/@aws-cdk/core/test/test.app.ts b/packages/@aws-cdk/core/test/test.app.ts index 8e3cad39a7dc2..12efa5bd73edc 100644 --- a/packages/@aws-cdk/core/test/test.app.ts +++ b/packages/@aws-cdk/core/test/test.app.ts @@ -28,11 +28,11 @@ function synth(context?: { [key: string]: any }): cxapi.CloudAssembly { const c1 = new MyConstruct(stack2, 's1c2'); // add some metadata - stack1.construct.addMetadata('meta', 111); + stack1.node.addMetadata('meta', 111); Annotations.of(r2).addWarning('warning1'); Annotations.of(r2).addWarning('warning2'); - c1.construct.addMetadata('meta', { key: 'value' }); - app.construct.addMetadata('applevel', 123); // apps can also have metadata + c1.node.addMetadata('meta', { key: 'value' }); + app.node.addMetadata('applevel', 123); // apps can also have metadata }); } @@ -98,8 +98,8 @@ export = { key2: 'val2', }); const prog = new App(); - test.deepEqual(prog.construct.tryGetContext('key1'), 'val1'); - test.deepEqual(prog.construct.tryGetContext('key2'), 'val2'); + test.deepEqual(prog.node.tryGetContext('key1'), 'val1'); + test.deepEqual(prog.node.tryGetContext('key2'), 'val2'); test.done(); }, @@ -114,8 +114,8 @@ export = { key2: 'val4', }, }); - test.deepEqual(prog.construct.tryGetContext('key1'), 'val1'); - test.deepEqual(prog.construct.tryGetContext('key2'), 'val2'); + test.deepEqual(prog.node.tryGetContext('key1'), 'val1'); + test.deepEqual(prog.node.tryGetContext('key2'), 'val2'); test.done(); }, @@ -150,14 +150,14 @@ export = { foo: 'bar', }, }); - test.deepEqual(prog.construct.tryGetContext('foo'), 'bar'); + test.deepEqual(prog.node.tryGetContext('foo'), 'bar'); test.done(); }, 'setContext(k,v) cannot be called after stacks have been added because stacks may use the context'(test: Test) { const prog = new App(); new Stack(prog, 's1'); - test.throws(() => prog.construct.setContext('foo', 'bar')); + test.throws(() => prog.node.setContext('foo', 'bar')); test.done(); }, @@ -165,7 +165,7 @@ export = { class Child extends Construct { protected validate() { - return [`Error from ${this.construct.id}`]; + return [`Error from ${this.node.id}`]; } } @@ -361,9 +361,9 @@ export = { }, }); - test.ok(app.construct.tryGetContext('isString') === 'string'); - test.ok(app.construct.tryGetContext('isNumber') === 10); - test.deepEqual(app.construct.tryGetContext('isObject'), { isString: 'string', isNumber: 10 }); + test.ok(app.node.tryGetContext('isString') === 'string'); + test.ok(app.node.tryGetContext('isNumber') === 10); + test.deepEqual(app.node.tryGetContext('isObject'), { isString: 'string', isNumber: 10 }); test.done(); }, @@ -374,6 +374,6 @@ class MyConstruct extends Construct { super(scope, id); new CfnResource(this, 'r1', { type: 'ResourceType1' }); - new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.construct.tryGetContext('ctx1') } }); + new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.node.tryGetContext('ctx1') } }); } } diff --git a/packages/@aws-cdk/core/test/test.aspect.ts b/packages/@aws-cdk/core/test/test.aspect.ts index 424747fd80f55..90294a86055a7 100644 --- a/packages/@aws-cdk/core/test/test.aspect.ts +++ b/packages/@aws-cdk/core/test/test.aspect.ts @@ -21,7 +21,7 @@ class VisitOnce implements IAspect { class MyAspect implements IAspect { public visit(node: IConstruct): void { - node.construct.addMetadata('foo', 'bar'); + node.node.addMetadata('foo', 'bar'); } } @@ -45,16 +45,16 @@ export = { visit(construct: IConstruct) { Aspects.of(construct).add({ visit(inner: IConstruct) { - inner.construct.addMetadata('test', 'would-be-ignored'); + inner.node.addMetadata('test', 'would-be-ignored'); }, }); }, }); app.synth(); - test.deepEqual(root.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); - test.deepEqual(root.construct.metadata[0].data, 'We detected an Aspect was added via another Aspect, and will not be applied'); + test.deepEqual(root.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); + test.deepEqual(root.node.metadata[0].data, 'We detected an Aspect was added via another Aspect, and will not be applied'); // warning is not added to child construct - test.equal(child.construct.metadata.length, 0); + test.equal(child.node.metadata.length, 0); test.done(); }, @@ -64,13 +64,13 @@ export = { const child = new MyConstruct(root, 'ChildConstruct'); Aspects.of(root).add(new MyAspect()); app.synth(); - test.deepEqual(root.construct.metadata[0].type, 'foo'); - test.deepEqual(root.construct.metadata[0].data, 'bar'); - test.deepEqual(root.construct.metadata[0].type, 'foo'); - test.deepEqual(child.construct.metadata[0].data, 'bar'); + test.deepEqual(root.node.metadata[0].type, 'foo'); + test.deepEqual(root.node.metadata[0].data, 'bar'); + test.deepEqual(root.node.metadata[0].type, 'foo'); + test.deepEqual(child.node.metadata[0].data, 'bar'); // no warning is added - test.equal(root.construct.metadata.length, 1); - test.equal(child.construct.metadata.length, 1); + test.equal(root.node.metadata.length, 1); + test.equal(child.node.metadata.length, 1); test.done(); }, diff --git a/packages/@aws-cdk/core/test/test.assets.ts b/packages/@aws-cdk/core/test/test.assets.ts index a7dcfd50ed767..5f77e89db470f 100644 --- a/packages/@aws-cdk/core/test/test.assets.ts +++ b/packages/@aws-cdk/core/test/test.assets.ts @@ -16,7 +16,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.ok(assetMetadata && assetMetadata.data); @@ -83,7 +83,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.ok(assetMetadata && assetMetadata.data); @@ -111,7 +111,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.ok(assetMetadata && assetMetadata.data); @@ -131,7 +131,7 @@ export = { '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.construct.setContext('assets-ecr-repository-name', 'my-custom-repo-name'); + stack.node.setContext('assets-ecr-repository-name', 'my-custom-repo-name'); // WHEN stack.addDockerImageAsset({ @@ -140,7 +140,7 @@ export = { }); // THEN - const assetMetadata = stack.construct.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); test.ok(assetMetadata && assetMetadata.data); diff --git a/packages/@aws-cdk/core/test/test.cfn-resource.ts b/packages/@aws-cdk/core/test/test.cfn-resource.ts index 7ac09497b11f7..5033af4b21598 100644 --- a/packages/@aws-cdk/core/test/test.cfn-resource.ts +++ b/packages/@aws-cdk/core/test/test.cfn-resource.ts @@ -115,7 +115,7 @@ export = nodeunit.testCase({ const r2 = new core.CfnResource(stack, 'R2', { type: 'Foo::R2' }); // also try to take a dependency on the parent of `r1` and expect the dependency not to materialize - r2.construct.addDependency(subtree); + r2.node.addDependency(subtree); // THEN - only R2 is synthesized test.deepEqual(app.synth().getStackByName(stack.stackName).template, { diff --git a/packages/@aws-cdk/core/test/test.construct.ts b/packages/@aws-cdk/core/test/test.construct.ts index fe7eae49419ae..cb128cf9e4126 100644 --- a/packages/@aws-cdk/core/test/test.construct.ts +++ b/packages/@aws-cdk/core/test/test.construct.ts @@ -9,9 +9,9 @@ import { reEnableStackTraceCollection, restoreStackTraceColection } from './util export = { 'the "Root" construct is a special construct which can be used as the root of the tree'(test: Test) { const root = new Root(); - test.equal(root.construct.id, '', 'if not specified, name of a root construct is an empty string'); - test.ok(!root.construct.scope, 'no parent'); - test.equal(root.construct.children.length, 1); + test.equal(root.node.id, '', 'if not specified, name of a root construct is an empty string'); + test.ok(!root.node.scope, 'no parent'); + test.equal(root.node.children.length, 1); test.done(); }, @@ -24,12 +24,12 @@ export = { 'construct.name returns the name of the construct'(test: Test) { const t = createTree(); - test.equal(t.child1.construct.id, 'Child1'); - test.equal(t.child2.construct.id, 'Child2'); - test.equal(t.child1_1.construct.id, 'Child11'); - test.equal(t.child1_2.construct.id, 'Child12'); - test.equal(t.child1_1_1.construct.id, 'Child111'); - test.equal(t.child2_1.construct.id, 'Child21'); + test.equal(t.child1.node.id, 'Child1'); + test.equal(t.child2.node.id, 'Child2'); + test.equal(t.child1_1.node.id, 'Child11'); + test.equal(t.child1_2.node.id, 'Child12'); + test.equal(t.child1_1_1.node.id, 'Child111'); + test.equal(t.child2_1.node.id, 'Child21'); test.done(); }, @@ -54,13 +54,13 @@ export = { 'if construct id contains path seperators, they will be replaced by double-dash'(test: Test) { const root = new Root(); const c = new Construct(root, 'Boom/Boom/Bam'); - test.deepEqual(c.construct.id, 'Boom--Boom--Bam'); + test.deepEqual(c.node.id, 'Boom--Boom--Bam'); test.done(); }, 'if "undefined" is forcefully used as an "id", it will be treated as an empty string'(test: Test) { const c = new Construct(undefined as any, undefined as any); - test.deepEqual(c.construct.id, ''); + test.deepEqual(c.node.id, ''); test.done(); }, @@ -82,17 +82,17 @@ export = { const c1 = new Construct(child2, 'My construct'); const c2 = new Construct(child1, 'My construct'); - test.deepEqual(c1.construct.path, 'This is the first child/Second level/My construct'); - test.deepEqual(c2.construct.path, 'This is the first child/My construct'); - test.deepEqual(c1.construct.uniqueId, 'ThisisthefirstchildSecondlevelMyconstruct202131E0'); - test.deepEqual(c2.construct.uniqueId, 'ThisisthefirstchildMyconstruct8C288DF9'); + test.deepEqual(c1.node.path, 'This is the first child/Second level/My construct'); + test.deepEqual(c2.node.path, 'This is the first child/My construct'); + test.deepEqual(c1.node.uniqueId, 'ThisisthefirstchildSecondlevelMyconstruct202131E0'); + test.deepEqual(c2.node.uniqueId, 'ThisisthefirstchildMyconstruct8C288DF9'); test.done(); }, 'cannot calculate uniqueId if the construct path is ["Default"]'(test: Test) { const root = new Root(); const c = new Construct(root, 'Default'); - test.throws(() => c.construct.uniqueId, /Unable to calculate a unique id for an empty set of components/); + test.throws(() => c.node.uniqueId, /Unable to calculate a unique id for an empty set of components/); test.done(); }, @@ -100,40 +100,40 @@ export = { const root = new Root(); const child = new Construct(root, 'Child1'); new Construct(root, 'Child2'); - test.equal(child.construct.children.length, 0, 'no children'); - test.equal(root.construct.children.length, 3, 'three children are expected'); + test.equal(child.node.children.length, 0, 'no children'); + test.equal(root.node.children.length, 3, 'three children are expected'); test.done(); }, 'construct.findChild(name) can be used to retrieve a child from a parent'(test: Test) { const root = new Root(); const child = new Construct(root, 'Contruct'); - test.strictEqual(root.construct.tryFindChild(child.construct.id), child, 'findChild(name) can be used to retrieve the child from a parent'); - test.ok(!root.construct.tryFindChild('NotFound'), 'findChild(name) returns undefined if the child is not found'); + test.strictEqual(root.node.tryFindChild(child.node.id), child, 'findChild(name) can be used to retrieve the child from a parent'); + test.ok(!root.node.tryFindChild('NotFound'), 'findChild(name) returns undefined if the child is not found'); test.done(); }, 'construct.getChild(name) can be used to retrieve a child from a parent'(test: Test) { const root = new Root(); const child = new Construct(root, 'Contruct'); - test.strictEqual(root.construct.findChild(child.construct.id), child, 'getChild(name) can be used to retrieve the child from a parent'); + test.strictEqual(root.node.findChild(child.node.id), child, 'getChild(name) can be used to retrieve the child from a parent'); test.throws(() => { - root.construct.findChild('NotFound'); + root.node.findChild('NotFound'); }, '', 'getChild(name) returns undefined if the child is not found'); test.done(); }, 'can remove children from the tree using tryRemoveChild()'(test: Test) { const root = new Root(); - const childrenBeforeAdding = root.construct.children.length; // Invariant to adding 'Metadata' resource or not + const childrenBeforeAdding = root.node.children.length; // Invariant to adding 'Metadata' resource or not // Add & remove const child = new Construct(root, 'Construct'); - test.equals(true, root.construct.tryRemoveChild(child.construct.id)); - test.equals(false, root.construct.tryRemoveChild(child.construct.id)); // Second time does nothing + test.equals(true, root.node.tryRemoveChild(child.node.id)); + test.equals(false, root.node.tryRemoveChild(child.node.id)); // Second time does nothing - test.equals(undefined, root.construct.tryFindChild(child.construct.id)); - test.equals(childrenBeforeAdding, root.construct.children.length); + test.equals(undefined, root.node.tryFindChild(child.node.id)); + test.equals(childrenBeforeAdding, root.node.children.length); test.done(); }, @@ -154,8 +154,8 @@ export = { }; const t = createTree(context); - test.equal(t.child1_2.construct.tryGetContext('ctx1'), 12); - test.equal(t.child1_1_1.construct.tryGetContext('ctx2'), 'hello'); + test.equal(t.child1_2.node.tryGetContext('ctx1'), 12); + test.equal(t.child1_1_1.node.tryGetContext('ctx2'), 'hello'); test.done(); }, @@ -163,34 +163,34 @@ export = { 'construct.setContext(k,v) sets context at some level and construct.getContext(key) will return the lowermost value defined in the stack'(test: Test) { const root = new Root(); const highChild = new Construct(root, 'highChild'); - highChild.construct.setContext('c1', 'root'); - highChild.construct.setContext('c2', 'root'); + highChild.node.setContext('c1', 'root'); + highChild.node.setContext('c2', 'root'); const child1 = new Construct(highChild, 'child1'); - child1.construct.setContext('c2', 'child1'); - child1.construct.setContext('c3', 'child1'); + child1.node.setContext('c2', 'child1'); + child1.node.setContext('c3', 'child1'); const child2 = new Construct(highChild, 'child2'); const child3 = new Construct(child1, 'child1child1'); - child3.construct.setContext('c1', 'child3'); - child3.construct.setContext('c4', 'child3'); + child3.node.setContext('c1', 'child3'); + child3.node.setContext('c4', 'child3'); - test.equal(highChild.construct.tryGetContext('c1'), 'root'); - test.equal(highChild.construct.tryGetContext('c2'), 'root'); - test.equal(highChild.construct.tryGetContext('c3'), undefined); + test.equal(highChild.node.tryGetContext('c1'), 'root'); + test.equal(highChild.node.tryGetContext('c2'), 'root'); + test.equal(highChild.node.tryGetContext('c3'), undefined); - test.equal(child1.construct.tryGetContext('c1'), 'root'); - test.equal(child1.construct.tryGetContext('c2'), 'child1'); - test.equal(child1.construct.tryGetContext('c3'), 'child1'); + test.equal(child1.node.tryGetContext('c1'), 'root'); + test.equal(child1.node.tryGetContext('c2'), 'child1'); + test.equal(child1.node.tryGetContext('c3'), 'child1'); - test.equal(child2.construct.tryGetContext('c1'), 'root'); - test.equal(child2.construct.tryGetContext('c2'), 'root'); - test.equal(child2.construct.tryGetContext('c3'), undefined); + test.equal(child2.node.tryGetContext('c1'), 'root'); + test.equal(child2.node.tryGetContext('c2'), 'root'); + test.equal(child2.node.tryGetContext('c3'), undefined); - test.equal(child3.construct.tryGetContext('c1'), 'child3'); - test.equal(child3.construct.tryGetContext('c2'), 'child1'); - test.equal(child3.construct.tryGetContext('c3'), 'child1'); - test.equal(child3.construct.tryGetContext('c4'), 'child3'); + test.equal(child3.node.tryGetContext('c1'), 'child3'); + test.equal(child3.node.tryGetContext('c2'), 'child1'); + test.equal(child3.node.tryGetContext('c3'), 'child1'); + test.equal(child3.node.tryGetContext('c4'), 'child3'); test.done(); }, @@ -198,29 +198,29 @@ export = { 'construct.setContext(key, value) can only be called before adding any children'(test: Test) { const root = new Root(); new Construct(root, 'child1'); - test.throws(() => root.construct.setContext('k', 'v')); + test.throws(() => root.node.setContext('k', 'v')); test.done(); }, 'fails if context key contains unresolved tokens'(test: Test) { const root = new Root(); - test.throws(() => root.construct.setContext(`my-${Aws.REGION}`, 'foo'), /Invalid context key/); - test.throws(() => root.construct.tryGetContext(Aws.REGION), /Invalid context key/); + test.throws(() => root.node.setContext(`my-${Aws.REGION}`, 'foo'), /Invalid context key/); + test.throws(() => root.node.tryGetContext(Aws.REGION), /Invalid context key/); test.done(); }, 'construct.pathParts returns an array of strings of all names from root to node'(test: Test) { const tree = createTree(); - test.deepEqual(tree.root.construct.path, ''); - test.deepEqual(tree.child1_1_1.construct.path, 'HighChild/Child1/Child11/Child111'); - test.deepEqual(tree.child2.construct.path, 'HighChild/Child2'); + test.deepEqual(tree.root.node.path, ''); + test.deepEqual(tree.child1_1_1.node.path, 'HighChild/Child1/Child11/Child111'); + test.deepEqual(tree.child2.node.path, 'HighChild/Child2'); test.done(); }, 'if a root construct has a name, it should be included in the path'(test: Test) { const tree = createTree({}); - test.deepEqual(tree.root.construct.path, ''); - test.deepEqual(tree.child1_1_1.construct.path, 'HighChild/Child1/Child11/Child111'); + test.deepEqual(tree.root.node.path, ''); + test.deepEqual(tree.child1_1_1.node.path, 'HighChild/Child1/Child11/Child111'); test.done(); }, @@ -251,31 +251,31 @@ export = { const previousValue = reEnableStackTraceCollection(); const root = new Root(); const con = new Construct(root, 'MyConstruct'); - test.deepEqual(con.construct.metadata, [], 'starts empty'); + test.deepEqual(con.node.metadata, [], 'starts empty'); - con.construct.addMetadata('key', 'value'); - con.construct.addMetadata('number', 103); - con.construct.addMetadata('array', [ 123, 456 ]); + con.node.addMetadata('key', 'value'); + con.node.addMetadata('number', 103); + con.node.addMetadata('array', [ 123, 456 ]); restoreStackTraceColection(previousValue); - test.deepEqual(con.construct.metadata[0].type, 'key'); - test.deepEqual(con.construct.metadata[0].data, 'value'); - test.deepEqual(con.construct.metadata[1].data, 103); - test.deepEqual(con.construct.metadata[2].data, [ 123, 456 ]); - test.ok(con.construct.metadata[0].trace && con.construct.metadata[0].trace[1].indexOf('FIND_ME') !== -1, 'First stack line should include this function\s name'); + test.deepEqual(con.node.metadata[0].type, 'key'); + test.deepEqual(con.node.metadata[0].data, 'value'); + test.deepEqual(con.node.metadata[1].data, 103); + test.deepEqual(con.node.metadata[2].data, [ 123, 456 ]); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace[1].indexOf('FIND_ME') !== -1, 'First stack line should include this function\s name'); test.done(); }, 'addMetadata(type, undefined/null) is ignored'(test: Test) { const root = new Root(); const con = new Construct(root, 'Foo'); - con.construct.addMetadata('Null', null); - con.construct.addMetadata('Undefined', undefined); - con.construct.addMetadata('True', true); - con.construct.addMetadata('False', false); - con.construct.addMetadata('Empty', ''); + con.node.addMetadata('Null', null); + con.node.addMetadata('Undefined', undefined); + con.node.addMetadata('True', true); + con.node.addMetadata('False', false); + con.node.addMetadata('Empty', ''); - const exists = (key: string) => con.construct.metadata.find(x => x.type === key); + const exists = (key: string) => con.node.metadata.find(x => x.type === key); test.ok(!exists('Null')); test.ok(!exists('Undefined')); @@ -292,9 +292,9 @@ export = { Annotations.of(con).addWarning('This construct is deprecated, use the other one instead'); restoreStackTraceColection(previousValue); - test.deepEqual(con.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); - test.deepEqual(con.construct.metadata[0].data, 'This construct is deprecated, use the other one instead'); - test.ok(con.construct.metadata[0].trace && con.construct.metadata[0].trace.length > 0); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); + test.deepEqual(con.node.metadata[0].data, 'This construct is deprecated, use the other one instead'); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); }, @@ -305,9 +305,9 @@ export = { Annotations.of(con).addError('Stop!'); restoreStackTraceColection(previousValue); - test.deepEqual(con.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.ERROR); - test.deepEqual(con.construct.metadata[0].data, 'Stop!'); - test.ok(con.construct.metadata[0].trace && con.construct.metadata[0].trace.length > 0); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.ERROR); + test.deepEqual(con.node.metadata[0].data, 'Stop!'); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); }, @@ -318,9 +318,9 @@ export = { Annotations.of(con).addInfo('Hey there, how do you do?'); restoreStackTraceColection(previousValue); - test.deepEqual(con.construct.metadata[0].type, cxschema.ArtifactMetadataEntryType.INFO); - test.deepEqual(con.construct.metadata[0].data, 'Hey there, how do you do?'); - test.ok(con.construct.metadata[0].trace && con.construct.metadata[0].trace.length > 0); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.INFO); + test.deepEqual(con.node.metadata[0].data, 'Hey there, how do you do?'); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); }, @@ -330,12 +330,12 @@ export = { new MyBeautifulConstruct(root, 'mbc2'); new MyBeautifulConstruct(root, 'mbc3'); new MyBeautifulConstruct(root, 'mbc4'); - test.ok(root.construct.children.length >= 4); + test.ok(root.node.children.length >= 4); test.done(); }, // eslint-disable-next-line max-len - 'construct.validate() can be implemented to perform validation, ConstructNode.validate(construct.construct) will return all errors from the subtree (DFS)'(test: Test) { + 'construct.validate() can be implemented to perform validation, ConstructNode.validate(construct.node) will return all errors from the subtree (DFS)'(test: Test) { class MyConstruct extends Construct { protected validate() { @@ -376,7 +376,7 @@ export = { const stack = new TestStack(); - const errors = ConstructNode.validate(stack.construct).map((v: ValidationError) => ({ path: v.source.construct.path, message: v.message })); + const errors = ConstructNode.validate(stack.node).map((v: ValidationError) => ({ path: v.source.node.path, message: v.message })); // validate DFS test.deepEqual(errors, [ @@ -394,11 +394,11 @@ export = { class LockableConstruct extends Construct { public lockMe() { - (this.construct._actualNode as any)._lock(); + (this.node._actualNode as any)._lock(); } public unlockMe() { - (this.construct._actualNode as any)._unlock(); + (this.node._actualNode as any)._unlock(); } } @@ -436,23 +436,23 @@ export = { new Construct(c2, '5'); // THEN - test.deepEqual(c1.construct.findAll().map(x => x.construct.id), c1.construct.findAll(ConstructOrder.PREORDER).map(x => x.construct.id)); // default is PreOrder - test.deepEqual(c1.construct.findAll(ConstructOrder.PREORDER).map(x => x.construct.id), [ '1', '2', '4', '5', '3' ]); - test.deepEqual(c1.construct.findAll(ConstructOrder.POSTORDER).map(x => x.construct.id), [ '4', '5', '2', '3', '1' ]); + test.deepEqual(c1.node.findAll().map(x => x.node.id), c1.node.findAll(ConstructOrder.PREORDER).map(x => x.node.id)); // default is PreOrder + test.deepEqual(c1.node.findAll(ConstructOrder.PREORDER).map(x => x.node.id), [ '1', '2', '4', '5', '3' ]); + test.deepEqual(c1.node.findAll(ConstructOrder.POSTORDER).map(x => x.node.id), [ '4', '5', '2', '3', '1' ]); test.done(); }, 'ancestors returns a list of parents up to root'(test: Test) { const { child1_1_1 } = createTree(); - test.deepEqual(child1_1_1.construct.scopes.map(x => x.construct.id), [ '', 'HighChild', 'Child1', 'Child11', 'Child111' ]); + test.deepEqual(child1_1_1.node.scopes.map(x => x.node.id), [ '', 'HighChild', 'Child1', 'Child11', 'Child111' ]); test.done(); }, '"root" returns the root construct'(test: Test) { const { child1, child2, child1_1_1, root } = createTree(); - test.ok(child1.construct.root === root); - test.ok(child2.construct.root === root); - test.ok(child1_1_1.construct.root === root); + test.ok(child1.node.root === root); + test.ok(child2.node.root === root); + test.ok(child1_1_1.node.root === root); test.done(); }, @@ -463,7 +463,7 @@ export = { const defaultChild = new Construct(root, 'Resource'); new Construct(root, 'child2'); - test.same(root.construct.defaultChild, defaultChild); + test.same(root.node.defaultChild, defaultChild); test.done(); }, 'returns the child with id "Default"'(test: Test) { @@ -472,16 +472,16 @@ export = { const defaultChild = new Construct(root, 'Default'); new Construct(root, 'child2'); - test.same(root.construct.defaultChild, defaultChild); + test.same(root.node.defaultChild, defaultChild); test.done(); }, 'can override defaultChild'(test: Test) { const root = new Root(); new Construct(root, 'Resource'); const defaultChild = new Construct(root, 'OtherResource'); - root.construct.defaultChild = defaultChild; + root.node.defaultChild = defaultChild; - test.same(root.construct.defaultChild, defaultChild); + test.same(root.node.defaultChild, defaultChild); test.done(); }, 'returns "undefined" if there is no default'(test: Test) { @@ -489,7 +489,7 @@ export = { new Construct(root, 'child1'); new Construct(root, 'child2'); - test.equal(root.construct.defaultChild, undefined); + test.equal(root.node.defaultChild, undefined); test.done(); }, 'fails if there are both "Resource" and "Default"'(test: Test) { @@ -499,7 +499,7 @@ export = { new Construct(root, 'child2'); new Construct(root, 'Resource'); - test.throws(() => root.construct.defaultChild, + test.throws(() => root.node.defaultChild, /Cannot determine default child for . There is both a child with id "Resource" and id "Default"/); test.done(); @@ -511,7 +511,7 @@ function createTree(context?: any) { const root = new Root(); const highChild = new Construct(root, 'HighChild'); if (context) { - Object.keys(context).forEach(key => highChild.construct.setContext(key, context[key])); + Object.keys(context).forEach(key => highChild.node.setContext(key, context[key])); } const child1 = new Construct(highChild, 'Child1'); @@ -540,9 +540,9 @@ function toTreeString(node: IConstruct, depth = 0) { for (let i = 0; i < depth; ++i) { out += ' '; } - const name = node.construct.id || ''; + const name = node.node.id || ''; out += `${node.constructor.name}${name.length > 0 ? ' [' + name + ']' : ''}\n`; - for (const child of node.construct.children) { + for (const child of node.node.children) { out += toTreeString(child, depth + 1); } return out; diff --git a/packages/@aws-cdk/core/test/test.context.ts b/packages/@aws-cdk/core/test/test.context.ts index 4e4bffc743010..b19c2514b1786 100644 --- a/packages/@aws-cdk/core/test/test.context.ts +++ b/packages/@aws-cdk/core/test/test.context.ts @@ -18,7 +18,7 @@ export = { test.deepEqual(before, [ 'dummy1a', 'dummy1b', 'dummy1c' ]); const key = expectedContextKey(stack); - stack.construct.setContext(key, ['us-east-1a', 'us-east-1b']); + stack.node.setContext(key, ['us-east-1a', 'us-east-1b']); const azs = stack.availabilityZones; test.deepEqual(azs, ['us-east-1a', 'us-east-1b']); @@ -32,7 +32,7 @@ export = { test.deepEqual(before, [ 'dummy1a', 'dummy1b', 'dummy1c' ]); const key = expectedContextKey(stack); - stack.construct.setContext(key, 'not-a-list'); + stack.node.setContext(key, 'not-a-list'); test.throws( () => stack.availabilityZones, @@ -149,7 +149,7 @@ export = { // NOTE: error key is inlined here because it's part of the CX-API // compatibility surface. - stack.construct.setContext(contextKey, { $providerError: 'I had a boo-boo' }); + stack.node.setContext(contextKey, { $providerError: 'I had a boo-boo' }); const construct = new Construct(stack, 'Child'); // Verify that we got the right hardcoded key above, give a descriptive error if not @@ -162,7 +162,7 @@ export = { }); // THEN - const error = construct.construct.metadata.find(m => m.type === 'aws:cdk:error'); + const error = construct.node.metadata.find(m => m.type === 'aws:cdk:error'); test.equals(error && error.data, 'I had a boo-boo'); test.done(); diff --git a/packages/@aws-cdk/core/test/test.logical-id.ts b/packages/@aws-cdk/core/test/test.logical-id.ts index 3a804f26c19ed..332dad2f14eba 100644 --- a/packages/@aws-cdk/core/test/test.logical-id.ts +++ b/packages/@aws-cdk/core/test/test.logical-id.ts @@ -209,7 +209,7 @@ export = { const ref = c1.ref; const c2 = new CfnResource(stack, 'Construct2', { type: 'R2', properties: { ReferenceToR1: ref } }); - c2.construct.addDependency(c1); + c2.node.addDependency(c1); // THEN test.deepEqual(toCloudFormation(stack), { @@ -229,8 +229,8 @@ export = { 'customize logical id allocation behavior by overriding `Stack.allocateLogicalId`'(test: Test) { class MyStack extends Stack { protected allocateLogicalId(element: CfnElement): string { - if (element.construct.id === 'A') { return 'LogicalIdOfA'; } - if (element.construct.id === 'B') { return 'LogicalIdOfB'; } + if (element.node.id === 'A') { return 'LogicalIdOfA'; } + if (element.node.id === 'B') { return 'LogicalIdOfB'; } throw new Error('Invalid element ID'); } } diff --git a/packages/@aws-cdk/core/test/test.resource.ts b/packages/@aws-cdk/core/test/test.resource.ts index 6f2612f45b81d..bebaf2e8be1a8 100644 --- a/packages/@aws-cdk/core/test/test.resource.ts +++ b/packages/@aws-cdk/core/test/test.resource.ts @@ -129,8 +129,8 @@ export = { const r1 = new Counter(stack, 'Counter1', { Count: 1 }); const r2 = new Counter(stack, 'Counter2', { Count: 1 }); const r3 = new CfnResource(stack, 'Resource3', { type: 'MyResourceType' }); - r2.construct.addDependency(r1); - r2.construct.addDependency(r3); + r2.node.addDependency(r1); + r2.node.addDependency(r3); synthesize(stack); @@ -357,8 +357,8 @@ export = { const c3 = new C3(stack, 'MyC3'); const dependingResource = new CfnResource(stack, 'MyResource', { type: 'R' }); - dependingResource.construct.addDependency(c1, c2); - dependingResource.construct.addDependency(c3); + dependingResource.node.addDependency(c1, c2); + dependingResource.node.addDependency(c3); synthesize(stack); @@ -642,7 +642,7 @@ export = { '"aws:cdk:path" metadata is added if "aws:cdk:path-metadata" context is set to true'(test: Test) { const stack = new Stack(); - stack.construct.setContext(cxapi.PATH_METADATA_ENABLE_CONTEXT, true); + stack.node.setContext(cxapi.PATH_METADATA_ENABLE_CONTEXT, true); const parent = new Construct(stack, 'Parent'); @@ -667,7 +667,7 @@ export = { const resB = new CfnResource(stackB, 'Resource', { type: 'R' }); // WHEN - resB.construct.addDependency(resA); + resB.node.addDependency(resA); // THEN const assembly = app.synth(); @@ -681,7 +681,7 @@ export = { }, }, }); - test.deepEqual(stackB.dependencies.map(s => s.construct.id), ['StackA']); + test.deepEqual(stackB.dependencies.map(s => s.node.id), ['StackA']); test.done(); }, diff --git a/packages/@aws-cdk/core/test/test.stack.ts b/packages/@aws-cdk/core/test/test.stack.ts index b24717886a5b0..f92d2d9512b1d 100644 --- a/packages/@aws-cdk/core/test/test.stack.ts +++ b/packages/@aws-cdk/core/test/test.stack.ts @@ -129,9 +129,9 @@ export = { const o = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); const c = new CfnCondition(stack, 'MyCondition'); - test.equal(stack.construct.findChild(p.construct.id), p); - test.equal(stack.construct.findChild(o.construct.id), o); - test.equal(stack.construct.findChild(c.construct.id), c); + test.equal(stack.node.findChild(p.node.id), p); + test.equal(stack.node.findChild(o.node.id), o); + test.equal(stack.node.findChild(c.node.id), c); test.done(); }, @@ -583,7 +583,7 @@ export = { app.synth(); // THEN - test.deepEqual(stack2.dependencies.map(s => s.construct.id), ['Stack1']); + test.deepEqual(stack2.dependencies.map(s => s.node.id), ['Stack1']); test.done(); }, @@ -859,7 +859,7 @@ export = { const child = new Stack(parent, 'child'); // WHEN - child.construct.addMetadata('foo', 'bar'); + child.node.addMetadata('foo', 'bar'); // THEN const asm = app.synth(); diff --git a/packages/@aws-cdk/core/test/test.stage.ts b/packages/@aws-cdk/core/test/test.stage.ts index 42ddf9fb8972d..d93b8d2c08c38 100644 --- a/packages/@aws-cdk/core/test/test.stage.ts +++ b/packages/@aws-cdk/core/test/test.stage.ts @@ -152,7 +152,7 @@ export = { // THEN app.synth(); - test.deepEqual(aspect.visits.map(c => c.construct.path), [ + test.deepEqual(aspect.visits.map(c => c.node.path), [ 'MyStage/Stack', 'MyStage/Stack/Resource', ]); @@ -172,7 +172,7 @@ export = { // THEN app.synth(); - test.deepEqual(aspect.visits.map(c => c.construct.path), [ + test.deepEqual(aspect.visits.map(c => c.node.path), [ '', 'Tree', ]); @@ -301,4 +301,4 @@ class BogusStack extends Stack { function acctRegion(s: Stack) { return [s.account, s.region]; -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts index 434422fc949c8..5b944e8976d1b 100644 --- a/packages/@aws-cdk/core/test/test.staging.ts +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -47,7 +47,7 @@ export = { 'staging can be disabled through context'(test: Test) { // GIVEN const stack = new Stack(); - stack.construct.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); const sourcePath = path.join(__dirname, 'fs', 'fixtures', 'test1'); // WHEN diff --git a/packages/@aws-cdk/core/test/test.synthesis.ts b/packages/@aws-cdk/core/test/test.synthesis.ts index 7fde8510f398f..d83ba978bdaa3 100644 --- a/packages/@aws-cdk/core/test/test.synthesis.ts +++ b/packages/@aws-cdk/core/test/test.synthesis.ts @@ -153,7 +153,7 @@ export = { } const root = new SynthesizeMe(); - const assembly = cdk.ConstructNode.synth(root.construct, { outdir: fs.mkdtempSync(path.join(os.tmpdir(), 'outdir')) }); + const assembly = cdk.ConstructNode.synth(root.node, { outdir: fs.mkdtempSync(path.join(os.tmpdir(), 'outdir')) }); test.deepEqual(calls, [ 'prepare', 'validate', 'synthesize' ]); const stack = assembly.getStackByName('art'); diff --git a/packages/@aws-cdk/core/test/test.util.ts b/packages/@aws-cdk/core/test/test.util.ts index 986f10a5a1d1c..010f6ef33dc5b 100644 --- a/packages/@aws-cdk/core/test/test.util.ts +++ b/packages/@aws-cdk/core/test/test.util.ts @@ -96,7 +96,7 @@ export = testCase({ test.done(); function path(s: Stack) { - return pathToTopLevelStack(s).map(x => x.construct.id); + return pathToTopLevelStack(s).map(x => x.node.id); } }, @@ -132,7 +132,7 @@ export = testCase({ function lca(s1: Stack, s2: Stack) { const res = findLastCommonElement(pathToTopLevelStack(s1), pathToTopLevelStack(s2)); if (!res) { return undefined; } - return res.construct.id; + return res.node.id; } }, }); diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index 3c3d7d8c42568..01759f71b7e45 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -147,7 +147,7 @@ export class Provider extends Construct implements cfn.ICustomResourceProvider { private createFunction(entrypoint: string) { const fn = new lambda.Function(this, `framework-${entrypoint}`, { code: lambda.Code.fromAsset(RUNTIME_HANDLER_PATH), - description: `AWS CDK resource provider framework - ${entrypoint} (${this.construct.path})`.slice(0, 256), + description: `AWS CDK resource provider framework - ${entrypoint} (${this.node.path})`.slice(0, 256), runtime: lambda.Runtime.NODEJS_10_X, handler: `framework.${entrypoint}`, timeout: FRAMEWORK_HANDLER_TIMEOUT, diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts index 9672810518f71..f35bce528d366 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts @@ -87,7 +87,7 @@ export class WaiterStateMachine extends Construct { RoleArn: role.roleArn, }, }); - resource.construct.addDependency(role); + resource.node.addDependency(role); this.stateMachineArn = resource.ref; } diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts index ddce585cb4a1b..3b241031e04c1 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/integ.aws-custom-resource.ts @@ -33,7 +33,7 @@ const listTopics = new AwsCustomResource(stack, 'ListTopics', { }, policy: AwsCustomResourcePolicy.fromSdkCalls({resources: AwsCustomResourcePolicy.ANY_RESOURCE}), }); -listTopics.construct.addDependency(topic); +listTopics.node.addDependency(topic); const ssmParameter = new ssm.StringParameter(stack, 'DummyParameter', { stringValue: '1337', diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts index 6fe85db543230..01ccd37e89d89 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.ts @@ -30,7 +30,7 @@ class TestStack extends Stack { }); // delay file2 updates so we can test async assertions - file2.construct.addDependency(file1); + file2.node.addDependency(file1); new CfnOutput(this, 'file1-url', { value: file1.url }); new CfnOutput(this, 'file2-url', { value: file2.url }); diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts index 4b06558fc1f8b..0823d21e05e51 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert.ts @@ -54,7 +54,7 @@ class S3AssertProvider extends Construct { public static getOrCreate(scope: Construct) { const providerId = 'com.amazonaws.cdk.custom-resources.s3assert-provider'; const stack = Stack.of(scope); - const group = stack.construct.tryFindChild(providerId) as S3AssertProvider || new S3AssertProvider(stack, providerId); + const group = stack.node.tryFindChild(providerId) as S3AssertProvider || new S3AssertProvider(stack, providerId); return group.provider.serviceToken; } diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts index ca689a41df832..234ec567cbdbb 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-file.ts @@ -65,7 +65,7 @@ class S3FileProvider extends Construct { public static getOrCreate(scope: Construct) { const stack = Stack.of(scope); const id = 'com.amazonaws.cdk.custom-resources.s3file-provider'; - const x = stack.construct.tryFindChild(id) as S3FileProvider || new S3FileProvider(stack, id); + const x = stack.node.tryFindChild(id) as S3FileProvider || new S3FileProvider(stack, id); return x.provider.serviceToken; } diff --git a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts index c22e99b13bd08..c13dad054c790 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts @@ -270,10 +270,10 @@ function roleFromPlaceholderArn(scope: Construct, arn: string | undefined): iam. scope = hackyRoleScope(scope, arn); // https://github.com/aws/aws-cdk/issues/7255 - let existingRole = scope.construct.tryFindChild(`ImmutableRole${id}`) as iam.IRole; + let existingRole = scope.node.tryFindChild(`ImmutableRole${id}`) as iam.IRole; if (existingRole) { return existingRole; } // For when #7255 is fixed. - existingRole = scope.construct.tryFindChild(id) as iam.IRole; + existingRole = scope.node.tryFindChild(id) as iam.IRole; if (existingRole) { return existingRole; } return iam.Role.fromRoleArn(scope, id, cfnExpressionFromManifestString(arn), { mutable: false }); diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index e565c0271881e..f20021b34d1a8 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -65,7 +65,7 @@ export class CdkPipeline extends Construct { constructor(scope: Construct, id: string, props: CdkPipelineProps) { super(scope, id); - if (!App.isApp(this.construct.root)) { + if (!App.isApp(this.node.root)) { throw new Error('CdkPipeline must be created under an App'); } @@ -103,7 +103,7 @@ export class CdkPipeline extends Construct { projectName: maybeSuffix(props.pipelineName, '-publish'), }); - this.construct.applyAspect({ visit: () => this._assets.removeAssetsStageIfEmpty() }); + this.node.applyAspect({ visit: () => this._assets.removeAssetsStageIfEmpty() }); } /** @@ -195,7 +195,7 @@ export class CdkPipeline extends Construct { const depAction = stackActions.find(s => s.stackArtifactId === depId); if (depAction === undefined) { - this.construct.addWarning(`Stack '${stackAction.stackName}' depends on stack ` + + this.node.addWarning(`Stack '${stackAction.stackName}' depends on stack ` + `'${depId}', but that dependency is not deployed through the pipeline!`); } else if (!(depAction.executeRunOrder < stackAction.prepareRunOrder)) { yield `Stack '${stackAction.stackName}' depends on stack ` + diff --git a/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts b/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts index 83b40ae2fbdf2..7fc07f8d30fa5 100644 --- a/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts +++ b/packages/@aws-cdk/pipelines/lib/private/construct-internals.ts @@ -6,10 +6,10 @@ import { App, IConstruct, Stage } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; export function appOf(construct: IConstruct): App { - const root = construct.construct.root; + const root = construct.node.root; if (!App.isApp(root)) { - throw new Error(`Construct must be created under an App, but is not: ${construct.construct.path}`); + throw new Error(`Construct must be created under an App, but is not: ${construct.node.path}`); } return root; diff --git a/packages/@aws-cdk/pipelines/lib/stage.ts b/packages/@aws-cdk/pipelines/lib/stage.ts index 994c6f2097224..dd5aa28c5e50e 100644 --- a/packages/@aws-cdk/pipelines/lib/stage.ts +++ b/packages/@aws-cdk/pipelines/lib/stage.ts @@ -55,7 +55,7 @@ export class CdkStage extends Construct { this.cloudAssemblyArtifact = props.cloudAssemblyArtifact; this.host = props.host; - this.construct.applyAspect({ visit: () => this.prepareStage() }); + this.node.applyAspect({ visit: () => this.prepareStage() }); } /** diff --git a/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts b/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts index 3e9f2d7b09c29..71717c4dbb1a7 100644 --- a/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts +++ b/packages/@aws-cdk/pipelines/test/cross-environment-infra.test.ts @@ -26,7 +26,7 @@ test('in a cross-account/cross-region setup, artifact bucket can be read by depl // THEN app.synth(); - const supportStack = app.construct.findAll().filter(Stack.isStack).find(s => s.stackName === 'PipelineStack-support-us-elsewhere'); + const supportStack = app.node.findAll().filter(Stack.isStack).find(s => s.stackName === 'PipelineStack-support-us-elsewhere'); expect(supportStack).not.toBeUndefined(); expect(supportStack).toHaveResourceLike('AWS::S3::BucketPolicy', { diff --git a/packages/decdk/lib/declarative-stack.ts b/packages/decdk/lib/declarative-stack.ts index 47b929861c66d..8b937ecd3525e 100644 --- a/packages/decdk/lib/declarative-stack.ts +++ b/packages/decdk/lib/declarative-stack.ts @@ -383,9 +383,9 @@ function invokeMethod(stack: cdk.Stack, method: reflect.Callable, parameters: an */ function deconstructGetAtt(stack: cdk.Stack, id: string, attribute: string) { return cdk.Lazy.stringValue({ produce: () => { - const res = stack.construct.tryFindChild(id); + const res = stack.node.tryFindChild(id); if (!res) { - const include = stack.construct.tryFindChild('Include') as cdk.CfnInclude; + const include = stack.node.tryFindChild('Include') as cdk.CfnInclude; if (!include) { throw new Error(`Unexpected - "Include" should be in the stack at this point`); } @@ -403,7 +403,7 @@ function deconstructGetAtt(stack: cdk.Stack, id: string, attribute: string) { } function findConstruct(stack: cdk.Stack, id: string) { - const child = stack.construct.tryFindChild(id); + const child = stack.node.tryFindChild(id); if (!child) { throw new Error(`Construct with ID ${id} not found (it must be defined before it is referenced)`); } @@ -411,7 +411,7 @@ function findConstruct(stack: cdk.Stack, id: string) { } function processReferences(stack: cdk.Stack) { - const include = stack.construct.findChild('Include') as cdk.CfnInclude; + const include = stack.node.findChild('Include') as cdk.CfnInclude; if (!include) { throw new Error('Unexpected'); } From d04d538b154f5db711c89e3dce3801d880ba445c Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Wed, 12 Aug 2020 16:53:49 +0000 Subject: [PATCH 002/422] chore(release): 1.58.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e69649d4564b..9377e85b14174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,40 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.58.0](https://github.com/aws/aws-cdk/compare/v1.57.0...v1.58.0) (2020-08-12) + + +### Features + +* **cloudwatch:** alarm status widget ([#9456](https://github.com/aws/aws-cdk/issues/9456)) ([41940d3](https://github.com/aws/aws-cdk/commit/41940d3cfad289cbaed8ff60a21c6c9fa9aad532)) +* **cognito:** better control sms role creation ([#9513](https://github.com/aws/aws-cdk/issues/9513)) ([a772fe8](https://github.com/aws/aws-cdk/commit/a772fe84784e62843ef724a9158fc8cda848c5c9)), closes [#6943](https://github.com/aws/aws-cdk/issues/6943) +* **core:** deprecate "Construct.node" in favor of "Construct.construct" ([#9557](https://github.com/aws/aws-cdk/issues/9557)) ([aa4c5b7](https://github.com/aws/aws-cdk/commit/aa4c5b7df3a4880638361026ec0f6a77b7476b40)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#10](https://github.com/aws//github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md/issues/10) +* **core:** local bundling provider ([#9564](https://github.com/aws/aws-cdk/issues/9564)) ([3da0aa9](https://github.com/aws/aws-cdk/commit/3da0aa99d16e908a39f43f463ac2889dd232c611)) +* **core:** new annotations api ([#9563](https://github.com/aws/aws-cdk/issues/9563)) ([ae9ed62](https://github.com/aws/aws-cdk/commit/ae9ed6208dc81a7a38f4b9626c7c30f1811f97a9)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#09](https://github.com/aws//github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md/issues/09) +* **core:** new APIs for Aspects and Tags ([#9558](https://github.com/aws/aws-cdk/issues/9558)) ([a311428](https://github.com/aws/aws-cdk/commit/a311428d6013a1486585979a010f4105b0e0f97a)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#02](https://github.com/aws//github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md/issues/02) +* **ecs:** Option to encrypt lifecycle hook SNS Topic ([#9343](https://github.com/aws/aws-cdk/issues/9343)) ([38aad67](https://github.com/aws/aws-cdk/commit/38aad67c5d2db21cfb3660c1574f7fedde9860dc)) +* **events:** use existing Role when running ECS Task ([#8145](https://github.com/aws/aws-cdk/issues/8145)) ([aad951a](https://github.com/aws/aws-cdk/commit/aad951ae5355391463d9af2a49cd890f8d78f2d0)), closes [#7859](https://github.com/aws/aws-cdk/issues/7859) +* **global-accelerator:** referencing Global Accelerator security group ([#9358](https://github.com/aws/aws-cdk/issues/9358)) ([1fe9684](https://github.com/aws/aws-cdk/commit/1fe9684ea6b2dcaac1d97b64edfd4ef87cc65c0f)) +* **iam:** validate policies for missing resources/principals ([#9269](https://github.com/aws/aws-cdk/issues/9269)) ([60d01b1](https://github.com/aws/aws-cdk/commit/60d01b132b0e76224f7aae6b6caad5d13e7a816b)), closes [#7615](https://github.com/aws/aws-cdk/issues/7615) +* **lambda:** autoscaling for lambda aliases ([#8883](https://github.com/aws/aws-cdk/issues/8883)) ([d9d9b90](https://github.com/aws/aws-cdk/commit/d9d9b908ca149b189f0e1bde7df0d75afd5b26ff)) +* **readme:** include partitions.io cdk board in "getting help" ([#9541](https://github.com/aws/aws-cdk/issues/9541)) ([f098014](https://github.com/aws/aws-cdk/commit/f098014e0e9e49b2cc6a30922b8b0545e9c45e5e)) +* "stack relative exports" flag ([#9604](https://github.com/aws/aws-cdk/issues/9604)) ([398f872](https://github.com/aws/aws-cdk/commit/398f8720fac6ae7eb663a36c87c1f8f11aa89045)) +* **secretsmanager:** Specify secret value at creation ([#9594](https://github.com/aws/aws-cdk/issues/9594)) ([07fedff](https://github.com/aws/aws-cdk/commit/07fedffadf3900d754b5df5a24cc84622299ede4)), closes [#5810](https://github.com/aws/aws-cdk/issues/5810) + + +### Bug Fixes + +* **cfn-include:** allowedValues aren't included when specified by a parameter ([#9532](https://github.com/aws/aws-cdk/issues/9532)) ([e7dc82f](https://github.com/aws/aws-cdk/commit/e7dc82f04d83a7c85131e11e258f3ab031e61eda)) +* **codedeploy:** ServerDeploymentGroup takes AutoScalingGroup instead of IAutoScalingGroup ([#9252](https://github.com/aws/aws-cdk/issues/9252)) ([9ff55ae](https://github.com/aws/aws-cdk/commit/9ff55aeeed49d89bf13b2baf9025a1f4e038aa43)), closes [#9175](https://github.com/aws/aws-cdk/issues/9175) +* **docdb:** `autoMinorVersionUpgrade` property was not set to `true` by default as stated in the docstring ([#9505](https://github.com/aws/aws-cdk/issues/9505)) ([e878f9c](https://github.com/aws/aws-cdk/commit/e878f9c5fd503615a4d65a3f866e80cff001a309)) +* **ec2:** Volume grants have an overly complicated API ([#9115](https://github.com/aws/aws-cdk/issues/9115)) ([74e8391](https://github.com/aws/aws-cdk/commit/74e839189b2e9b028e6b9944884bf8fe73de2429)), closes [#9114](https://github.com/aws/aws-cdk/issues/9114) +* **efs:** LifecyclePolicy of AFTER_7_DAYS is not applied ([#9475](https://github.com/aws/aws-cdk/issues/9475)) ([f78c346](https://github.com/aws/aws-cdk/commit/f78c3469522006d38078db6effc4556d44da9747)), closes [#9474](https://github.com/aws/aws-cdk/issues/9474) +* **eks:** clusters in a FAILED state are not detected ([#9553](https://github.com/aws/aws-cdk/issues/9553)) ([d651948](https://github.com/aws/aws-cdk/commit/d651948b4b4ef43fedbaba69905e860fd595513d)) +* **eks:** private endpoint access doesn't work with `Vpc.fromLookup` ([#9544](https://github.com/aws/aws-cdk/issues/9544)) ([dd0f4cb](https://github.com/aws/aws-cdk/commit/dd0f4cb55bd9d7a95ccc9691ba33dab658d60e97)), closes [#9542](https://github.com/aws/aws-cdk/issues/9542) [#5383](https://github.com/aws/aws-cdk/issues/5383) +* **lambda:** cannot create lambda in public subnets ([#9468](https://github.com/aws/aws-cdk/issues/9468)) ([b46fdc9](https://github.com/aws/aws-cdk/commit/b46fdc92d3c3cee269bfa7785fa78679aa781880)) +* **pipelines:** CodeBuild images have (too) old Node version ([#9446](https://github.com/aws/aws-cdk/issues/9446)) ([bd45f34](https://github.com/aws/aws-cdk/commit/bd45f3419e24d6a9d9989a0efeacf2233866100b)), closes [#9070](https://github.com/aws/aws-cdk/issues/9070) +* **pipelines:** manual approval of changeset uses wrong ordering ([#9508](https://github.com/aws/aws-cdk/issues/9508)) ([5c01da8](https://github.com/aws/aws-cdk/commit/5c01da8d82f77e0241890101258aace2dac1902d)), closes [#9101](https://github.com/aws/aws-cdk/issues/9101) [#9101](https://github.com/aws/aws-cdk/issues/9101) + ## [1.57.0](https://github.com/aws/aws-cdk/compare/v1.56.0...v1.57.0) (2020-08-07) diff --git a/lerna.json b/lerna.json index 96e8ea36e52af..51753d20a7b8a 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.57.0" + "version": "1.58.0" } From 0d915311a4e082362bb2b5165d91664d8afacb3f Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Wed, 12 Aug 2020 10:09:08 -0700 Subject: [PATCH 003/422] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9377e85b14174..d445eaaa302df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ All notable changes to this project will be documented in this file. See [standa * **cognito:** better control sms role creation ([#9513](https://github.com/aws/aws-cdk/issues/9513)) ([a772fe8](https://github.com/aws/aws-cdk/commit/a772fe84784e62843ef724a9158fc8cda848c5c9)), closes [#6943](https://github.com/aws/aws-cdk/issues/6943) * **core:** deprecate "Construct.node" in favor of "Construct.construct" ([#9557](https://github.com/aws/aws-cdk/issues/9557)) ([aa4c5b7](https://github.com/aws/aws-cdk/commit/aa4c5b7df3a4880638361026ec0f6a77b7476b40)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#10](https://github.com/aws//github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md/issues/10) * **core:** local bundling provider ([#9564](https://github.com/aws/aws-cdk/issues/9564)) ([3da0aa9](https://github.com/aws/aws-cdk/commit/3da0aa99d16e908a39f43f463ac2889dd232c611)) -* **core:** new annotations api ([#9563](https://github.com/aws/aws-cdk/issues/9563)) ([ae9ed62](https://github.com/aws/aws-cdk/commit/ae9ed6208dc81a7a38f4b9626c7c30f1811f97a9)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#09](https://github.com/aws//github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md/issues/09) -* **core:** new APIs for Aspects and Tags ([#9558](https://github.com/aws/aws-cdk/issues/9558)) ([a311428](https://github.com/aws/aws-cdk/commit/a311428d6013a1486585979a010f4105b0e0f97a)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#02](https://github.com/aws//github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md/issues/02) +* **core:** new annotations api ([#9563](https://github.com/aws/aws-cdk/issues/9563)) ([ae9ed62](https://github.com/aws/aws-cdk/commit/ae9ed6208dc81a7a38f4b9626c7c30f1811f97a9)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#09](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#09-logging-logging-api-changes) +* **core:** new APIs for Aspects and Tags ([#9558](https://github.com/aws/aws-cdk/issues/9558)) ([a311428](https://github.com/aws/aws-cdk/commit/a311428d6013a1486585979a010f4105b0e0f97a)), closes [/github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#02](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0192-remove-constructs-compat.md#02-aspects-changes-in-aspects-api) * **ecs:** Option to encrypt lifecycle hook SNS Topic ([#9343](https://github.com/aws/aws-cdk/issues/9343)) ([38aad67](https://github.com/aws/aws-cdk/commit/38aad67c5d2db21cfb3660c1574f7fedde9860dc)) * **events:** use existing Role when running ECS Task ([#8145](https://github.com/aws/aws-cdk/issues/8145)) ([aad951a](https://github.com/aws/aws-cdk/commit/aad951ae5355391463d9af2a49cd890f8d78f2d0)), closes [#7859](https://github.com/aws/aws-cdk/issues/7859) * **global-accelerator:** referencing Global Accelerator security group ([#9358](https://github.com/aws/aws-cdk/issues/9358)) ([1fe9684](https://github.com/aws/aws-cdk/commit/1fe9684ea6b2dcaac1d97b64edfd4ef87cc65c0f)) From cc229c2c660e7e5be2255f031c001218c26b4752 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Wed, 12 Aug 2020 19:08:58 +0100 Subject: [PATCH 004/422] fix(cloudfront): Escape hatch support for Distribution (#9648) Changed the ID for the CfnDistribution to support escape hatches (`dist.node.defaultChild`). fixes #9620 BREAKING CHANGE: (cloudfront) Changed IDs for Distributions (will cause resource replacement). ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/integ.http-origin.expected.json | 2 +- .../integ.load-balancer-origin.expected.json | 4 ++-- .../test/integ.origin-group.expected.json | 4 ++-- .../test/integ.s3-origin.expected.json | 2 +- .../aws-cloudfront/lib/distribution.ts | 2 +- .../aws-cloudfront/test/distribution.test.ts | 18 ++++++++++++++++++ 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json index c5f7da26599c6..34b8f67a5b66f 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json @@ -1,6 +1,6 @@ { "Resources": { - "DistributionCFDistribution882A7313": { + "Distribution830FAC52": { "Type": "AWS::CloudFront::Distribution", "Properties": { "DistributionConfig": { diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json index 8c97abc155066..1b83e071d6275 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json @@ -400,7 +400,7 @@ } } }, - "DistributionCFDistribution882A7313": { + "Distribution830FAC52": { "Type": "AWS::CloudFront::Distribution", "Properties": { "DistributionConfig": { @@ -430,4 +430,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json index 6ed3596c52eba..21d939154cff2 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json @@ -64,7 +64,7 @@ } } }, - "DistributionCFDistribution882A7313": { + "Distribution830FAC52": { "Type": "AWS::CloudFront::Distribution", "Properties": { "DistributionConfig": { @@ -141,4 +141,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json index 173044c7a0dab..919d482d8b880 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json @@ -64,7 +64,7 @@ } } }, - "DistributionCFDistribution882A7313": { + "Distribution830FAC52": { "Type": "AWS::CloudFront::Distribution", "Properties": { "DistributionConfig": { diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 81cb0e7470ec4..48300b3faae83 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -158,7 +158,7 @@ export class Distribution extends Resource implements IDistribution { this.certificate = props.certificate; this.errorResponses = props.errorResponses ?? []; - const distribution = new CfnDistribution(this, 'CFDistribution', { distributionConfig: { + const distribution = new CfnDistribution(this, 'Resource', { distributionConfig: { enabled: true, origins: Lazy.anyValue({ produce: () => this.renderOrigins() }), originGroups: Lazy.anyValue({ produce: () => this.renderOriginGroups() }), diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 29942711ff781..60275f1cf07d0 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -382,6 +382,24 @@ test('price class is included if provided', () => { }); }); +test('escape hatches are supported', () => { + const dist = new Distribution(stack, 'Dist', { + defaultBehavior: { origin: defaultOrigin }, + }); + const cfnDist = dist.node.defaultChild as CfnDistribution; + cfnDist.addPropertyOverride('DistributionConfig.DefaultCacheBehavior.ForwardedValues.Headers', ['*']); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + ForwardedValues: { + Headers: ['*'], + }, + }, + }, + }); +}); + function defaultOrigin(domainName?: string): IOrigin { return new TestOrigin(domainName ?? 'www.example.com'); } From b1b4ceee16a2483604fa97741ed2f7ddf340d10a Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 12 Aug 2020 11:32:06 -0700 Subject: [PATCH 005/422] feat(codebuild): add support for GPU build images (#8879) CodeBuild has added support for running builds on machines with GPU drivers. This required some changes to the protocol between `IBuildImage` and `Project`, as those images are hosted in a public ECR repository that the image must grant the Project's Role access to. Introduced a `bind()` method to `IBuildImage` - to maintain backwards comaptibility, made it optional. Fixes #8408 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-codebuild/README.md | 34 +++ packages/@aws-cdk/aws-codebuild/lib/index.ts | 1 + .../lib/linux-gpu-build-image.ts | 141 +++++++++ .../private/run-script-linux-build-spec.ts | 32 ++ .../@aws-cdk/aws-codebuild/lib/project.ts | 53 ++-- packages/@aws-cdk/aws-codebuild/package.json | 2 + ...arning-container-build-image.expected.json | 279 ++++++++++++++++++ ...aws-deep-learning-container-build-image.ts | 21 ++ .../test/test.linux-gpu-build-image.ts | 65 ++++ packages/@aws-cdk/aws-ecr/lib/repository.ts | 3 +- .../region-info/build-tools/fact-tables.ts | 28 +- .../build-tools/generate-static-data.ts | 7 +- packages/@aws-cdk/region-info/lib/fact.ts | 6 + .../@aws-cdk/region-info/lib/region-info.ts | 27 ++ 14 files changed, 665 insertions(+), 34 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts create mode 100644 packages/@aws-cdk/aws-codebuild/lib/private/run-script-linux-build-spec.ts create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.ts create mode 100644 packages/@aws-cdk/aws-codebuild/test/test.linux-gpu-build-image.ts diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 73b4632722fc4..910a8413645d5 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -238,6 +238,40 @@ The following example shows how to define an image from a private docker registr [Docker Registry example](./test/integ.docker-registry.lit.ts) +### GPU images + +The class `LinuxGpuBuildImage` contains constants for working with +[AWS Deep Learning Container images](https://aws.amazon.com/releasenotes/available-deep-learning-containers-images): + + +```typescript +new codebuild.Project(this, 'Project', { + environment: { + buildImage: codebuild.LinuxGpuBuildImage.DLC_TENSORFLOW_2_1_0_INFERENCE, + }, + ... +}) +``` + +One complication is that the repositories for the DLC images are in +different accounts in different AWS regions. +In most cases, the CDK will handle providing the correct account for you; +in rare cases (for example, deploying to new regions) +where our information might be out of date, +you can always specify the account +(along with the repository name and tag) +explicitly using the `awsDeepLearningContainersImage` method: + +```typescript +new codebuild.Project(this, 'Project', { + environment: { + buildImage: codebuild.LinuxGpuBuildImage.awsDeepLearningContainersImage( + 'tensorflow-inference', '2.1.0-gpu-py36-cu101-ubuntu18.04', '123456789012'), + }, + ... +}) +``` + ## Credentials CodeBuild allows you to store credentials used when communicating with various sources, diff --git a/packages/@aws-cdk/aws-codebuild/lib/index.ts b/packages/@aws-cdk/aws-codebuild/lib/index.ts index 8ecdd6a743476..d1d0907ec734d 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/index.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/index.ts @@ -8,6 +8,7 @@ export * from './artifacts'; export * from './cache'; export * from './build-spec'; export * from './file-location'; +export * from './linux-gpu-build-image'; // AWS::CodeBuild CloudFormation Resources: export * from './codebuild.generated'; diff --git a/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts b/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts new file mode 100644 index 0000000000000..d9934e546f86d --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts @@ -0,0 +1,141 @@ +import * as ecr from '@aws-cdk/aws-ecr'; +import * as core from '@aws-cdk/core'; +import { FactName, RegionInfo } from '@aws-cdk/region-info'; +import { BuildSpec } from './build-spec'; +import { runScriptLinuxBuildSpec } from './private/run-script-linux-build-spec'; +import { + BuildEnvironment, BuildImageBindOptions, BuildImageConfig, ComputeType, IBindableBuildImage, IBuildImage, + ImagePullPrincipalType, IProject, +} from './project'; + +const mappingName = 'AwsDeepLearningContainersRepositoriesAccounts'; + +/** + * A CodeBuild GPU image running Linux. + * + * This class has public constants that represent the most popular GPU images from AWS Deep Learning Containers. + * + * @see https://aws.amazon.com/releasenotes/available-deep-learning-containers-images + */ +export class LinuxGpuBuildImage implements IBindableBuildImage { + /** Tensorflow 1.14.0 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_1_14_0 = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '1.14.0-gpu-py36-cu100-ubuntu16.04'); + /** Tensorflow 1.15.0 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_1_15_0 = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '1.15.0-gpu-py36-cu100-ubuntu18.04'); + /** Tensorflow 1.15.2 GPU training image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_1_15_2_TRAINING = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '1.15.2-gpu-py37-cu100-ubuntu18.04'); + /** Tensorflow 1.15.2 GPU inference image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_1_15_2_INFERENCE = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-inference', + '1.15.2-gpu-py36-cu100-ubuntu18.04'); + /** Tensorflow 2.0.0 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_2_0_0 = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '2.0.0-gpu-py36-cu100-ubuntu18.04'); + /** Tensorflow 2.0.1 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_2_0_1 = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '2.0.1-gpu-py36-cu100-ubuntu18.04'); + /** Tensorflow 2.1.0 GPU training image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_2_1_0_TRAINING = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '2.1.0-gpu-py36-cu101-ubuntu18.04'); + /** Tensorflow 2.1.0 GPU inference image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_2_1_0_INFERENCE = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-inference', + '2.1.0-gpu-py36-cu101-ubuntu18.04'); + /** Tensorflow 2.2.0 GPU training image from AWS Deep Learning Containers. */ + public static readonly DLC_TENSORFLOW_2_2_0_TRAINING = LinuxGpuBuildImage.awsDeepLearningContainersImage('tensorflow-training', + '2.2.0-gpu-py37-cu101-ubuntu18.04'); + + /** PyTorch 1.2.0 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_PYTORCH_1_2_0 = LinuxGpuBuildImage.awsDeepLearningContainersImage('pytorch-training', + '1.2.0-gpu-py36-cu100-ubuntu16.04'); + /** PyTorch 1.3.1 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_PYTORCH_1_3_1 = LinuxGpuBuildImage.awsDeepLearningContainersImage('pytorch-training', + '1.3.1-gpu-py36-cu101-ubuntu16.04'); + /** PyTorch 1.4.0 GPU training image from AWS Deep Learning Containers. */ + public static readonly DLC_PYTORCH_1_4_0_TRAINING = LinuxGpuBuildImage.awsDeepLearningContainersImage('pytorch-training', + '1.4.0-gpu-py36-cu101-ubuntu16.04'); + /** PyTorch 1.4.0 GPU inference image from AWS Deep Learning Containers. */ + public static readonly DLC_PYTORCH_1_4_0_INFERENCE = LinuxGpuBuildImage.awsDeepLearningContainersImage('pytorch-inference', + '1.4.0-gpu-py36-cu101-ubuntu16.04'); + /** PyTorch 1.5.0 GPU training image from AWS Deep Learning Containers. */ + public static readonly DLC_PYTORCH_1_5_0_TRAINING = LinuxGpuBuildImage.awsDeepLearningContainersImage('pytorch-training', + '1.5.0-gpu-py36-cu101-ubuntu16.04'); + /** PyTorch 1.5.0 GPU inference image from AWS Deep Learning Containers. */ + public static readonly DLC_PYTORCH_1_5_0_INFERENCE = LinuxGpuBuildImage.awsDeepLearningContainersImage('pytorch-inference', + '1.5.0-gpu-py36-cu101-ubuntu16.04'); + + /** MXNet 1.4.1 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_MXNET_1_4_1 = LinuxGpuBuildImage.awsDeepLearningContainersImage('mxnet-training', + '1.4.1-gpu-py36-cu100-ubuntu16.04'); + /** MXNet 1.6.0 GPU image from AWS Deep Learning Containers. */ + public static readonly DLC_MXNET_1_6_0 = LinuxGpuBuildImage.awsDeepLearningContainersImage('mxnet-training', + '1.6.0-gpu-py36-cu101-ubuntu16.04'); + + /** + * Returns a Linux GPU build image from AWS Deep Learning Containers. + * + * @param repositoryName the name of the repository, + * for example "pytorch-inference" + * @param tag the tag of the image, for example "1.5.0-gpu-py36-cu101-ubuntu16.04" + * @param account the AWS account ID where the DLC repository for this region is hosted in. + * In many cases, the CDK can infer that for you, but for some newer region our information + * might be out of date; in that case, you can specify the region explicitly using this optional parameter + * @see https://aws.amazon.com/releasenotes/available-deep-learning-containers-images + */ + public static awsDeepLearningContainersImage(repositoryName: string, tag: string, account?: string): IBuildImage { + return new LinuxGpuBuildImage(repositoryName, tag, account); + } + + public readonly type = 'LINUX_GPU_CONTAINER'; + public readonly defaultComputeType = ComputeType.LARGE; + public readonly imageId: string; + public readonly imagePullPrincipalType?: ImagePullPrincipalType = ImagePullPrincipalType.SERVICE_ROLE; + + private readonly accountExpression: string; + + private constructor(private readonly repositoryName: string, tag: string, private readonly account: string | undefined) { + this.accountExpression = account ?? core.Fn.findInMap(mappingName, core.Aws.REGION, 'account'); + this.imageId = `${this.accountExpression}.dkr.ecr.${core.Aws.REGION}.${core.Aws.URL_SUFFIX}/${repositoryName}:${tag}`; + } + + public bind(scope: core.Construct, project: IProject, _options: BuildImageBindOptions): BuildImageConfig { + if (!this.account) { + const scopeStack = core.Stack.of(scope); + // Unfortunately, the account IDs of the DLC repositories are not the same in all regions. + // Because of that, use a (singleton) Mapping to find the correct account + if (!scopeStack.node.tryFindChild(mappingName)) { + const mapping: { [k1: string]: { [k2: string]: any } } = {}; + // get the accounts from the region-info module + const region2Accounts = RegionInfo.regionMap(FactName.DLC_REPOSITORY_ACCOUNT); + for (const [region, account] of Object.entries(region2Accounts)) { + mapping[region] = { account }; + } + new core.CfnMapping(scopeStack, mappingName, { mapping }); + } + } + + const repository = ecr.Repository.fromRepositoryAttributes(scope, 'AwsDlcRepositoryCodeBuild', { + repositoryName: this.repositoryName, + repositoryArn: ecr.Repository.arnForLocalRepository(this.repositoryName, scope, this.accountExpression), + }); + repository.grantPull(project); + + return { + }; + } + + public validate(buildEnvironment: BuildEnvironment): string[] { + const ret = []; + if (buildEnvironment.computeType && + buildEnvironment.computeType !== ComputeType.LARGE) { + ret.push(`GPU images only support ComputeType '${ComputeType.LARGE}' - ` + + `'${buildEnvironment.computeType}' was given`); + } + return ret; + } + + public runScriptBuildspec(entrypoint: string): BuildSpec { + return runScriptLinuxBuildSpec(entrypoint); + } +} diff --git a/packages/@aws-cdk/aws-codebuild/lib/private/run-script-linux-build-spec.ts b/packages/@aws-cdk/aws-codebuild/lib/private/run-script-linux-build-spec.ts new file mode 100644 index 0000000000000..6fc63c55cf618 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/lib/private/run-script-linux-build-spec.ts @@ -0,0 +1,32 @@ +import { BuildSpec } from '../build-spec'; + +export const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; +export const S3_KEY_ENV = 'SCRIPT_S3_KEY'; + +export function runScriptLinuxBuildSpec(entrypoint: string) { + return BuildSpec.fromObject({ + version: '0.2', + phases: { + pre_build: { + commands: [ + // Better echo the location here; if this fails, the error message only contains + // the unexpanded variables by default. It might fail if you're running an old + // definition of the CodeBuild project--the permissions will have been changed + // to only allow downloading the very latest version. + `echo "Downloading scripts from s3://\${${S3_BUCKET_ENV}}/\${${S3_KEY_ENV}}"`, + `aws s3 cp s3://\${${S3_BUCKET_ENV}}/\${${S3_KEY_ENV}} /tmp`, + 'mkdir -p /tmp/scriptdir', + `unzip /tmp/$(basename \$${S3_KEY_ENV}) -d /tmp/scriptdir`, + ], + }, + build: { + commands: [ + 'export SCRIPT_DIR=/tmp/scriptdir', + `echo "Running ${entrypoint}"`, + `chmod +x /tmp/scriptdir/${entrypoint}`, + `/tmp/scriptdir/${entrypoint}`, + ], + }, + }, + }); +} diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index d4f349431b544..153108f7bd24e 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -16,13 +16,11 @@ import { CodePipelineArtifacts } from './codepipeline-artifacts'; import { IFileSystemLocation } from './file-location'; import { NoArtifacts } from './no-artifacts'; import { NoSource } from './no-source'; +import { runScriptLinuxBuildSpec, S3_BUCKET_ENV, S3_KEY_ENV } from './private/run-script-linux-build-spec'; import { renderReportGroupArn } from './report-group-utils'; import { ISource } from './source'; import { CODEPIPELINE_SOURCE_ARTIFACTS_TYPE, NO_SOURCE_TYPE } from './source-types'; -const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; -const S3_KEY_ENV = 'SCRIPT_S3_KEY'; - export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable { /** * The ARN of this Project. @@ -796,6 +794,12 @@ export class Project extends ProjectBase { if (props.encryptionKey) { this.encryptionKey = props.encryptionKey; } + + // bind + const bindFunction = (this.buildImage as any).bind; + if (bindFunction) { + bindFunction.call(this.buildImage, this, this, {}); + } } /** @@ -1196,6 +1200,21 @@ export interface IBuildImage { runScriptBuildspec(entrypoint: string): BuildSpec; } +/** Optional arguments to {@link IBuildImage.binder} - currently empty. */ +export interface BuildImageBindOptions {} + +/** The return type from {@link IBuildImage.binder} - currently empty. */ +export interface BuildImageConfig {} + +// @deprecated(not in tsdoc on purpose): add bind() to IBuildImage +// and get rid of IBindableBuildImage + +/** A variant of {@link IBuildImage} that allows binding to the project. */ +export interface IBindableBuildImage extends IBuildImage { + /** Function that allows the build image access to the construct tree. */ + bind(scope: Construct, project: IProject, options: BuildImageBindOptions): BuildImageConfig; +} + class ArmBuildImage implements IBuildImage { public readonly type = 'ARM_CONTAINER'; public readonly defaultComputeType = ComputeType.LARGE; @@ -1423,34 +1442,6 @@ export class LinuxBuildImage implements IBuildImage { } } -function runScriptLinuxBuildSpec(entrypoint: string) { - return BuildSpec.fromObject({ - version: '0.2', - phases: { - pre_build: { - commands: [ - // Better echo the location here; if this fails, the error message only contains - // the unexpanded variables by default. It might fail if you're running an old - // definition of the CodeBuild project--the permissions will have been changed - // to only allow downloading the very latest version. - `echo "Downloading scripts from s3://\${${S3_BUCKET_ENV}}/\${${S3_KEY_ENV}}"`, - `aws s3 cp s3://\${${S3_BUCKET_ENV}}/\${${S3_KEY_ENV}} /tmp`, - 'mkdir -p /tmp/scriptdir', - `unzip /tmp/$(basename \$${S3_KEY_ENV}) -d /tmp/scriptdir`, - ], - }, - build: { - commands: [ - 'export SCRIPT_DIR=/tmp/scriptdir', - `echo "Running ${entrypoint}"`, - `chmod +x /tmp/scriptdir/${entrypoint}`, - `/tmp/scriptdir/${entrypoint}`, - ], - }, - }, - }); -} - /** * Construction properties of {@link WindowsBuildImage}. * Module-private, as the constructor of {@link WindowsBuildImage} is private. diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index f9e29ceea015a..e218e0644d518 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -91,6 +91,7 @@ "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.2" }, "homepage": "https://github.com/aws/aws-cdk", @@ -108,6 +109,7 @@ "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.2" }, "engines": { diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json new file mode 100644 index 0000000000000..d5f641ecb1b9c --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json @@ -0,0 +1,279 @@ +{ + "Resources": { + "ProjectRole4CCB274E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ProjectRoleDefaultPolicy7F29461B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "ProjectC78D97AD" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "ProjectC78D97AD" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:CreateReportGroup", + "codebuild:CreateReport", + "codebuild:UpdateReport", + "codebuild:BatchPutTestCases" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "ProjectC78D97AD" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Fn::FindInMap": [ + "AwsDeepLearningContainersRepositoriesAccounts", + { + "Ref": "AWS::Region" + }, + "account" + ] + }, + ":repository/mxnet-training" + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ProjectRoleDefaultPolicy7F29461B", + "Roles": [ + { + "Ref": "ProjectRole4CCB274E" + } + ] + } + }, + "ProjectC78D97AD": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_LARGE", + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::FindInMap": [ + "AwsDeepLearningContainersRepositoriesAccounts", + { + "Ref": "AWS::Region" + }, + "account" + ] + }, + ".dkr.ecr.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/mxnet-training:1.4.1-gpu-py36-cu100-ubuntu16.04" + ] + ] + }, + "ImagePullCredentialsType": "SERVICE_ROLE", + "PrivilegedMode": false, + "Type": "LINUX_GPU_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "ProjectRole4CCB274E", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"ls\"\n ]\n }\n }\n}", + "Type": "NO_SOURCE" + } + } + } + }, + "Mappings": { + "AwsDeepLearningContainersRepositoriesAccounts": { + "us-east-1": { + "account": "763104351884" + }, + "us-east-2": { + "account": "763104351884" + }, + "us-west-1": { + "account": "763104351884" + }, + "us-west-2": { + "account": "763104351884" + }, + "ca-central-1": { + "account": "763104351884" + }, + "eu-west-1": { + "account": "763104351884" + }, + "eu-west-2": { + "account": "763104351884" + }, + "eu-west-3": { + "account": "763104351884" + }, + "eu-central-1": { + "account": "763104351884" + }, + "eu-north-1": { + "account": "763104351884" + }, + "sa-east-1": { + "account": "763104351884" + }, + "ap-south-1": { + "account": "763104351884" + }, + "ap-northeast-1": { + "account": "763104351884" + }, + "ap-northeast-2": { + "account": "763104351884" + }, + "ap-southeast-1": { + "account": "763104351884" + }, + "ap-southeast-2": { + "account": "763104351884" + }, + "ap-east-1": { + "account": "871362719292" + }, + "me-south-1": { + "account": "217643126080" + }, + "cn-north-1": { + "account": "727897471807" + }, + "cn-northwest-1": { + "account": "727897471807" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.ts b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.ts new file mode 100644 index 0000000000000..13d58db1198b5 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.ts @@ -0,0 +1,21 @@ +import * as core from '@aws-cdk/core'; +import * as codebuild from '../lib'; + +const app = new core.App(); +const stack = new core.Stack(app, 'aws-deep-learning-container-build-image'); + +new codebuild.Project(stack, 'Project', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { + commands: ['ls'], + }, + }, + }), + environment: { + buildImage: codebuild.LinuxGpuBuildImage.DLC_MXNET_1_4_1, + }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codebuild/test/test.linux-gpu-build-image.ts b/packages/@aws-cdk/aws-codebuild/test/test.linux-gpu-build-image.ts new file mode 100644 index 0000000000000..b3063b0cf0f01 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/test.linux-gpu-build-image.ts @@ -0,0 +1,65 @@ +import { arrayWith, expect, haveResourceLike, objectLike } from '@aws-cdk/assert'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as codebuild from '../lib'; + +export = { + 'Linux GPU build image': { + 'AWS Deep Learning Container images': { + 'allows passing the account that the repository of the image is hosted in'(test: Test) { + const stack = new cdk.Stack(); + + new codebuild.Project(stack, 'Project', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + build: { commands: ['ls'] }, + }, + }), + environment: { + buildImage: codebuild.LinuxGpuBuildImage.awsDeepLearningContainersImage( + 'my-repo', 'my-tag', '123456789012'), + }, + }); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Environment: { + ComputeType: 'BUILD_GENERAL1_LARGE', + Image: { + 'Fn::Join': ['', [ + '123456789012.dkr.ecr.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/my-repo:my-tag', + ]], + }, + }, + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith(objectLike({ + Action: [ + 'ecr:BatchCheckLayerAvailability', + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + ], + Resource: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':ecr:', + { Ref: 'AWS::Region' }, + ':123456789012:repository/my-repo', + ]], + }, + })), + }, + })); + + test.done(); + }, + }, + }, +}; diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 4d4c0c734443e..0f1aae84354b8 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -394,8 +394,9 @@ export class Repository extends RepositoryBase { * Returns an ECR ARN for a repository that resides in the same account/region * as the current stack. */ - public static arnForLocalRepository(repositoryName: string, scope: IConstruct): string { + public static arnForLocalRepository(repositoryName: string, scope: IConstruct, account?: string): string { return Stack.of(scope).formatArn({ + account, service: 'ecr', resource: 'repository', resourceName: repositoryName, diff --git a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts index b93ca024c9094..4e7ee6eee1261 100644 --- a/packages/@aws-cdk/region-info/build-tools/fact-tables.ts +++ b/packages/@aws-cdk/region-info/build-tools/fact-tables.ts @@ -103,4 +103,30 @@ export const ELBV2_ACCOUNTS: { [region: string]: string } = { 'us-gov-east-1': '190560391635', 'cn-north-1': '638102146993', 'cn-northwest-1': '037604701340', -}; \ No newline at end of file +}; + +// https://aws.amazon.com/releasenotes/available-deep-learning-containers-images +export const DLC_REPOSITORY_ACCOUNTS: { [region: string]: string } = { + 'us-east-1': '763104351884', + 'us-east-2': '763104351884', + 'us-west-1': '763104351884', + 'us-west-2': '763104351884', + 'ca-central-1': '763104351884', + 'eu-west-1': '763104351884', + 'eu-west-2': '763104351884', + 'eu-west-3': '763104351884', + 'eu-central-1': '763104351884', + 'eu-north-1': '763104351884', + 'sa-east-1': '763104351884', + 'ap-south-1': '763104351884', + 'ap-northeast-1': '763104351884', + 'ap-northeast-2': '763104351884', + 'ap-southeast-1': '763104351884', + 'ap-southeast-2': '763104351884', + + 'ap-east-1': '871362719292', + 'me-south-1': '217643126080', + + 'cn-north-1': '727897471807', + 'cn-northwest-1': '727897471807', +}; diff --git a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts index abcc335b308d0..bc042279d8e50 100644 --- a/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts +++ b/packages/@aws-cdk/region-info/build-tools/generate-static-data.ts @@ -2,7 +2,10 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { Default } from '../lib/default'; import { AWS_REGIONS, AWS_SERVICES } from './aws-entities'; -import { AWS_CDK_METADATA, AWS_OLDER_REGIONS, ELBV2_ACCOUNTS, PARTITION_MAP, ROUTE_53_BUCKET_WEBSITE_ZONE_IDS } from './fact-tables'; +import { + AWS_CDK_METADATA, AWS_OLDER_REGIONS, DLC_REPOSITORY_ACCOUNTS, ELBV2_ACCOUNTS, PARTITION_MAP, + ROUTE_53_BUCKET_WEBSITE_ZONE_IDS, +} from './fact-tables'; async function main(): Promise { const lines = [ @@ -49,6 +52,8 @@ async function main(): Promise { registerFact(region, 'ELBV2_ACCOUNT', ELBV2_ACCOUNTS[region]); + registerFact(region, 'DLC_REPOSITORY_ACCOUNT', DLC_REPOSITORY_ACCOUNTS[region]); + const vpcEndpointServiceNamePrefix = `${domainSuffix.split('.').reverse().join('.')}.vpce`; registerFact(region, 'VPC_ENDPOINT_SERVICE_NAME_PREFIX', vpcEndpointServiceNamePrefix); diff --git a/packages/@aws-cdk/region-info/lib/fact.ts b/packages/@aws-cdk/region-info/lib/fact.ts index 3936f9c129449..79033f9c9adb9 100644 --- a/packages/@aws-cdk/region-info/lib/fact.ts +++ b/packages/@aws-cdk/region-info/lib/fact.ts @@ -140,6 +140,12 @@ export class FactName { */ public static readonly ELBV2_ACCOUNT = 'elbv2Account'; + /** + * The ID of the AWS account that owns the public ECR repository that contains the + * AWS Deep Learning Containers images in a given region. + */ + public static readonly DLC_REPOSITORY_ACCOUNT = 'dlcRepositoryAccount'; + /** * The name of the regional service principal for a given service. * diff --git a/packages/@aws-cdk/region-info/lib/region-info.ts b/packages/@aws-cdk/region-info/lib/region-info.ts index cc424bf304bf1..3402cb8fd657f 100644 --- a/packages/@aws-cdk/region-info/lib/region-info.ts +++ b/packages/@aws-cdk/region-info/lib/region-info.ts @@ -12,6 +12,25 @@ export class RegionInfo { return Fact.regions.map(RegionInfo.get); } + /** + * Retrieves a collection of all fact values for all regions that fact is defined in. + * + * @param factName the name of the fact to retrieve values for. + * For a list of common fact names, see the FactName class + * @returns a mapping with AWS region codes as the keys, + * and the fact in the given region as the value for that key + */ + public static regionMap(factName: string): { [region: string]: string } { + const ret: { [region: string]: string } = {}; + for (const regionInfo of RegionInfo.regions) { + const fact = Fact.find(regionInfo.name, factName); + if (fact) { + ret[regionInfo.name] = fact; + } + } + return ret; + } + /** * Obtain region info for a given region name. * @@ -82,4 +101,12 @@ export class RegionInfo { public get elbv2Account(): string | undefined { return Fact.find(this.name, FactName.ELBV2_ACCOUNT); } + + /** + * The ID of the AWS account that owns the public ECR repository containing the + * AWS Deep Learning Containers images in this region. + */ + public get dlcRepositoryAccount(): string | undefined { + return Fact.find(this.name, FactName.DLC_REPOSITORY_ACCOUNT); + } } From 16bb36a85f43e4fc930202df65d7dd04f7186f46 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2020 18:57:21 +0000 Subject: [PATCH 006/422] chore(deps-dev): bump @types/uuid from 8.0.0 to 8.3.0 (#9649) Bumps [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid) from 8.0.0 to 8.3.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/aws-cdk/package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index f1bc8e6cda1d8..ea51c1177340a 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -52,7 +52,7 @@ "@types/semver": "^7.3.1", "@types/sinon": "^9.0.4", "@types/table": "^4.0.7", - "@types/uuid": "^8.0.0", + "@types/uuid": "^8.3.0", "@types/wrap-ansi": "^3.0.0", "@types/yaml": "^1.9.7", "@types/yargs": "^15.0.5", diff --git a/yarn.lock b/yarn.lock index ed5cf3f1e9b63..2b366de4263f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1618,10 +1618,10 @@ resolved "https://registry.yarnpkg.com/@types/table/-/table-4.0.7.tgz#c21100d37d4924abbbde85414170260d4d7b0316" integrity sha512-HKtXvBxU8U8evZCSlUi9HbfT/SFW7nSGCoiBEheB06jAhXeW6JbGh8biEAqIFG5rZo9f8xeJVdIn455sddmIcw== -"@types/uuid@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" - integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== "@types/wrap-ansi@^3.0.0": version "3.0.0" @@ -9077,9 +9077,9 @@ tap-mocha-reporter@^3.0.9, tap-mocha-reporter@^5.0.1: unicode-length "^2.0.2" tap-parser@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/tap-parser/-/tap-parser-10.0.1.tgz#b63c2500eeef2be8fbf09d512914196d1f12ebec" - integrity sha512-qdT15H0DoJIi7zOqVXDn9X0gSM68JjNy1w3VemwTJlDnETjbi6SutnqmBfjDJAwkFS79NJ97gZKqie00ZCGmzg== + version "10.1.0" + resolved "https://registry.yarnpkg.com/tap-parser/-/tap-parser-10.1.0.tgz#7b1aac40dbcaa4716c0b58952686eae65d2b74ad" + integrity sha512-FujQeciDaOiOvaIVGS1Rpb0v4R6XkOjvWCWowlz5oKuhPkEJ8U6pxgqt38xuzYhPt8dWEnfHn2jqpZdJEkW7pA== dependencies: events-to-array "^1.0.1" minipass "^3.0.0" From 4c62702fe886a41e00a0be1fdf12bdb75a9ac968 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Wed, 12 Aug 2020 20:19:35 +0100 Subject: [PATCH 007/422] feat(cloudfront): Distribution support for logging, geo restrictions, http version and IPv6 (#9635) Adding support for most of the missing attributes for `DistributionProps`, including HttpVersion, Logging, Geo restrictions, and WAF ACLs. The only missing property now is "aliases", which needs some design work alongside the certificate usage. I also added two integ tests for `Distribution`, as previously all integ tests were only in the `aws-cloudfront-origins` module. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/integ.http-origin.expected.json | 2 + .../integ.load-balancer-origin.expected.json | 2 + .../test/integ.origin-group.expected.json | 2 + .../test/integ.s3-origin.expected.json | 2 + packages/@aws-cdk/aws-cloudfront/README.md | 22 +++ .../aws-cloudfront/lib/distribution.ts | 134 ++++++++++++++- .../aws-cloudfront/lib/geo-restriction.ts | 52 ++++++ packages/@aws-cdk/aws-cloudfront/lib/index.ts | 3 +- .../aws-cloudfront/lib/web_distribution.ts | 61 +------ packages/@aws-cdk/aws-cloudfront/package.json | 3 - .../aws-cloudfront/test/distribution.test.ts | 162 ++++++++++++++++-- .../test/geo-restriction.test.ts | 27 +++ .../integ.distribution-basic.expected.json | 30 ++++ .../test/integ.distribution-basic.ts | 12 ++ ...integ.distribution-extensive.expected.json | 57 ++++++ .../test/integ.distribution-extensive.ts | 22 +++ .../aws-cloudfront/test/origin.test.ts | 10 +- .../aws-cloudfront/test/test-origin.ts | 13 ++ 18 files changed, 530 insertions(+), 86 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudfront/lib/geo-restriction.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/geo-restriction.test.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/test-origin.ts diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json index 34b8f67a5b66f..a4f80dc55a8e0 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.http-origin.expected.json @@ -12,6 +12,8 @@ "ViewerProtocolPolicy": "allow-all" }, "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, "Origins": [ { "CustomOriginConfig": { diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json index 1b83e071d6275..d01791d8e6cb4 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.load-balancer-origin.expected.json @@ -412,6 +412,8 @@ "ViewerProtocolPolicy": "allow-all" }, "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, "Origins": [ { "CustomOriginConfig": { diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json index 21d939154cff2..b3d92abe8d269 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json @@ -76,6 +76,8 @@ "ViewerProtocolPolicy": "allow-all" }, "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, "OriginGroups": { "Items": [ { diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json index 919d482d8b880..0a1d437718590 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.s3-origin.expected.json @@ -76,6 +76,8 @@ "ViewerProtocolPolicy": "allow-all" }, "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, "Origins": [ { "DomainName": { diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index f71cf9f971391..c13af4ace9e05 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -221,6 +221,28 @@ myDistribution.addBehavior('images/*', myOrigin, { }); ``` +### Logging + +You can configure CloudFront to create log files that contain detailed information about every user request that CloudFront receives. +The logs can go to either an existing bucket, or a bucket will be created for you. + +```ts +// Simplest form - creates a new bucket and logs to it. +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, + enableLogging: true, +}); + +// You can optionally log to a specific bucket, configure whether cookies are logged, and give the log files a prefix. +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') }, + enableLogging: true, // Optional, this is implied if loggingBucket is specified + loggingBucket: new s3.Bucket(this, 'LoggingBucket'), + loggingFilePrefix: 'distribution-access-logs/', + loggingIncludesCookies: true, +}); +``` + ## CloudFrontWebDistribution API - Stable ![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 48300b3faae83..dee47eaa122df 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -1,7 +1,9 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; +import * as s3 from '@aws-cdk/aws-s3'; import { Construct, IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core'; import { CfnDistribution } from './cloudfront.generated'; +import { GeoRestriction } from './geo-restriction'; import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin'; import { CacheBehavior } from './private/cache-behavior'; @@ -82,6 +84,83 @@ export interface DistributionProps { */ readonly certificate?: acm.ICertificate; + /** + * Any comments you want to include about the distribution. + * + * @default - no comment + */ + readonly comment?: string; + + /** + * The object that you want CloudFront to request from your origin (for example, index.html) + * when a viewer requests the root URL for your distribution. If no default object is set, the + * request goes to the origin's root (e.g., example.com/). + * + * @default - no default root object + */ + readonly defaultRootObject?: string; + + /** + * Enable or disable the distribution. + * + * @default true + */ + readonly enabled?: boolean; + + /** + * Whether CloudFront will respond to IPv6 DNS requests with an IPv6 address. + * + * If you specify false, CloudFront responds to IPv6 DNS requests with the DNS response code NOERROR and with no IP addresses. + * This allows viewers to submit a second request, for an IPv4 address for your distribution. + * + * @default true + */ + readonly enableIpv6?: boolean; + + /** + * Enable access logging for the distribution. + * + * @default - false, unless `loggingBucket` is specified. + */ + readonly enableLogging?: boolean; + + /** + * Controls the countries in which your content is distributed. + * + * @default - No geographic restrictions + */ + readonly geoRestriction?: GeoRestriction; + + /** + * Specify the maximum HTTP version that you want viewers to use to communicate with CloudFront. + * + * For viewers and CloudFront to use HTTP/2, viewers must support TLS 1.2 or later, and must support server name identification (SNI). + * + * @default HttpVersion.HTTP2 + */ + readonly httpVersion?: HttpVersion; + + /** + * The Amazon S3 bucket to store the access logs in. + * + * @default - A bucket is created if `enableLogging` is true + */ + readonly logBucket?: s3.IBucket; + + /** + * Specifies whether you want CloudFront to include cookies in access logs + * + * @default false + */ + readonly logIncludesCookies?: boolean; + + /** + * An optional string that you want CloudFront to prefix to the access log filenames for this distribution. + * + * @default - no prefix + */ + readonly logFilePrefix?: string; + /** * The price class that corresponds with the maximum price that you want to pay for CloudFront service. * If you specify PriceClass_All, CloudFront responds to requests for your objects from all CloudFront edge locations. @@ -92,6 +171,20 @@ export interface DistributionProps { */ readonly priceClass?: PriceClass; + /** + * Unique identifier that specifies the AWS WAF web ACL to associate with this CloudFront distribution. + * + * To specify a web ACL created using the latest version of AWS WAF, use the ACL ARN, for example + * `arn:aws:wafv2:us-east-1:123456789012:global/webacl/ExampleWebACL/473e64fd-f30b-4765-81a0-62ad96dd167a`. + * To specify a web ACL created using AWS WAF Classic, use the ACL ID, for example `473e64fd-f30b-4765-81a0-62ad96dd167a`. + * + * @see https://docs.aws.amazon.com/waf/latest/developerguide/what-is-aws-waf.html + * @see https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateDistribution.html#API_CreateDistribution_RequestParameters. + * + * @default - No AWS Web Application Firewall web access control list (web ACL). + */ + readonly webAclId?: string; + /** * How CloudFront should handle requests that are not successful (e.g., PageNotFound). * @@ -159,14 +252,20 @@ export class Distribution extends Resource implements IDistribution { this.errorResponses = props.errorResponses ?? []; const distribution = new CfnDistribution(this, 'Resource', { distributionConfig: { - enabled: true, + enabled: props.enabled ?? true, origins: Lazy.anyValue({ produce: () => this.renderOrigins() }), originGroups: Lazy.anyValue({ produce: () => this.renderOriginGroups() }), defaultCacheBehavior: this.defaultBehavior._renderBehavior(), cacheBehaviors: Lazy.anyValue({ produce: () => this.renderCacheBehaviors() }), - viewerCertificate: this.certificate ? this.renderViewerCertificate(this.certificate) : undefined, + comment: props.comment, customErrorResponses: this.renderErrorResponses(), + defaultRootObject: props.defaultRootObject, + httpVersion: props.httpVersion ?? HttpVersion.HTTP2, + ipv6Enabled: props.enableIpv6 ?? true, + logging: this.renderLogging(props), priceClass: props.priceClass ?? undefined, + restrictions: this.renderRestrictions(props.geoRestriction), + viewerCertificate: this.certificate ? this.renderViewerCertificate(this.certificate) : undefined, } }); this.domainName = distribution.attrDomainName; @@ -280,6 +379,29 @@ export class Distribution extends Resource implements IDistribution { }); } + private renderLogging(props: DistributionProps): CfnDistribution.LoggingProperty | undefined { + if (!props.enableLogging && !props.logBucket) { return undefined; } + if (props.enableLogging === false && props.logBucket) { + throw new Error('Explicitly disabled logging but provided a logging bucket.'); + } + + const bucket = props.logBucket ?? new s3.Bucket(this, 'LoggingBucket'); + return { + bucket: bucket.bucketRegionalDomainName, + includeCookies: props.logIncludesCookies, + prefix: props.logFilePrefix, + }; + } + + private renderRestrictions(geoRestriction?: GeoRestriction) { + return geoRestriction ? { + geoRestriction: { + restrictionType: geoRestriction.restrictionType, + locations: geoRestriction.locations, + }, + } : undefined; + } + private renderViewerCertificate(certificate: acm.ICertificate): CfnDistribution.ViewerCertificateProperty { return { acmCertificateArn: certificate.certificateArn, @@ -289,6 +411,14 @@ export class Distribution extends Resource implements IDistribution { } } +/** Maximum HTTP version to support */ +export enum HttpVersion { + /** HTTP 1.1 */ + HTTP1_1 = 'http1.1', + /** HTTP 2 */ + HTTP2 = 'http2' +} + /** * The price class determines how many edge locations CloudFront will use for your distribution. * See https://aws.amazon.com/cloudfront/pricing/ for full list of supported regions. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/geo-restriction.ts b/packages/@aws-cdk/aws-cloudfront/lib/geo-restriction.ts new file mode 100644 index 0000000000000..7c18b2f7ebc68 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/geo-restriction.ts @@ -0,0 +1,52 @@ +/** + * Controls the countries in which content is distributed. + */ +export class GeoRestriction { + + /** + * Whitelist specific countries which you want CloudFront to distribute your content. + * + * @param locations Two-letter, uppercase country code for a country + * that you want to whitelist. Include one element for each country. + * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website + */ + public static whitelist(...locations: string[]) { + return new GeoRestriction('whitelist', GeoRestriction.validateLocations(locations)); + } + + /** + * Blacklist specific countries which you don't want CloudFront to distribute your content. + * + * @param locations Two-letter, uppercase country code for a country + * that you want to blacklist. Include one element for each country. + * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website + */ + public static blacklist(...locations: string[]) { + return new GeoRestriction('blacklist', GeoRestriction.validateLocations(locations)); + } + + private static LOCATION_REGEX = /^[A-Z]{2}$/; + + private static validateLocations(locations: string[]) { + if (locations.length === 0) { + throw new Error('Should provide at least 1 location'); + } + locations.forEach(location => { + if (!GeoRestriction.LOCATION_REGEX.test(location)) { + // eslint-disable-next-line max-len + throw new Error(`Invalid location format for location: ${location}, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code`); + } + }); + return locations; + } + + /** + * Creates an instance of GeoRestriction for internal use + * + * @param restrictionType Specifies the restriction type to impose (whitelist or blacklist) + * @param locations Two-letter, uppercase country code for a country + * that you want to whitelist/blacklist. Include one element for each country. + * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website + */ + private constructor(readonly restrictionType: 'whitelist' | 'blacklist', readonly locations: string[]) {} +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/index.ts b/packages/@aws-cdk/aws-cloudfront/lib/index.ts index bf106211657c9..cf78bf5cdd7e0 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/index.ts @@ -1,7 +1,8 @@ export * from './distribution'; -export * from './web_distribution'; +export * from './geo-restriction'; export * from './origin'; export * from './origin_access_identity'; +export * from './web_distribution'; // AWS::CloudFront CloudFormation Resources: export * from './cloudfront.generated'; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index b2fc472adfd46..c2d8343cdbe10 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -4,14 +4,10 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { CfnDistribution } from './cloudfront.generated'; -import { IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution'; +import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution'; +import { GeoRestriction } from './geo-restriction'; import { IOriginAccessIdentity } from './origin_access_identity'; -export enum HttpVersion { - HTTP1_1 = 'http1.1', - HTTP2 = 'http2' -} - /** * HTTP status code to failover to second origin */ @@ -515,59 +511,6 @@ export class ViewerCertificate { public readonly aliases: string[] = []) { } } -/** - * Controls the countries in which your content is distributed. - */ -export class GeoRestriction { - - /** - * Whitelist specific countries which you want CloudFront to distribute your content. - * - * @param locations Two-letter, uppercase country code for a country - * that you want to whitelist. Include one element for each country. - * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website - */ - public static whitelist(...locations: string[]) { - return new GeoRestriction('whitelist', GeoRestriction.validateLocations(locations)); - } - - /** - * Blacklist specific countries which you don't want CloudFront to distribute your content. - * - * @param locations Two-letter, uppercase country code for a country - * that you want to blacklist. Include one element for each country. - * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website - */ - public static blacklist(...locations: string[]) { - return new GeoRestriction('blacklist', GeoRestriction.validateLocations(locations)); - } - - private static LOCATION_REGEX = /^[A-Z]{2}$/; - - private static validateLocations(locations: string[]) { - if (locations.length === 0) { - throw new Error('Should provide at least 1 location'); - } - locations.forEach(location => { - if (!GeoRestriction.LOCATION_REGEX.test(location)) { - // eslint-disable-next-line max-len - throw new Error(`Invalid location format for location: ${location}, location should be two-letter and uppercase country ISO 3166-1-alpha-2 code`); - } - }); - return locations; - } - - /** - * Creates an instance of GeoRestriction for internal use - * - * @param restrictionType Specifies the restriction type to impose (whitelist or blacklist) - * @param locations Two-letter, uppercase country code for a country - * that you want to whitelist/blacklist. Include one element for each country. - * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website - */ - private constructor(readonly restrictionType: 'whitelist' | 'blacklist', readonly locations: string[]) {} -} - export interface CloudFrontWebDistributionProps { /** diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 45b22ae133343..ad24a82a2d4b3 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -126,9 +126,6 @@ "docs-public-apis:@aws-cdk/aws-cloudfront.CloudFrontAllowedMethods.GET_HEAD", "docs-public-apis:@aws-cdk/aws-cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS", "docs-public-apis:@aws-cdk/aws-cloudfront.CloudFrontAllowedMethods.ALL", - "docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion", - "docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion.HTTP1_1", - "docs-public-apis:@aws-cdk/aws-cloudfront.HttpVersion.HTTP2", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.SSL_V3", "docs-public-apis:@aws-cdk/aws-cloudfront.OriginSslPolicy.TLS_V1", diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 60275f1cf07d0..34e0177777ed7 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -1,8 +1,11 @@ +import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as acm from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; +import * as s3 from '@aws-cdk/aws-s3'; import { App, Duration, Stack } from '@aws-cdk/core'; -import { CfnDistribution, Distribution, IOrigin, LambdaEdgeEventType, OriginBase, OriginProps, OriginProtocolPolicy, PriceClass } from '../lib'; +import { CfnDistribution, Distribution, GeoRestriction, HttpVersion, IOrigin, LambdaEdgeEventType, PriceClass } from '../lib'; +import { defaultOrigin } from './test-origin'; let app: App; let stack: Stack; @@ -26,6 +29,8 @@ test('minimal example renders correctly', () => { ViewerProtocolPolicy: 'allow-all', }, Enabled: true, + HttpVersion: 'http2', + IPV6Enabled: true, Origins: [{ DomainName: 'www.example.com', Id: 'StackMyDistOrigin1D6D5E535', @@ -37,6 +42,66 @@ test('minimal example renders correctly', () => { }); }); +test('exhaustive example of props renders correctly', () => { + const origin = defaultOrigin(); + const certificate = acm.Certificate.fromCertificateArn(stack, 'Cert', 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012'); + + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + certificate, + comment: 'a test', + defaultRootObject: 'index.html', + enabled: false, + enableIpv6: false, + enableLogging: true, + geoRestriction: GeoRestriction.blacklist('US', 'GB'), + httpVersion: HttpVersion.HTTP1_1, + logFilePrefix: 'logs/', + logIncludesCookies: true, + priceClass: PriceClass.PRICE_CLASS_100, + webAclId: '473e64fd-f30b-4765-81a0-62ad96dd167a', + }); + + expect(stack).toHaveResource('AWS::CloudFront::Distribution', { + DistributionConfig: { + DefaultCacheBehavior: { + ForwardedValues: { QueryString: false }, + TargetOriginId: 'StackMyDistOrigin1D6D5E535', + ViewerProtocolPolicy: 'allow-all', + }, + Comment: 'a test', + DefaultRootObject: 'index.html', + Enabled: false, + HttpVersion: 'http1.1', + IPV6Enabled: false, + Logging: { + Bucket: {'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName']}, + IncludeCookies: true, + Prefix: 'logs/', + }, + Origins: [{ + DomainName: 'www.example.com', + Id: 'StackMyDistOrigin1D6D5E535', + CustomOriginConfig: { + OriginProtocolPolicy: 'https-only', + }, + }], + PriceClass: 'PriceClass_100', + Restrictions: { + GeoRestriction: { + Locations: ['US', 'GB'], + RestrictionType: 'blacklist', + }, + }, + ViewerCertificate: { + AcmCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012', + SslSupportMethod: 'sni-only', + MinimumProtocolVersion: 'TLSv1.2_2018', + }, + }, + }); +}); + describe('multiple behaviors', () => { test('a second behavior can\'t be specified with the catch-all path pattern', () => { @@ -75,6 +140,8 @@ describe('multiple behaviors', () => { ViewerProtocolPolicy: 'allow-all', }], Enabled: true, + HttpVersion: 'http2', + IPV6Enabled: true, Origins: [{ DomainName: 'www.example.com', Id: 'StackMyDistOrigin1D6D5E535', @@ -110,6 +177,8 @@ describe('multiple behaviors', () => { ViewerProtocolPolicy: 'allow-all', }], Enabled: true, + HttpVersion: 'http2', + IPV6Enabled: true, Origins: [{ DomainName: 'www.example.com', Id: 'StackMyDistOrigin1D6D5E535', @@ -159,6 +228,8 @@ describe('multiple behaviors', () => { ViewerProtocolPolicy: 'allow-all', }], Enabled: true, + HttpVersion: 'http2', + IPV6Enabled: true, Origins: [{ DomainName: 'www.example.com', Id: 'StackMyDistOrigin1D6D5E535', @@ -273,6 +344,84 @@ describe('custom error responses', () => { }); +describe('logging', () => { + test('does not include logging if disabled and no bucket provided', () => { + const origin = defaultOrigin(); + new Distribution(stack, 'MyDist', { defaultBehavior: { origin } }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + Logging: ABSENT, + }, + }); + }); + + test('throws error if logging disabled but bucket provided', () => { + const origin = defaultOrigin(); + + expect(() => { + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + enableLogging: false, + logBucket: new s3.Bucket(stack, 'Bucket'), + }); + }).toThrow(/Explicitly disabled logging but provided a logging bucket./); + }); + + test('creates bucket if none is provided', () => { + const origin = defaultOrigin(); + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + enableLogging: true, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + Logging: { + Bucket: {'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName']}, + }, + }, + }); + }); + + test('uses existing bucket if provided', () => { + const origin = defaultOrigin(); + const loggingBucket = new s3.Bucket(stack, 'MyLoggingBucket'); + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + logBucket: loggingBucket, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + Logging: { + Bucket: {'Fn::GetAtt': ['MyLoggingBucket4382CD04', 'RegionalDomainName']}, + }, + }, + }); + }); + + test('can set prefix and cookies', () => { + const origin = defaultOrigin(); + new Distribution(stack, 'MyDist', { + defaultBehavior: { origin }, + enableLogging: true, + logFilePrefix: 'logs/', + logIncludesCookies: true, + }); + + expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { + DistributionConfig: { + Logging: { + Bucket: {'Fn::GetAtt': ['MyDistLoggingBucket9B8976BC', 'RegionalDomainName']}, + IncludeCookies: true, + Prefix: 'logs/', + }, + }, + }); + }); +}); + describe('with Lambda@Edge functions', () => { let lambdaFunction: lambda.Function; let origin: IOrigin; @@ -399,14 +548,3 @@ test('escape hatches are supported', () => { }, }); }); - -function defaultOrigin(domainName?: string): IOrigin { - return new TestOrigin(domainName ?? 'www.example.com'); -} - -class TestOrigin extends OriginBase { - constructor(domainName: string, props: OriginProps = {}) { super(domainName, props); } - protected renderCustomOriginConfig(): CfnDistribution.CustomOriginConfigProperty | undefined { - return { originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY }; - } -} diff --git a/packages/@aws-cdk/aws-cloudfront/test/geo-restriction.test.ts b/packages/@aws-cdk/aws-cloudfront/test/geo-restriction.test.ts new file mode 100644 index 0000000000000..23af143c67851 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/geo-restriction.test.ts @@ -0,0 +1,27 @@ +import '@aws-cdk/assert/jest'; +import { GeoRestriction } from '../lib'; + +describe.each([ + ['whitelist', GeoRestriction.whitelist], + ['blacklist', GeoRestriction.blacklist], +])('%s', (type, geoFn) => { + + test('throws is location is empty', () => { + expect(() => { geoFn(); }).toThrow(/Should provide at least 1 location/); + }); + + test('throws if locations are the wrong format', () => { + const error = /Invalid location format for location: .*/; + expect(() => { geoFn('a'); }).toThrow(error); + expect(() => { geoFn('abc'); }).toThrow(error); + expect(() => { geoFn('ab'); }).toThrow(error); + expect(() => { geoFn('a1'); }).toThrow(error); + }); + + test('includes proper restriction type and location list', () => { + const restriction = geoFn('US', 'GB'); + expect(restriction.restrictionType).toEqual(type); + expect(restriction.locations).toEqual(['US', 'GB']); + }); + +}); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json new file mode 100644 index 0000000000000..26a9b80a80b67 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.expected.json @@ -0,0 +1,30 @@ +{ + "Resources": { + "DistB3B78991": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "ForwardedValues": { + "QueryString": false + }, + "TargetOriginId": "integdistributionbasicDistOrigin151B53FF1", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "www.example.com", + "Id": "integdistributionbasicDistOrigin151B53FF1" + } + ] + } + } + } + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.ts new file mode 100644 index 0000000000000..61fd0f0872077 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-basic.ts @@ -0,0 +1,12 @@ +import * as cdk from '@aws-cdk/core'; +import * as cloudfront from '../lib'; +import { TestOrigin } from './test-origin'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-distribution-basic'); + +new cloudfront.Distribution(stack, 'Dist', { + defaultBehavior: { origin: new TestOrigin('www.example.com') }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json new file mode 100644 index 0000000000000..587b65b5f98f9 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.expected.json @@ -0,0 +1,57 @@ +{ + "Resources": { + "MyDistLoggingBucket9B8976BC": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyDistDB88FD9A": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "Comment": "a test", + "DefaultCacheBehavior": { + "ForwardedValues": { + "QueryString": false + }, + "TargetOriginId": "integdistributionextensiveMyDistOrigin185F089B3", + "ViewerProtocolPolicy": "allow-all" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "MyDistLoggingBucket9B8976BC", + "RegionalDomainName" + ] + }, + "IncludeCookies": true, + "Prefix": "logs/" + }, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "www.example.com", + "Id": "integdistributionextensiveMyDistOrigin185F089B3" + } + ], + "PriceClass": "PriceClass_100", + "Restrictions": { + "GeoRestriction": { + "Locations": [ + "US", + "GB" + ], + "RestrictionType": "whitelist" + } + } + } + } + } + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.ts new file mode 100644 index 0000000000000..6a8f949ca9b43 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-extensive.ts @@ -0,0 +1,22 @@ +import * as cdk from '@aws-cdk/core'; +import * as cloudfront from '../lib'; +import { TestOrigin } from './test-origin'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-distribution-extensive'); + +new cloudfront.Distribution(stack, 'MyDist', { + defaultBehavior: { origin: new TestOrigin('www.example.com') }, + comment: 'a test', + defaultRootObject: 'index.html', + enabled: true, + enableIpv6: true, + enableLogging: true, + geoRestriction: cloudfront.GeoRestriction.whitelist('US', 'GB'), + httpVersion: cloudfront.HttpVersion.HTTP2, + logFilePrefix: 'logs/', + logIncludesCookies: true, + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts b/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts index a9d553dbe01c0..cbf3b32ad6f10 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/origin.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; import { App, Stack, Duration } from '@aws-cdk/core'; -import { CfnDistribution, OriginProtocolPolicy, OriginBase, OriginProps } from '../lib'; +import { TestOrigin } from './test-origin'; let app: App; let stack: Stack; @@ -44,11 +44,3 @@ test.each(['api', '/api', '/api/', 'api/']) expect(originBindConfig.originProperty?.originPath).toEqual('/api'); }); - -/** Used for testing common Origin functionality */ -class TestOrigin extends OriginBase { - constructor(domainName: string, props: OriginProps = {}) { super(domainName, props); } - protected renderCustomOriginConfig(): CfnDistribution.CustomOriginConfigProperty | undefined { - return { originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY }; - } -} diff --git a/packages/@aws-cdk/aws-cloudfront/test/test-origin.ts b/packages/@aws-cdk/aws-cloudfront/test/test-origin.ts new file mode 100644 index 0000000000000..c39c632389e1b --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/test-origin.ts @@ -0,0 +1,13 @@ +import { CfnDistribution, IOrigin, OriginBase, OriginProps, OriginProtocolPolicy } from '../lib'; + +/** Used for testing common Origin functionality */ +export class TestOrigin extends OriginBase { + constructor(domainName: string, props: OriginProps = {}) { super(domainName, props); } + protected renderCustomOriginConfig(): CfnDistribution.CustomOriginConfigProperty | undefined { + return { originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY }; + } +} + +export function defaultOrigin(domainName?: string): IOrigin { + return new TestOrigin(domainName ?? 'www.example.com'); +} From 0ea4ea3734c4fb136515d85f9b0a791649aa51f2 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Wed, 12 Aug 2020 16:49:59 -0700 Subject: [PATCH 008/422] chore: secret scan in CodeBuild test (#9657) As it turns out, our secret scan was flagging the 'account' part of the mapping generated for the AWS Deep Learning Containers image repositories. Turn them into 'repositoryAccount' instead to silence the scanner. Also, actually enable the scanner to run correctly for PR builds as well. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- git-secrets-scan.sh | 6 +- .../lib/linux-gpu-build-image.ts | 4 +- ...arning-container-build-image.expected.json | 82 +++++++++---------- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/git-secrets-scan.sh b/git-secrets-scan.sh index ed11ef5f4d890..95c2425ae5ec5 100755 --- a/git-secrets-scan.sh +++ b/git-secrets-scan.sh @@ -21,10 +21,10 @@ mkdir -p .tools git rev-parse --git-dir > /dev/null 2>&1 || { git init --quiet git add -A . - - # AWS config needs to be added to this fresh repository's config - .tools/git-secrets/git-secrets --register-aws } +# AWS config needs to be added to this repository's config +.tools/git-secrets/git-secrets --register-aws + .tools/git-secrets/git-secrets --scan echo "git-secrets scan ok" diff --git a/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts b/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts index d9934e546f86d..b3f4c1d6dad61 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/linux-gpu-build-image.ts @@ -95,7 +95,7 @@ export class LinuxGpuBuildImage implements IBindableBuildImage { private readonly accountExpression: string; private constructor(private readonly repositoryName: string, tag: string, private readonly account: string | undefined) { - this.accountExpression = account ?? core.Fn.findInMap(mappingName, core.Aws.REGION, 'account'); + this.accountExpression = account ?? core.Fn.findInMap(mappingName, core.Aws.REGION, 'repositoryAccount'); this.imageId = `${this.accountExpression}.dkr.ecr.${core.Aws.REGION}.${core.Aws.URL_SUFFIX}/${repositoryName}:${tag}`; } @@ -109,7 +109,7 @@ export class LinuxGpuBuildImage implements IBindableBuildImage { // get the accounts from the region-info module const region2Accounts = RegionInfo.regionMap(FactName.DLC_REPOSITORY_ACCOUNT); for (const [region, account] of Object.entries(region2Accounts)) { - mapping[region] = { account }; + mapping[region] = { repositoryAccount: account }; } new core.CfnMapping(scopeStack, mappingName, { mapping }); } diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json index d5f641ecb1b9c..2c91da398b0a7 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.aws-deep-learning-container-build-image.expected.json @@ -138,7 +138,7 @@ { "Ref": "AWS::Region" }, - "account" + "repositoryAccount" ] }, ":repository/mxnet-training" @@ -180,7 +180,7 @@ { "Ref": "AWS::Region" }, - "account" + "repositoryAccount" ] }, ".dkr.ecr.", @@ -214,65 +214,65 @@ }, "Mappings": { "AwsDeepLearningContainersRepositoriesAccounts": { - "us-east-1": { - "account": "763104351884" + "ap-east-1": { + "repositoryAccount": "871362719292" }, - "us-east-2": { - "account": "763104351884" + "ap-northeast-1": { + "repositoryAccount": "763104351884" }, - "us-west-1": { - "account": "763104351884" + "ap-northeast-2": { + "repositoryAccount": "763104351884" }, - "us-west-2": { - "account": "763104351884" + "ap-south-1": { + "repositoryAccount": "763104351884" }, - "ca-central-1": { - "account": "763104351884" + "ap-southeast-1": { + "repositoryAccount": "763104351884" }, - "eu-west-1": { - "account": "763104351884" + "ap-southeast-2": { + "repositoryAccount": "763104351884" }, - "eu-west-2": { - "account": "763104351884" + "ca-central-1": { + "repositoryAccount": "763104351884" }, - "eu-west-3": { - "account": "763104351884" + "cn-north-1": { + "repositoryAccount": "727897471807" + }, + "cn-northwest-1": { + "repositoryAccount": "727897471807" }, "eu-central-1": { - "account": "763104351884" + "repositoryAccount": "763104351884" }, "eu-north-1": { - "account": "763104351884" - }, - "sa-east-1": { - "account": "763104351884" + "repositoryAccount": "763104351884" }, - "ap-south-1": { - "account": "763104351884" + "eu-west-1": { + "repositoryAccount": "763104351884" }, - "ap-northeast-1": { - "account": "763104351884" + "eu-west-2": { + "repositoryAccount": "763104351884" }, - "ap-northeast-2": { - "account": "763104351884" + "eu-west-3": { + "repositoryAccount": "763104351884" }, - "ap-southeast-1": { - "account": "763104351884" + "me-south-1": { + "repositoryAccount": "217643126080" }, - "ap-southeast-2": { - "account": "763104351884" + "sa-east-1": { + "repositoryAccount": "763104351884" }, - "ap-east-1": { - "account": "871362719292" + "us-east-1": { + "repositoryAccount": "763104351884" }, - "me-south-1": { - "account": "217643126080" + "us-east-2": { + "repositoryAccount": "763104351884" }, - "cn-north-1": { - "account": "727897471807" + "us-west-1": { + "repositoryAccount": "763104351884" }, - "cn-northwest-1": { - "account": "727897471807" + "us-west-2": { + "repositoryAccount": "763104351884" } } } From a48e95a2fb8cbe1b4dd7f0b4e573126fd5a2b01d Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 13 Aug 2020 12:05:18 +0300 Subject: [PATCH 009/422] chore: small fixes to DESIGN_GUIDELINES.md (#9534) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- DESIGN_GUIDELINES.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/DESIGN_GUIDELINES.md b/DESIGN_GUIDELINES.md index 822965e702bf4..807528f8f6d42 100644 --- a/DESIGN_GUIDELINES.md +++ b/DESIGN_GUIDELINES.md @@ -9,9 +9,10 @@ the AWS Construct Library in order to ensure a consistent and integrated experience across the entire AWS surface area. As much as possible, the guidelines in this document are enforced using the -**awslint** tool which reflects on the APIs and verifies that the APIs adhere to -the guidelines. When a guideline is backed by a linter rule, the rule name will -be referenced like this: _[awslint:resource-class-is-construct]_. +[**awslint** tool](https://www.npmjs.com/package/awslint) which reflects on the +APIs and verifies that the APIs adhere to the guidelines. When a guideline is +backed by a linter rule, the rule name will be referenced like this: +_[awslint:resource-class-is-construct]_. For the purpose of this document we will use "Foo" to denote the official name of the resource as defined in the AWS CloudFormation resource specification @@ -147,9 +148,9 @@ behavior through interfaces and not through inheritance. Construct classes should extend only one of the following classes [_awslint:construct-inheritence_]: -* The **Resource** class (if it represents an AWS resource) The **Construct** -* class (if it represents an abstract component) The **XxxBase** class (which, -* in turn extends **Resource**) +* The **Resource** class (if it represents an AWS resource) +* The **Construct** class (if it represents an abstract component) +* The **XxxBase** class (which, in turn extends **Resource**) All constructs must define a static type check method called **isFoo** with the following implementation [_awslint:static-type-check_]: From 014c13a78261b400404819549f6ff25d27b0c51d Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 13 Aug 2020 10:36:41 +0100 Subject: [PATCH 010/422] feat(ec2): CloudFormation-init support (#9065) > NOTE: This is a reduced version of #8788, which is the full CloudFormation-init support. This has been reduced down to only support instances (not ASGs), and to only support the InitCommand and InitService init elements, rather than the full set. This is to reduce the PR size and encourage a more thorough review. A follow-up review will add the remainder of the elements and auto-scaling group support. Add CloudFormation-init support. The CloudFormation-init metadata is encapsulated in a CloudFormationInit object, and using it automatically renders the UserData to apply it and send a signal to the appropriate CloudFormation resource and adds the permissions required to use cfn-init, cfn-signal and any S3 files/assets to the instance role. On an Instance, using CloudFormation-init automatically adds a ResourceSignal with a default timeout to the instance. Note this currently also includes the same changes as #9063, as this relies on it. #9063 can be independently shipped. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/README.md | 100 ++++- .../@aws-cdk/aws-ec2/lib/cfn-init-elements.ts | 343 ++++++++++++++++++ packages/@aws-cdk/aws-ec2/lib/cfn-init.ts | 296 +++++++++++++++ packages/@aws-cdk/aws-ec2/lib/index.ts | 2 + packages/@aws-cdk/aws-ec2/lib/instance.ts | 172 ++++++++- .../aws-ec2/lib/private/cfn-init-internal.ts | 140 +++++++ packages/@aws-cdk/aws-ec2/package.json | 5 +- .../aws-ec2/test/cfn-init-element.test.ts | 176 +++++++++ .../@aws-cdk/aws-ec2/test/cfn-init.test.ts | 237 ++++++++++++ .../@aws-cdk/aws-ec2/test/instance.test.ts | 67 +++- .../test/integ.instance-init.expected.json | 181 +++++++++ .../aws-ec2/test/integ.instance-init.ts | 34 ++ 12 files changed, 1726 insertions(+), 27 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/lib/cfn-init-elements.ts create mode 100644 packages/@aws-cdk/aws-ec2/lib/cfn-init.ts create mode 100644 packages/@aws-cdk/aws-ec2/lib/private/cfn-init-internal.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.instance-init.ts diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 668943296ac4d..e7b8c6a7a227d 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -1,4 +1,5 @@ ## Amazon EC2 Construct Library + --- @@ -48,10 +49,9 @@ distinguishes three different subnet types: connected to from other instances in the same VPC. A default VPC configuration will not include isolated subnets, - A default VPC configuration will create public and **private** subnets. However, if -`natGateways:0` **and** `subnetConfiguration` is undefined, default VPC configuration -will create public and **isolated** subnets. See [*Advanced Subnet Configuration*](#advanced-subnet-configuration) +`natGateways:0` **and** `subnetConfiguration` is undefined, default VPC configuration +will create public and **isolated** subnets. See [*Advanced Subnet Configuration*](#advanced-subnet-configuration) below for information on how to change the default subnet configuration. Constructs using the VPC will "launch instances" (or more accurately, create @@ -68,7 +68,6 @@ created by setting the `natGateways` property to a lower value (the default is one NAT gateway per availability zone). Be aware that this may have availability implications for your application. - [Read more about subnets](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html). @@ -280,9 +279,9 @@ const igwId = vpc.internetGatewayId; For a VPC with only `ISOLATED` subnets, this value will be undefined. -This is only supported for VPC's created in the stack - currently you're +This is only supported for VPC's created in the stack - currently you're unable to get the ID for imported VPC's. To do that you'd have to specifically -look up the Internet Gateway by name, which would require knowing the name +look up the Internet Gateway by name, which would require knowing the name beforehand. This can be useful for configuring routing using a combination of gateways: @@ -501,7 +500,9 @@ examples of things you might want to use: > [Runtime Context](https://docs.aws.amazon.com/cdk/latest/guide/context.html) in the CDK > developer guide. -## VPN connections to a VPC +## Special VPC configurations + +### VPN connections to a VPC Create your VPC with VPN connections by specifying the `vpnConnections` props (keys are construct `id`s): @@ -531,6 +532,7 @@ const vpc = new ec2.Vpc(this, 'MyVpc', { ``` VPN connections can then be added: + ```ts fixture=with-vpc vpc.addVpnConnection('Dynamic', { ip: '1.2.3.4' @@ -554,14 +556,15 @@ const vpnConnection = vpc.addVpnConnection('Dynamic', { const state = vpnConnection.metricTunnelState(); ``` -## VPC endpoints +### VPC endpoints + A VPC endpoint enables you to privately connect your VPC to supported AWS services and VPC endpoint services powered by PrivateLink without requiring an internet gateway, NAT device, VPN connection, or AWS Direct Connect connection. Instances in your VPC do not require public IP addresses to communicate with resources in the service. Traffic between your VPC and the other service does not leave the Amazon network. Endpoints are virtual devices. They are horizontally scaled, redundant, and highly available VPC components that allow communication between instances in your VPC and services without imposing availability risks or bandwidth constraints on your network traffic. [example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts) -By default, CDK will place a VPC endpoint in one subnet per AZ. If you wish to override the AZs CDK places the VPC endpoint in, +By default, CDK will place a VPC endpoint in one subnet per AZ. If you wish to override the AZs CDK places the VPC endpoint in, use the `subnets` parameter as follows: ```ts @@ -591,7 +594,8 @@ new InterfaceVpcEndpoint(stack, 'VPC Endpoint', { }); ``` -### Security groups for interface VPC endpoints +#### Security groups for interface VPC endpoints + By default, interface VPC endpoints create a new security group and traffic is **not** automatically allowed from the VPC CIDR. @@ -603,7 +607,8 @@ myEndpoint.connections.allowDefaultPortFromAnyIpv4(); Alternatively, existing security groups can be used by specifying the `securityGroups` prop. -## VPC endpoint services +### VPC endpoint services + A VPC endpoint service enables you to expose a Network Load Balancer(s) as a provider service to consumers, who connect to your service over a VPC endpoint. You can restrict access to your service via whitelisted principals (anything that extends ArnPrincipal), and require that new connections be manually accepted. ```ts @@ -614,17 +619,69 @@ new VpcEndpointService(this, 'EndpointService', { }); ``` -## Bastion Hosts +## Instances + +You can use the `Instance` class to start up a single EC2 instance. For production setups, we recommend +you use an `AutoScalingGroup` from the `aws-autoscaling` module instead, as AutoScalingGroups will take +care of restarting your instance if it ever fails. + +### Configuring Instances using CloudFormation Init (cfn-init) + +CloudFormation Init allows you to configure your instances by writing files to them, installing software +packages, starting services and running arbitrary commands. By default, if any of the instance setup +commands throw an error, the deployment will fail and roll back to the previously known good state. +The following documentation also applies to `AutoScalingGroup`s. + +For the full set of capabilities of this system, see the documentation for +[`AWS::CloudFormation::Init`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html). +Here is an example of applying some configuration to an instance: + +```ts +new ec2.Instance(this, 'Instance', { + init: ec2.CloudFormationInit.fromElements( + ec2.InitCommand.shellCommand('/bin/true'), + ), + initOptions: { + // Optional, which configsets to activate (['default'] by default) + configSets: ['default'], + + // Optional, how long the installation is expected to take (5 minutes by default) + timeout: Duration.minutes(30), + }, +}); +``` + +You can have services restarted after the init process has made changes to the system. +To do that, instantiate an `InitServiceRestartHandle` and pass it to the config elements +that need to trigger the restart and the service itself. For example, the following +config installs nginx through a custom script, and then +restarts nginx so that it picks up the new config and files: + +```ts +const handle = new ec2.InitServiceRestartHandle(); + +ec2.CloudFormationInit.fromElements( + ec2.InitCommand.shellCommand('/usr/bin/custom-nginx-install.sh', { serviceRestartHandles: [handle] }), + ec2.InitService.enable('nginx', { + serviceRestartHandle: handle, + }) +); +``` + +### Bastion Hosts + A bastion host functions as an instance used to access servers and resources in a VPC without open up the complete VPC on a network level. You can use bastion hosts using a standard SSH connection targetting port 22 on the host. As an alternative, you can connect the SSH connection feature of AWS Systems Manager Session Manager, which does not need an opened security group. (https://aws.amazon.com/about-aws/whats-new/2019/07/session-manager-launches-tunneling-support-for-ssh-and-scp/) A default bastion host for use via SSM can be configured like: + ```ts fixture=with-vpc const host = new ec2.BastionHostLinux(this, 'BastionHost', { vpc }); ``` If you want to connect from the internet using SSH, you need to place the host into a public subnet. You can then configure allowed source hosts. + ```ts fixture=with-vpc const host = new ec2.BastionHostLinux(this, 'BastionHost', { vpc, @@ -637,6 +694,7 @@ As there are no SSH public keys deployed on this machine, you need to use [EC2 I with the command `aws ec2-instance-connect send-ssh-public-key` to provide your SSH public key. EBS volume for the bastion host can be encrypted like: + ```ts const host = new ec2.BastionHostLinux(stack, 'BastionHost', { vpc, @@ -649,7 +707,7 @@ EBS volume for the bastion host can be encrypted like: }); ``` -## Block Devices +### Block Devices To add EBS block device mappings, specify the `blockDeviceMappings` property. The follow example sets the EBS-backed root device (`/dev/sda1`) size to 50 GiB, and adds another EBS-backed device mapped to `/dev/sdm` that is 100 GiB in @@ -672,7 +730,7 @@ new ec2.Instance(this, 'Instance', { ``` -## Volumes +### Volumes Whereas a `BlockDeviceVolume` is an EBS volume that is created and destroyed as part of the creation and destruction of a specific instance. A `Volume` is for when you want an EBS volume separate from any particular instance. A `Volume` is an EBS block device that can be attached to, or detached from, any instance at any time. Some types of `Volume`s can also be attached to multiple instances at the same time to allow you to have shared storage between those instances. @@ -696,7 +754,7 @@ const volume = new ec2.Volume(this, 'Volume', { volume.grantAttachVolume(role, [instance]); ``` -### Instances Attaching Volumes to Themselves +#### Instances Attaching Volumes to Themselves If you need to grant an instance the ability to attach/detach an EBS volume to/from itself, then using `grantAttachVolume` and `grantDetachVolume` as outlined above will lead to an unresolvable circular reference between the instance role and the instance. In this case, use `grantAttachVolumeByResourceTag` and `grantDetachVolumeByResourceTag` as follows: @@ -713,7 +771,7 @@ const attachGrant = volume.grantAttachVolumeByResourceTag(instance.grantPrincipa const detachGrant = volume.grantDetachVolumeByResourceTag(instance.grantPrincipal, [instance]); ``` -### Attaching Volumes +#### Attaching Volumes The Amazon EC2 documentation for [Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html) and @@ -741,7 +799,8 @@ instance.userData.addCommands( ``` ## VPC Flow Logs -VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html). + +VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (). By default a flow log will be created with CloudWatch Logs as the destination. @@ -752,6 +811,7 @@ new ec2.FlowLog(this, 'FlowLog', { resourceType: ec2.FlowLogResourceType.fromVpc(vpc) }) ``` + Or you can add a Flow Log to a VPC by using the addFlowLog method like this: ```ts @@ -781,6 +841,7 @@ the log group. In the case of an S3 destination, it will create the S3 bucket. If you want to customize any of the destination resources you can provide your own as part of the `destination`. *CloudWatch Logs* + ```ts const logGroup = new logs.LogGroup(this, 'MyCustomLogGroup'); @@ -795,6 +856,7 @@ new ec2.FlowLog(this, 'FlowLog', { ``` *S3* + ```ts const bucket = new s3.Bucket(this, 'MyCustomBucket'); @@ -806,10 +868,12 @@ new ec2.FlowLog(this, 'FlowLog', { ``` ## User Data + User data enables you to run a script when your instances start up. In order to configure these scripts you can add commands directly to the script or you can use the UserData's convenience functions to aid in the creation of your script. A user data could be configured to run a script found in an asset through the following: + ```ts const asset = new Asset(this, 'Asset', {path: path.join(__dirname, 'configure.sh')}); const instance = new ec2.Instance(this, 'Instance', { @@ -846,4 +910,4 @@ const subnet = Subnet.fromSubnetAttributes(this, 'SubnetFromAttributes', { // Supply only subnet id const subnet = Subnet.fromSubnetId(this, 'SubnetFromId', 's-1234'); -``` \ No newline at end of file +``` diff --git a/packages/@aws-cdk/aws-ec2/lib/cfn-init-elements.ts b/packages/@aws-cdk/aws-ec2/lib/cfn-init-elements.ts new file mode 100644 index 0000000000000..13ed6e61d1cb5 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/cfn-init-elements.ts @@ -0,0 +1,343 @@ +import { Duration } from '@aws-cdk/core'; +import { InitBindOptions, InitElementConfig, InitElementType, InitPlatform } from './private/cfn-init-internal'; + +/** + * An object that represents reasons to restart an InitService + * + * Pass an instance of this object to the `InitFile`, `InitCommand`, + * `InitSource` and `InitPackage` objects, and finally to an `InitService` + * itself to cause the actions (files, commands, sources, and packages) + * to trigger a restart of the service. + * + * For example, the following will run a custom command to install Nginx, + * and trigger the nginx service to be restarted after the command has run. + * + * ```ts + * const handle = new ec2.InitServiceRestartHandle(); + * ec2.CloudFormationInit.fromElements( + * ec2.InitCommand.shellCommand('/usr/bin/custom-nginx-install.sh', { serviceRestartHandles: [handle] }), + * ec2.InitService.enable('nginx', { serviceRestartHandle: handle }), + * ); + * ``` + */ +export class InitServiceRestartHandle { + private readonly commands = new Array(); + private readonly files = new Array(); + private readonly sources = new Array(); + private readonly packages: Record = {}; + + /** + * Add a command key to the restart set + * @internal + */ + public _addCommand(key: string) { + return this.commands.push(key); + } + + /** + * Add a file key to the restart set + * @internal + */ + public _addFile(key: string) { + return this.files.push(key); + } + + /** + * Add a source key to the restart set + * @internal + */ + public _addSource(key: string) { + return this.sources.push(key); + } + + /** + * Add a package key to the restart set + * @internal + */ + public _addPackage(packageType: string, key: string) { + if (!this.packages[packageType]) { + this.packages[packageType] = []; + } + this.packages[packageType].push(key); + } + + /** + * Render the restart handles for use in an InitService declaration + * @internal + */ + public _renderRestartHandles(): any { + const nonEmpty = (x: A[]) => x.length > 0 ? x : undefined; + + return { + commands: nonEmpty(this.commands), + files: nonEmpty(this.files), + packages: Object.keys(this.packages).length > 0 ? this.packages : undefined, + sources: nonEmpty(this.sources), + }; + } +} + +/** + * Base class for all CloudFormation Init elements + */ +export abstract class InitElement { + + /** + * Returns the init element type for this element. + */ + public abstract readonly elementType: string; + + /** + * Called when the Init config is being consumed. Renders the CloudFormation + * representation of this init element, and calculates any authentication + * properties needed, if any. + * + * @param options bind options for the element. + * @internal + */ + public abstract _bind(options: InitBindOptions): InitElementConfig; + +} + +/** + * Options for InitCommand + */ +export interface InitCommandOptions { + /** + * Identifier key for this command + * + * Commands are executed in lexicographical order of their key names. + * + * @default - Automatically generated based on index + */ + readonly key?: string; + + /** + * Sets environment variables for the command. + * + * This property overwrites, rather than appends, the existing environment. + * + * @default - Use current environment + */ + readonly env?: Record; + + /** + * The working directory + * + * @default - Use default working directory + */ + readonly cwd?: string; + + /** + * Command to determine whether this command should be run + * + * If the test passes (exits with error code of 0), the command is run. + * + * @default - Always run the command + */ + readonly testCmd?: string; + + /** + * Continue running if this command fails + * + * @default false + */ + readonly ignoreErrors?: boolean; + + /** + * The duration to wait after a command has finished in case the command causes a reboot. + * + * Set this value to `InitCommandWaitDuration.none()` if you do not want to wait for every command; + * `InitCommandWaitDuration.forever()` directs cfn-init to exit and resume only after the reboot is complete. + * + * For Windows systems only. + * + * @default - 60 seconds + */ + readonly waitAfterCompletion?: InitCommandWaitDuration; + + /** + * Restart the given service(s) after this command has run + * + * @default - Do not restart any service + */ + readonly serviceRestartHandles?: InitServiceRestartHandle[]; +} + +/** + * Represents a duration to wait after a command has finished, in case of a reboot (Windows only). + */ +export abstract class InitCommandWaitDuration { + /** Wait for a specified duration after a command. */ + public static of(duration: Duration): InitCommandWaitDuration { + return new class extends InitCommandWaitDuration { + /** @internal */ + public _render() { return duration.toSeconds(); } + }(); + } + + /** Do not wait for this command. */ + public static none(): InitCommandWaitDuration { + return InitCommandWaitDuration.of(Duration.seconds(0)); + } + + /** cfn-init will exit and resume only after a reboot. */ + public static forever(): InitCommandWaitDuration { + return new class extends InitCommandWaitDuration { + /** @internal */ + public _render() { return 'forever'; } + }(); + } + + /** + * Render to a CloudFormation value. + * @internal + */ + public abstract _render(): any; +} + +/** + * Command to execute on the instance + */ +export class InitCommand extends InitElement { + /** + * Run a shell command + * + * Remember that some characters like `&`, `|`, `;`, `>` etc. have special meaning in a shell and + * need to be preceded by a `\` if you want to treat them as part of a filename. + */ + public static shellCommand(shellCommand: string, options: InitCommandOptions = {}): InitCommand { + return new InitCommand([shellCommand], options); + } + + /** + * Run a command from an argv array + * + * You do not need to escape space characters or enclose command parameters in quotes. + */ + public static argvCommand(argv: string[], options: InitCommandOptions = {}): InitCommand { + if (argv.length === 0) { + throw new Error('Cannot define argvCommand with an empty arguments'); + } + return new InitCommand(argv, options); + } + + public readonly elementType = InitElementType.COMMAND.toString(); + + private constructor(private readonly command: string[], private readonly options: InitCommandOptions) { + super(); + } + + /** @internal */ + public _bind(options: InitBindOptions): InitElementConfig { + const commandKey = this.options.key || `${options.index}`.padStart(3, '0'); // 001, 005, etc. + + if (options.platform !== InitPlatform.WINDOWS && this.options.waitAfterCompletion !== undefined) { + throw new Error(`Command '${this.command}': 'waitAfterCompletion' is only valid for Windows systems.`); + } + + for (const handle of this.options.serviceRestartHandles ?? []) { + handle._addCommand(commandKey); + } + + return { + config: { + [commandKey]: { + command: this.command, + env: this.options.env, + cwd: this.options.cwd, + test: this.options.testCmd, + ignoreErrors: this.options.ignoreErrors, + waitAfterCompletion: this.options.waitAfterCompletion?._render(), + }, + }, + }; + } + +} + +/** + * Options for an InitService + */ +export interface InitServiceOptions { + /** + * Enable or disable this service + * + * Set to true to ensure that the service will be started automatically upon boot. + * + * Set to false to ensure that the service will not be started automatically upon boot. + * + * @default - true if used in `InitService.enable()`, no change to service + * state if used in `InitService.fromOptions()`. + */ + readonly enabled?: boolean; + + /** + * Make sure this service is running or not running after cfn-init finishes. + * + * Set to true to ensure that the service is running after cfn-init finishes. + * + * Set to false to ensure that the service is not running after cfn-init finishes. + * + * @default - same value as `enabled`. + */ + readonly ensureRunning?: boolean; + + /** + * Restart service when the actions registered into the restartHandle have been performed + * + * Register actions into the restartHandle by passing it to `InitFile`, `InitCommand`, + * `InitPackage` and `InitSource` objects. + * + * @default - No files trigger restart + */ + readonly serviceRestartHandle?: InitServiceRestartHandle; +} + +/** + * A services that be enabled, disabled or restarted when the instance is launched. + */ +export class InitService extends InitElement { + /** + * Enable and start the given service, optionally restarting it + */ + public static enable(serviceName: string, options: InitServiceOptions = {}): InitService { + const { enabled, ensureRunning, ...otherOptions } = options; + return new InitService(serviceName, { + enabled: enabled ?? true, + ensureRunning: ensureRunning ?? enabled ?? true, + ...otherOptions, + }); + } + + /** + * Disable and stop the given service + */ + public static disable(serviceName: string): InitService { + return new InitService(serviceName, { enabled: false, ensureRunning: false }); + } + + public readonly elementType = InitElementType.SERVICE.toString(); + + private constructor(private readonly serviceName: string, private readonly serviceOptions: InitServiceOptions) { + super(); + } + + /** @internal */ + public _bind(options: InitBindOptions): InitElementConfig { + const serviceManager = options.platform === InitPlatform.LINUX ? 'sysvinit' : 'windows'; + + return { + config: { + [serviceManager]: { + [this.serviceName]: { + enabled: this.serviceOptions.enabled, + ensureRunning: this.serviceOptions.ensureRunning, + ...this.serviceOptions.serviceRestartHandle?._renderRestartHandles(), + }, + }, + }, + }; + } + +} diff --git a/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts b/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts new file mode 100644 index 0000000000000..47985cbf5187c --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts @@ -0,0 +1,296 @@ +import * as crypto from 'crypto'; +import * as iam from '@aws-cdk/aws-iam'; +import { Aws, CfnResource, Construct } from '@aws-cdk/core'; +import { InitElement } from './cfn-init-elements'; +import { AttachInitOptions, InitBindOptions, InitElementConfig, InitElementType, InitPlatform } from './private/cfn-init-internal'; + +/** + * A CloudFormation-init configuration + */ +export class CloudFormationInit { + /** + * Build a new config from a set of Init Elements + */ + public static fromElements(...elements: InitElement[]): CloudFormationInit { + return CloudFormationInit.fromConfig(new InitConfig(elements)); + } + + /** + * Use an existing InitConfig object as the default and only config + */ + public static fromConfig(config: InitConfig): CloudFormationInit { + return CloudFormationInit.fromConfigSets({ + configSets: { + default: ['config'], + }, + configs: { config }, + }); + } + + /** + * Build a CloudFormationInit from config sets + */ + public static fromConfigSets(props: ConfigSetProps): CloudFormationInit { + return new CloudFormationInit(props.configSets, props.configs); + } + + private readonly _configSets: Record = {}; + private readonly _configs: Record = {}; + + private constructor(configSets: Record, configs: Record) { + Object.assign(this._configSets, configSets); + Object.assign(this._configs, configs); + } + + /** + * Add a config with the given name to this CloudFormationInit object + */ + public addConfig(configName: string, config: InitConfig) { + if (this._configs[configName]) { + throw new Error(`CloudFormationInit already contains a config named '${configName}'`); + } + this._configs[configName] = config; + } + + /** + * Add a config set with the given name to this CloudFormationInit object + * + * The new configset will reference the given configs in the given order. + */ + public addConfigSet(configSetName: string, configNames: string[] = []) { + if (this._configSets[configSetName]) { + throw new Error(`CloudFormationInit already contains a configSet named '${configSetName}'`); + } + + const unk = configNames.filter(c => !this._configs[c]); + if (unk.length > 0) { + throw new Error(`Unknown configs referenced in definition of '${configSetName}': ${unk}`); + } + + this._configSets[configSetName] = [...configNames]; + } + + /** + * Attach the CloudFormation Init config to the given resource + * + * This method does the following: + * + * - Renders the `AWS::CloudFormation::Init` object to the given resource's + * metadata, potentially adding a `AWS::CloudFormation::Authentication` object + * next to it if required. + * - Updates the instance role policy to be able to call the APIs required for + * `cfn-init` and `cfn-signal` to work, and potentially add permissions to download + * referenced asset and bucket resources. + * - Updates the given UserData with commands to execute the `cfn-init` script. + * + * As an app builder, use `instance.applyCloudFormationInit()` or + * `autoScalingGroup.applyCloudFormationInit()` to trigger this method. + * + * @internal + */ + public _attach(attachedResource: CfnResource, attachOptions: AttachInitOptions) { + // Note: This will not reflect mutations made after attaching. + const bindResult = this.bind(attachedResource.stack, attachOptions); + attachedResource.addMetadata('AWS::CloudFormation::Init', bindResult.configData); + const fingerprint = contentHash(JSON.stringify(bindResult.configData)).substr(0, 16); + + attachOptions.instanceRole.addToPolicy(new iam.PolicyStatement({ + actions: ['cloudformation:DescribeStackResource', 'cloudformation:SignalResource'], + resources: [Aws.STACK_ID], + })); + + if (bindResult.authData) { + attachedResource.addMetadata('AWS::CloudFormation::Authentication', bindResult.authData); + } + + // To identify the resources that have the metadata and where the signal + // needs to be sent, we need { region, stackName, logicalId } + const resourceLocator = `--region ${Aws.REGION} --stack ${Aws.STACK_NAME} --resource ${attachedResource.logicalId}`; + const configSets = (attachOptions.configSets ?? ['default']).join(','); + const printLog = attachOptions.printLog ?? true; + + if (attachOptions.embedFingerprint ?? true) { + // It just so happens that the comment char is '#' for both bash and PowerShell + attachOptions.userData.addCommands(`# fingerprint: ${fingerprint}`); + } + + if (attachOptions.platform === InitPlatform.WINDOWS) { + const errCode = attachOptions.ignoreFailures ? '0' : '$LASTEXITCODE'; + attachOptions.userData.addCommands(...[ + `cfn-init.exe -v ${resourceLocator} -c ${configSets}`, + `cfn-signal.exe -e ${errCode} ${resourceLocator}`, + ...printLog ? ['type C:\\cfn\\log\\cfn-init.log'] : [], + ]); + } else { + const errCode = attachOptions.ignoreFailures ? '0' : '$?'; + attachOptions.userData.addCommands(...[ + // Run a subshell without 'errexit', so we can signal using the exit code of cfn-init + '(', + ' set +e', + ` /opt/aws/bin/cfn-init -v ${resourceLocator} -c ${configSets}`, + ` /opt/aws/bin/cfn-signal -e ${errCode} ${resourceLocator}`, + ...printLog ? [' cat /var/log/cfn-init.log >&2'] : [], + ')', + ]); + } + } + + private bind(scope: Construct, options: AttachInitOptions): { configData: any, authData: any } { + const nonEmptyConfigs = mapValues(this._configs, c => c.isEmpty() ? undefined : c); + + const configNameToBindResult = mapValues(nonEmptyConfigs, c => c._bind(scope, options)); + + return { + configData: { + configSets: mapValues(this._configSets, configNames => configNames.filter(name => nonEmptyConfigs[name] !== undefined)), + ...mapValues(configNameToBindResult, c => c.config), + }, + authData: Object.values(configNameToBindResult).map(c => c.authentication).reduce(deepMerge, undefined), + }; + } + +} + +/** + * A collection of configuration elements + */ +export class InitConfig { + private readonly elements = new Array(); + + constructor(elements: InitElement[]) { + this.add(...elements); + } + + /** + * Whether this configset has elements or not + */ + public isEmpty() { + return this.elements.length === 0; + } + + /** + * Add one or more elements to the config + */ + public add(...elements: InitElement[]) { + this.elements.push(...elements); + } + + /** + * Called when the config is applied to an instance. + * Creates the CloudFormation representation of the Init config and handles any permissions and assets. + * @internal + */ + public _bind(scope: Construct, options: AttachInitOptions): InitElementConfig { + const bindOptions = { + instanceRole: options.instanceRole, + platform: options.platform, + scope, + }; + + const packageConfig = this.bindForType(InitElementType.PACKAGE, bindOptions); + const groupsConfig = this.bindForType(InitElementType.GROUP, bindOptions); + const usersConfig = this.bindForType(InitElementType.USER, bindOptions); + const sourcesConfig = this.bindForType(InitElementType.SOURCE, bindOptions); + const filesConfig = this.bindForType(InitElementType.FILE, bindOptions); + const commandsConfig = this.bindForType(InitElementType.COMMAND, bindOptions); + // Must be last! + const servicesConfig = this.bindForType(InitElementType.SERVICE, bindOptions); + + const authentication = [ packageConfig, groupsConfig, usersConfig, sourcesConfig, filesConfig, commandsConfig, servicesConfig ] + .map(c => c?.authentication) + .reduce(deepMerge, undefined); + + return { + config: { + packages: packageConfig?.config, + groups: groupsConfig?.config, + users: usersConfig?.config, + sources: sourcesConfig?.config, + files: filesConfig?.config, + commands: commandsConfig?.config, + services: servicesConfig?.config, + }, + authentication, + }; + } + + private bindForType(elementType: InitElementType, renderOptions: Omit): InitElementConfig | undefined { + const elements = this.elements.filter(elem => elem.elementType === elementType); + if (elements.length === 0) { return undefined; } + + const bindResults = elements.map((e, index) => e._bind({ index, ...renderOptions })); + + return { + config: bindResults.map(r => r.config).reduce(deepMerge, undefined) ?? {}, + authentication: bindResults.map(r => r.authentication).reduce(deepMerge, undefined), + }; + } +} + +/** + * Options for CloudFormationInit.withConfigSets + */ +export interface ConfigSetProps { + /** + * The definitions of each config set + */ + readonly configSets: Record; + + /** + * The sets of configs to pick from + */ + readonly configs: Record; +} + +/** + * Deep-merge objects and arrays + * + * Treat arrays as sets, removing duplicates. This is acceptable for rendering + * cfn-inits, not applicable elsewhere. + */ +function deepMerge(target?: Record, src?: Record) { + if (target == null) { return src; } + if (src == null) { return target; } + + for (const [key, value] of Object.entries(src)) { + if (Array.isArray(value)) { + if (target[key] && !Array.isArray(target[key])) { + throw new Error(`Trying to merge array [${value}] into a non-array '${target[key]}'`); + } + target[key] = Array.from(new Set([ + ...target[key] ?? [], + ...value, + ])); + continue; + } + if (typeof value === 'object' && value) { + target[key] = deepMerge(target[key] ?? {}, value); + continue; + } + if (value !== undefined) { + target[key] = value; + } + } + + return target; +} + +/** + * Map a function over values of an object + * + * If the mapping function returns undefined, remove the key + */ +function mapValues(xs: Record, fn: (x: A) => B | undefined): Record { + const ret: Record = {}; + for (const [k, v] of Object.entries(xs)) { + const mapped = fn(v); + if (mapped !== undefined) { + ret[k] = mapped; + } + } + return ret; +} + +function contentHash(content: string) { + return crypto.createHash('sha256').update(content).digest('hex'); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index f7afc14ccdd0b..ff7f7131d53e4 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -1,5 +1,7 @@ export * from './bastion-host'; export * from './connections'; +export * from './cfn-init'; +export * from './cfn-init-elements'; export * from './instance-types'; export * from './instance'; export * from './machine-image'; diff --git a/packages/@aws-cdk/aws-ec2/lib/instance.ts b/packages/@aws-cdk/aws-ec2/lib/instance.ts index 79a6bb262d053..ba619c7667f7e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance.ts @@ -1,10 +1,13 @@ +import * as crypto from 'crypto'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Duration, Fn, IResource, Lazy, Resource, Tag } from '@aws-cdk/core'; +import { Construct, Duration, Fn, IResource, Lazy, Resource, Stack, Tag } from '@aws-cdk/core'; +import { CloudFormationInit } from './cfn-init'; import { Connections, IConnectable } from './connections'; import { CfnInstance } from './ec2.generated'; import { InstanceType } from './instance-types'; import { IMachineImage, OperatingSystemType } from './machine-image'; +import { InitPlatform } from './private/cfn-init-internal'; import { ISecurityGroup, SecurityGroup } from './security-group'; import { UserData } from './user-data'; import { BlockDevice, synthesizeBlockDeviceMappings } from './volume'; @@ -137,6 +140,26 @@ export interface InstanceProps { */ readonly userData?: UserData; + /** + * Changes to the UserData force replacement + * + * Depending the EC2 instance type, changing UserData either + * restarts the instance or replaces the instance. + * + * - Instance store-backed instances are replaced. + * - EBS-backed instances are restarted. + * + * By default, restarting does not execute the new UserData so you + * will need a different mechanism to ensure the instance is restarted. + * + * Setting this to `true` will make the instance's Logical ID depend on the + * UserData, which will cause CloudFormation to replace it if the UserData + * changes. + * + * @default - true iff `initOptions` is specified, false otherwise. + */ + readonly userDataCausesReplacement?: boolean; + /** * An IAM role to associate with the instance profile assigned to this Auto Scaling Group. * @@ -190,6 +213,22 @@ export interface InstanceProps { * @default - no association */ readonly privateIpAddress?: string + + /** + * Apply the given CloudFormation Init configuration to the instance at startup + * + * @default - no CloudFormation init + */ + readonly init?: CloudFormationInit; + + /** + * Use the given options for applying CloudFormation Init + * + * Describes the configsets to use and the timeout to wait + * + * @default - default options + */ + readonly initOptions?: ApplyCloudFormationInitOptions; } /** @@ -257,6 +296,10 @@ export class Instance extends Resource implements IInstance { constructor(scope: Construct, id: string, props: InstanceProps) { super(scope, id); + if (props.initOptions && !props.init) { + throw new Error('Setting \'initOptions\' requires that \'init\' is also set'); + } + if (props.securityGroup) { this.securityGroup = props.securityGroup; } else { @@ -334,7 +377,23 @@ export class Instance extends Resource implements IInstance { this.instancePublicDnsName = this.instance.attrPublicDnsName; this.instancePublicIp = this.instance.attrPublicIp; + if (props.init) { + this.applyCloudFormationInit(props.init, props.initOptions); + } + this.applyUpdatePolicies(props); + + // Trigger replacement (via new logical ID) on user data change, if specified or cfn-init is being used. + const originalLogicalId = Stack.of(this).getLogicalId(this.instance); + this.instance.overrideLogicalId(Lazy.stringValue({ produce: () => { + let logicalId = originalLogicalId; + if (props.userDataCausesReplacement ?? props.initOptions) { + const md5 = crypto.createHash('md5'); + md5.update(this.userData.render()); + logicalId += md5.digest('hex').substr(0, 16); + } + return logicalId; + }})); } /** @@ -361,6 +420,50 @@ export class Instance extends Resource implements IInstance { this.role.addToPolicy(statement); } + /** + * Use a CloudFormation Init configuration at instance startup + * + * This does the following: + * + * - Attaches the CloudFormation Init metadata to the Instance resource. + * - Add commands to the instance UserData to run `cfn-init` and `cfn-signal`. + * - Update the instance's CreationPolicy to wait for the `cfn-signal` commands. + */ + private applyCloudFormationInit(init: CloudFormationInit, options: ApplyCloudFormationInitOptions = {}) { + const platform = this.osType === OperatingSystemType.WINDOWS ? InitPlatform.WINDOWS : InitPlatform.LINUX; + init._attach(this.instance, { + platform, + instanceRole: this.role, + userData: this.userData, + configSets: options.configSets, + embedFingerprint: options.embedFingerprint, + printLog: options.printLog, + ignoreFailures: options.ignoreFailures, + }); + this.waitForResourceSignal(options.timeout ?? Duration.minutes(5)); + } + + /** + * Wait for a single additional resource signal + * + * Add 1 to the current ResourceSignal Count and add the given timeout to the current timeout. + * + * Use this to pause the CloudFormation deployment to wait for the instances + * in the AutoScalingGroup to report successful startup during + * creation and updates. The UserData script needs to invoke `cfn-signal` + * with a success or failure code after it is done setting up the instance. + */ + private waitForResourceSignal(timeout: Duration) { + const oldResourceSignal = this.instance.cfnOptions.creationPolicy?.resourceSignal; + this.instance.cfnOptions.creationPolicy = { + ...this.instance.cfnOptions.creationPolicy, + resourceSignal: { + count: (oldResourceSignal?.count ?? 0) + 1, + timeout: (oldResourceSignal?.timeout ? Duration.parse(oldResourceSignal?.timeout).plus(timeout) : timeout).toIsoString(), + }, + }; + } + /** * Apply CloudFormation update policies for the instance */ @@ -375,3 +478,70 @@ export class Instance extends Resource implements IInstance { } } } + +/** + * Options for applying CloudFormation init to an instance or instance group + */ +export interface ApplyCloudFormationInitOptions { + /** + * ConfigSet to activate + * + * @default ['default'] + */ + readonly configSets?: string[]; + + /** + * Timeout waiting for the configuration to be applied + * + * @default Duration.minutes(5) + */ + readonly timeout?: Duration; + + /** + * Force instance replacement by embedding a config fingerprint + * + * If `true` (the default), a hash of the config will be embedded into the + * UserData, so that if the config changes, the UserData changes. + * + * - If the EC2 instance is instance-store backed or + * `userDataCausesReplacement` is set, this will cause the instance to be + * replaced and the new configuration to be applied. + * - If the instance is EBS-backed and `userDataCausesReplacement` is not + * set, the change of UserData will make the instance restart but not be + * replaced, and the configuration will not be applied automatically. + * + * If `false`, no hash will be embedded, and if the CloudFormation Init + * config changes nothing will happen to the running instance. If a + * config update introduces errors, you will not notice until after the + * CloudFormation deployment successfully finishes and the next instance + * fails to launch. + * + * @default true + */ + readonly embedFingerprint?: boolean; + + /** + * Print the results of running cfn-init to the Instance System Log + * + * By default, the output of running cfn-init is written to a log file + * on the instance. Set this to `true` to print it to the System Log + * (visible from the EC2 Console), `false` to not print it. + * + * (Be aware that the system log is refreshed at certain points in + * time of the instance life cycle, and successful execution may + * not always show up). + * + * @default true + */ + readonly printLog?: boolean; + + /** + * Don't fail the instance creation when cfn-init fails + * + * You can use this to prevent CloudFormation from rolling back when + * instances fail to start up, to help in debugging. + * + * @default false + */ + readonly ignoreFailures?: boolean; +} diff --git a/packages/@aws-cdk/aws-ec2/lib/private/cfn-init-internal.ts b/packages/@aws-cdk/aws-ec2/lib/private/cfn-init-internal.ts new file mode 100644 index 0000000000000..ced90167c6101 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/private/cfn-init-internal.ts @@ -0,0 +1,140 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { Construct } from '@aws-cdk/core'; +import { UserData } from '../user-data'; + +/** + * The type of the init element. + */ +export enum InitElementType { + PACKAGE = 'PACKAGE', + GROUP = 'GROUP', + USER = 'USER', + SOURCE = 'SOURCE', + FILE = 'FILE', + COMMAND = 'COMMAND', + SERVICE = 'SERVICE', +} + +/** + * The platform to which the init template applies. + */ +export enum InitPlatform { + WINDOWS = 'WINDOWS', + LINUX = 'LINUX', +} + +/** + * Context information passed when an InitElement is being consumed + * @internal + */ +export interface InitBindOptions { + /** + * Scope in which to define any resources, if necessary. + */ + readonly scope: Construct; + + /** + * Which OS platform (Linux, Windows) the init block will be for. + * Impacts which config types are available and how they are created. + */ + readonly platform: InitPlatform; + + /** + * Ordered index of current element type. Primarily used to auto-generate + * command keys and retain ordering. + */ + readonly index: number; + + /** + * Instance role of the consuming instance or fleet + */ + readonly instanceRole: iam.IRole; +} + +/** + * A return type for a configured InitElement. Both its CloudFormation representation, and any + * additional metadata needed to create the CloudFormation::Init. + * + * Marked internal so as not to leak the underlying L1 representation. + * + * @internal + */ +export interface InitElementConfig { + /** + * The CloudFormation representation of the configuration of an InitElement. + */ + readonly config: Record; + + /** + * Optional authentication blocks to be associated with the Init Config + * + * @default - No authentication associated with the config + */ + readonly authentication?: Record; +} + +/** + * Options for attach a CloudFormationInit to a resource + */ +export interface AttachInitOptions { + /** + * Instance role of the consuming instance or fleet + */ + readonly instanceRole: iam.IRole; + + /** + * OS Platfrom the init config will be used for + */ + readonly platform: InitPlatform; + + /** + * UserData to add commands to + */ + readonly userData: UserData; + + /** + * ConfigSet to activate + * + * @default ['default'] + */ + readonly configSets?: string[]; + + /** + * Whether to embed a hash into the userData + * + * If `true` (the default), a hash of the config will be embedded into the + * UserData, so that if the config changes, the UserData changes and + * the instance will be replaced. + * + * If `false`, no such hash will be embedded, and if the CloudFormation Init + * config changes nothing will happen to the running instance. + * + * @default true + */ + readonly embedFingerprint?: boolean; + + /** + * Print the results of running cfn-init to the Instance System Log + * + * By default, the output of running cfn-init is written to a log file + * on the instance. Set this to `true` to print it to the System Log + * (visible from the EC2 Console), `false` to not print it. + * + * (Be aware that the system log is refreshed at certain points in + * time of the instance life cycle, and successful execution may + * not always show up). + * + * @default true + */ + readonly printLog?: boolean; + + /** + * Don't fail the instance creation when cfn-init fails + * + * You can use this to prevent CloudFormation from rolling back when + * instances fail to start up, to help in debugging. + * + * @default false + */ + readonly ignoreFailures?: boolean; +} diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index ad6075cc5ec77..cae87529d643b 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -77,20 +77,23 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/region-info": "0.0.0", "constructs": "^3.0.2" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", diff --git a/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts b/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts new file mode 100644 index 0000000000000..fd713a4173370 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts @@ -0,0 +1,176 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { App, Duration, Stack } from '@aws-cdk/core'; +import * as ec2 from '../lib'; +import { InitPlatform } from '../lib/private/cfn-init-internal'; + +let app: App; +let stack: Stack; + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '1234', region: 'testregion' }, + }); +}); + +describe('InitCommand', () => { + + test('throws error on empty argv command', () => { + expect(() => { ec2.InitCommand.argvCommand([]); }).toThrow(); + }); + + test('auto-generates an indexed command key if none is provided', () => { + // GIVEN + const command = ec2.InitCommand.shellCommand('/bin/sh'); + + // WHEN + const rendered = getElementConfig(command, InitPlatform.LINUX); + + // THEN + expect(rendered['000']).toBeDefined(); + }); + + test('renders a minimalist template when no options are defined', () => { + // GIVEN + const command = ec2.InitCommand.shellCommand('/bin/sh'); + + // WHEN + const rendered = getElementConfig(command, InitPlatform.LINUX); + + // THEN + expect(rendered).toEqual({ + '000': { command: ['/bin/sh'] }, + }); + }); + + test('creates a shell command with all provided options', () => { + // GIVEN + const command = ec2.InitCommand.shellCommand('/bin/sh', { + key: 'command_0', + env: { SECRETS_FILE: '/tmp/secrets' }, + cwd: '/home/myUser', + testCmd: 'test -d /home/myUser', + ignoreErrors: false, + waitAfterCompletion: ec2.InitCommandWaitDuration.of(Duration.hours(2)), + }); + + // WHEN + const rendered = getElementConfig(command, InitPlatform.WINDOWS); + + // THEN + expect(rendered).toEqual({ + command_0: { + command: ['/bin/sh'], + env: { SECRETS_FILE: '/tmp/secrets' }, + cwd: '/home/myUser', + test: 'test -d /home/myUser', + ignoreErrors: false, + waitAfterCompletion: 7200, + }, + }); + }); + + test('creates an argv command with all provided options', () => { + // GIVEN + const command = ec2.InitCommand.argvCommand(['/bin/sh', '-c', 'doStuff'], { + key: 'command_0', + env: { SECRETS_FILE: '/tmp/secrets' }, + cwd: '/home/myUser', + testCmd: 'test -d /home/myUser', + ignoreErrors: false, + waitAfterCompletion: ec2.InitCommandWaitDuration.of(Duration.hours(2)), + }); + + // WHEN + const rendered = getElementConfig(command, InitPlatform.WINDOWS); + + // THEN + expect(rendered).toEqual({ + command_0: { + command: ['/bin/sh', '-c', 'doStuff'], + env: { SECRETS_FILE: '/tmp/secrets' }, + cwd: '/home/myUser', + test: 'test -d /home/myUser', + ignoreErrors: false, + waitAfterCompletion: 7200, + }, + }); + }); + + test('errors when waitAfterCompletion is specified for Linux systems', () => { + // GIVEN + const command = ec2.InitCommand.shellCommand('/bin/sh', { + key: 'command_0', + env: { SECRETS_FILE: '/tmp/secrets' }, + cwd: '/home/myUser', + testCmd: 'test -d /home/myUser', + ignoreErrors: false, + waitAfterCompletion: ec2.InitCommandWaitDuration.of(Duration.hours(2)), + }); + + // THEN + expect(() => { + command._bind(defaultOptions(InitPlatform.LINUX)); + }).toThrow(/'waitAfterCompletion' is only valid for Windows/); + }); + +}); + +describe('InitService', () => { + + test.each([ + ['Linux', 'sysvinit', InitPlatform.LINUX], + ['Windows', 'windows', InitPlatform.WINDOWS], + ])('enable always sets enabled and running to true for %s', (_platform, key, platform) => { + // GIVEN + const service = ec2.InitService.enable('httpd'); + + // WHEN + const rendered = service._bind(defaultOptions(platform)).config; + + // THEN + expect(rendered[key]).toBeDefined(); + expect(rendered[key]).toEqual({ + httpd: { + enabled: true, + ensureRunning: true, + }, + }); + }); + + test.each([ + ['Linux', 'sysvinit', InitPlatform.LINUX], + ['Windows', 'windows', InitPlatform.WINDOWS], + ])('disable returns a minimalist disabled service for %s', (_platform, key, platform) => { + // GIVEN + const service = ec2.InitService.disable('httpd'); + + // WHEN + const rendered = service._bind(defaultOptions(platform)).config; + + // THEN + expect(rendered[key]).toBeDefined(); + expect(rendered[key]).toEqual({ + httpd: { + enabled: false, + ensureRunning: false, + }, + }); + }); + +}); + +function getElementConfig(element: ec2.InitElement, platform: InitPlatform) { + return element._bind(defaultOptions(platform)).config; +} + +function defaultOptions(platform: InitPlatform) { + return { + scope: stack, + index: 0, + platform, + instanceRole: new iam.Role(stack, 'InstanceRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + }), + }; +} diff --git a/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts b/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts new file mode 100644 index 0000000000000..cee99ad289a5c --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts @@ -0,0 +1,237 @@ +import { ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as iam from '@aws-cdk/aws-iam'; +import { App, Aws, CfnResource, Stack } from '@aws-cdk/core'; +import * as ec2 from '../lib'; +import { InitPlatform } from '../lib/private/cfn-init-internal'; + +let app: App; +let stack: Stack; +let instanceRole: iam.Role; +let resource: CfnResource; +let linuxUserData: ec2.UserData; + +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '1234', region: 'testregion' }, + }); + instanceRole = new iam.Role(stack, 'InstanceRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + }); + resource = new CfnResource(stack, 'Resource', { + type: 'CDK::Test::Resource', + }); + linuxUserData = ec2.UserData.forLinux(); +}); + +test('whole config with restart handles', () => { + // WHEN + const handle = new ec2.InitServiceRestartHandle(); + const config = new ec2.InitConfig([ + ec2.InitCommand.argvCommand(['/bin/true'], { serviceRestartHandles: [handle] }), + ec2.InitService.enable('httpd', { serviceRestartHandle: handle }), + ]); + + // THEN + expect(config._bind(stack, linuxOptions()).config).toEqual(expect.objectContaining({ + services: { + sysvinit: { + httpd: { + enabled: true, + ensureRunning: true, + commands: ['000'], + }, + }, + }, + })); +}); + +test('CloudFormationInit can be added to after instantiation', () => { + // GIVEN + const config = new ec2.InitConfig([]); + const init = ec2.CloudFormationInit.fromConfig(config); + + // WHEN + config.add(ec2.InitCommand.argvCommand(['/bin/true'])); + init._attach(resource, linuxOptions()); + + // THEN + expectMetadataLike({ + 'AWS::CloudFormation::Init': { + config: { + commands: { + '000': { command: ['/bin/true'] }, + }, + }, + }, + }); +}); + +test('empty configs are not rendered', () => { + // GIVEN + const config1 = new ec2.InitConfig([]); + const config2 = new ec2.InitConfig([ + ec2.InitCommand.argvCommand(['/bin/true']), + ]); + + // WHEN + const init = ec2.CloudFormationInit.fromConfigSets({ + configSets: { default: ['config2', 'config1'] }, + configs: { config1, config2 }, + }); + init._attach(resource, linuxOptions()); + + // THEN + expectMetadataLike({ + 'AWS::CloudFormation::Init': { + configSets: { + default: ['config2'], + }, + config2: { + commands: { + '000': { command: ['/bin/true'] }, + }, + }, + }, + }); +}); + +describe('userdata', () => { + let simpleInit: ec2.CloudFormationInit; + beforeEach(() => { + simpleInit = ec2.CloudFormationInit.fromElements( + ec2.InitCommand.argvCommand(['/bin/true']), + ); + }); + + test('linux userdata contains right commands', () => { + // WHEN + simpleInit._attach(resource, linuxOptions()); + + // THEN + const lines = linuxUserData.render().split('\n'); + expectLine(lines, cmdArg('cfn-init', `--region ${Aws.REGION}`)); + expectLine(lines, cmdArg('cfn-init', `--stack ${Aws.STACK_NAME}`)); + expectLine(lines, cmdArg('cfn-init', `--resource ${resource.logicalId}`)); + expectLine(lines, cmdArg('cfn-init', '-c default')); + expectLine(lines, cmdArg('cfn-signal', `--region ${Aws.REGION}`)); + expectLine(lines, cmdArg('cfn-signal', `--stack ${Aws.STACK_NAME}`)); + expectLine(lines, cmdArg('cfn-signal', `--resource ${resource.logicalId}`)); + expectLine(lines, cmdArg('cfn-signal', '-e $?')); + expectLine(lines, cmdArg('cat', 'cfn-init.log')); + expectLine(lines, /fingerprint/); + }); + + test('Windows userdata contains right commands', () => { + // WHEN + const windowsUserData = ec2.UserData.forWindows(); + + simpleInit._attach(resource, { + platform: InitPlatform.WINDOWS, + instanceRole, + userData: windowsUserData, + }); + + // THEN + const lines = windowsUserData.render().split('\n'); + expectLine(lines, cmdArg('cfn-init', `--region ${Aws.REGION}`)); + expectLine(lines, cmdArg('cfn-init', `--stack ${Aws.STACK_NAME}`)); + expectLine(lines, cmdArg('cfn-init', `--resource ${resource.logicalId}`)); + expectLine(lines, cmdArg('cfn-init', '-c default')); + expectLine(lines, cmdArg('cfn-signal', `--region ${Aws.REGION}`)); + expectLine(lines, cmdArg('cfn-signal', `--stack ${Aws.STACK_NAME}`)); + expectLine(lines, cmdArg('cfn-signal', `--resource ${resource.logicalId}`)); + expectLine(lines, cmdArg('cfn-signal', '-e $LASTEXITCODE')); + expectLine(lines, cmdArg('type', 'cfn-init.log')); + expectLine(lines, /fingerprint/); + }); + + test('ignoreFailures disables result code reporting', () => { + // WHEN + simpleInit._attach(resource, { + ...linuxOptions(), + ignoreFailures: true, + }); + + // THEN + const lines = linuxUserData.render().split('\n'); + dontExpectLine(lines, cmdArg('cfn-signal', '-e $?')); + expectLine(lines, cmdArg('cfn-signal', '-e 0')); + }); + + test('can disable log printing', () => { + // WHEN + simpleInit._attach(resource, { + ...linuxOptions(), + printLog: false, + }); + + // THEN + const lines = linuxUserData.render().split('\n'); + dontExpectLine(lines, cmdArg('cat', 'cfn-init.log')); + }); + + test('can disable fingerprinting', () => { + // WHEN + simpleInit._attach(resource, { + ...linuxOptions(), + embedFingerprint: false, + }); + + // THEN + const lines = linuxUserData.render().split('\n'); + dontExpectLine(lines, /fingerprint/); + }); + + test('can request multiple different configsets to be used', () => { + // WHEN + simpleInit._attach(resource, { + ...linuxOptions(), + configSets: ['banana', 'peach'], + }); + + // THEN + const lines = linuxUserData.render().split('\n'); + expectLine(lines, cmdArg('cfn-init', '-c banana,peach')); + }); +}); + +function linuxOptions() { + return { + platform: InitPlatform.LINUX, + instanceRole, + userData: linuxUserData, + }; +} + +function expectMetadataLike(pattern: any) { + expect(stack).toHaveResourceLike('CDK::Test::Resource', { + Metadata: pattern, + }, ResourcePart.CompleteDefinition); +} + +function expectLine(lines: string[], re: RegExp) { + for (const line of lines) { + if (re.test(line)) { return; } + } + + throw new Error(`None of the lines matched '${re}': ${lines.join('\n')}`); +} + +function dontExpectLine(lines: string[], re: RegExp) { + try { + expectLine(lines, re); + } catch (e) { + return; + } + throw new Error(`Found unexpected line matching '${re}': ${lines.join('\n')}`); +} + +function cmdArg(command: string, argument: string) { + return new RegExp(`${escapeRegex(command)}(\.exe)? .*${escapeRegex(argument)}`); +} + +function escapeRegex(s: string) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 2a78f3cdf516e..0d3c06e49ceae 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -1,9 +1,11 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { arrayWith, expect as cdkExpect, haveResource, ResourcePart, stringLike } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; import { StringParameter } from '@aws-cdk/aws-ssm'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Stack } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; -import { AmazonLinuxImage, BlockDeviceVolume, EbsDeviceVolumeType, Instance, InstanceClass, InstanceSize, InstanceType, Vpc } from '../lib'; +import { AmazonLinuxImage, BlockDeviceVolume, CloudFormationInit, + EbsDeviceVolumeType, InitCommand, Instance, InstanceClass, InstanceSize, InstanceType, Vpc } from '../lib'; nodeunitShim({ 'instance is created correctly'(test: Test) { @@ -19,7 +21,7 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::EC2::Instance', { + cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { InstanceType: 't3.large', })); @@ -39,7 +41,7 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::EC2::Instance', { + cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { InstanceType: 't3.large', SourceDestCheck: false, })); @@ -61,7 +63,7 @@ nodeunitShim({ param.grantRead(instance); // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { + cdkExpect(stack).to(haveResource('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -139,7 +141,7 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::EC2::Instance', { + cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { BlockDeviceMappings: [ { DeviceName: 'ebs', @@ -285,7 +287,7 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::EC2::Instance', { + cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { InstanceType: 't3.large', PrivateIpAddress: '10.0.0.2', })); @@ -293,3 +295,54 @@ nodeunitShim({ test.done(); }, }); + +test('add CloudFormation Init to instance', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + new Instance(stack, 'Instance', { + vpc, + machineImage: new AmazonLinuxImage(), + instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.LARGE), + init: CloudFormationInit.fromElements( + InitCommand.shellCommand('echo hello'), + ), + }); + + // THEN + expect(stack).toHaveResource('AWS::EC2::Instance', { + UserData: { + 'Fn::Base64': { + 'Fn::Join': [ '', [ + stringLike('#!/bin/bash\n# fingerprint: *\n(\n set +e\n /opt/aws/bin/cfn-init -v --region '), + { Ref: 'AWS::Region' }, + ' --stack ', + { Ref: 'AWS::StackName' }, + ' --resource InstanceC1063A87 -c default\n /opt/aws/bin/cfn-signal -e $? --region ', + { Ref: 'AWS::Region' }, + ' --stack ', + { Ref: 'AWS::StackName' }, + ' --resource InstanceC1063A87\n cat /var/log/cfn-init.log >&2\n)', + ]], + }, + }, + }); + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith({ + Action: [ 'cloudformation:DescribeStackResource', 'cloudformation:SignalResource' ], + Effect: 'Allow', + Resource: { Ref: 'AWS::StackId' }, + }), + Version: '2012-10-17', + }, + }); + cdkExpect(stack).to(haveResource('AWS::EC2::Instance', { + CreationPolicy: { + ResourceSignal: { + Count: 1, + Timeout: 'PT5M', + }, + }, + }, ResourcePart.CompleteDefinition)); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json new file mode 100644 index 0000000000000..8a1144ff37fc2 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json @@ -0,0 +1,181 @@ +{ + "Resources": { + "Instance2InstanceSecurityGroupC6129B1D": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-init/Instance2/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-init/Instance2" + } + ], + "VpcId": "vpc-60900905" + } + }, + "Instance2InstanceRole03DD7CB2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-init/Instance2" + } + ] + } + }, + "Instance2InstanceRoleDefaultPolicy610B37CD": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cloudformation:DescribeStackResource", + "cloudformation:SignalResource" + ], + "Effect": "Allow", + "Resource": { + "Ref": "AWS::StackId" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Instance2InstanceRoleDefaultPolicy610B37CD", + "Roles": [ + { + "Ref": "Instance2InstanceRole03DD7CB2" + } + ] + } + }, + "Instance2InstanceProfile582F915C": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "Instance2InstanceRole03DD7CB2" + } + ] + } + }, + "Instance255F35265e4fe939d8bb9e8f3": { + "Type": "AWS::EC2::Instance", + "Properties": { + "AvailabilityZone": "us-east-1a", + "IamInstanceProfile": { + "Ref": "Instance2InstanceProfile582F915C" + }, + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.micro", + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "Instance2InstanceSecurityGroupC6129B1D", + "GroupId" + ] + } + ], + "SubnetId": "subnet-e19455ca", + "Tags": [ + { + "Key": "Name", + "Value": "integ-init/Instance2" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\n# fingerprint: 44c8061df9fadf16\n(\n set +e\n /opt/aws/bin/cfn-init -v --region ", + { + "Ref": "AWS::Region" + }, + " --stack ", + { + "Ref": "AWS::StackName" + }, + " --resource Instance255F35265e4fe939d8bb9e8f3 -c default\n /opt/aws/bin/cfn-signal -e $? --region ", + { + "Ref": "AWS::Region" + }, + " --stack ", + { + "Ref": "AWS::StackName" + }, + " --resource Instance255F35265e4fe939d8bb9e8f3\n cat /var/log/cfn-init.log >&2\n)" + ] + ] + } + } + }, + "DependsOn": [ + "Instance2InstanceRoleDefaultPolicy610B37CD", + "Instance2InstanceRole03DD7CB2" + ], + "CreationPolicy": { + "ResourceSignal": { + "Count": 1, + "Timeout": "PT30M" + } + }, + "Metadata": { + "AWS::CloudFormation::Init": { + "configSets": { + "default": [ + "config" + ] + }, + "config": { + "commands": { + "000": { + "command": [ + "/bin/true" + ] + } + } + } + } + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.instance-init.ts b/packages/@aws-cdk/aws-ec2/test/integ.instance-init.ts new file mode 100644 index 0000000000000..51d5b1bf07534 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.instance-init.ts @@ -0,0 +1,34 @@ +#!/usr/bin/env node +import * as cdk from '@aws-cdk/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as ec2 from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-init', { + env: { + account: process.env.CDK_INTEG_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_INTEG_REGION || process.env.CDK_DEFAULT_REGION, + }, +}); + +const vpc = ec2.Vpc.fromLookup(stack, 'VPC', { isDefault: true }); + +const tmpDir = fs.mkdtempSync('/tmp/cfn-init-test'); +fs.writeFileSync(path.resolve(tmpDir, 'testFile'), 'Hello World!\n'); + +new ec2.Instance(stack, 'Instance2', { + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + userDataCausesReplacement: true, + initOptions: { + timeout: cdk.Duration.minutes(30), + }, + init: ec2.CloudFormationInit.fromElements( + ec2.InitCommand.argvCommand(['/bin/true']), + ), +}); + +app.synth(); \ No newline at end of file From 246842f4f5259ca15b0c04fcda27e3fa37262594 Mon Sep 17 00:00:00 2001 From: Lars Fronius Date: Thu, 13 Aug 2020 14:10:14 +0200 Subject: [PATCH 011/422] fix(cloudfront): ensures origin groups are added with their own ID as a target (#9593) This is to fix the issue I created yesterday https://github.com/aws/aws-cdk/issues/9561 - mostly to get the CI running and see if the test pass already. The whole issue is described in #9561. Fixes #9561 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/integ.origin-group.expected.json | 12 ++++++++++- .../test/integ.origin-group.ts | 5 +++++ .../test/origin-group.test.ts | 4 ++++ .../aws-cloudfront/lib/distribution.ts | 20 ++++++++++++------- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json index b3d92abe8d269..a0e2688a424f7 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.expected.json @@ -72,9 +72,19 @@ "ForwardedValues": { "QueryString": false }, - "TargetOriginId": "cloudfrontorigingroupDistributionOrigin137659A54", + "TargetOriginId": "cloudfrontorigingroupDistributionOriginGroup10B57F1D1", "ViewerProtocolPolicy": "allow-all" }, + "CacheBehaviors": [ + { + "ForwardedValues": { + "QueryString": false + }, + "PathPattern": "/api", + "TargetOriginId": "cloudfrontorigingroupDistributionOriginGroup10B57F1D1", + "ViewerProtocolPolicy": "allow-all" + } + ], "Enabled": true, "HttpVersion": "http2", "IPV6Enabled": true, diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.ts index 31557317a9b40..aa210ad2755fa 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.origin-group.ts @@ -17,6 +17,11 @@ const originGroup = new origins.OriginGroup({ new cloudfront.Distribution(stack, 'Distribution', { defaultBehavior: { origin: originGroup }, + additionalBehaviors: { + '/api': { + origin: originGroup, + }, + }, }); app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/origin-group.test.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/origin-group.test.ts index 00dce3cc8c39e..2d343b686dff9 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/origin-group.test.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/origin-group.test.ts @@ -28,8 +28,12 @@ describe('Origin Groups', () => { const primaryOriginId = 'DistributionOrigin13547B94F'; const failoverOriginId = 'DistributionOrigin2C85CC43B'; + const originGroupId = 'DistributionOriginGroup1A1A31B49'; expect(stack).toHaveResourceLike('AWS::CloudFront::Distribution', { DistributionConfig: { + DefaultCacheBehavior: { + TargetOriginId: originGroupId, + }, Origins: [ { Id: primaryOriginId, diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index dee47eaa122df..c3b9d840e0edd 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -57,6 +57,7 @@ export interface DistributionAttributes { interface BoundOrigin extends OriginBindOptions, OriginBindConfig { readonly origin: IOrigin; + readonly originGroupId?: string; } /** @@ -291,30 +292,35 @@ export class Distribution extends Resource implements IDistribution { private addOrigin(origin: IOrigin, isFailoverOrigin: boolean = false): string { const existingOrigin = this.boundOrigins.find(boundOrigin => boundOrigin.origin === origin); if (existingOrigin) { - return existingOrigin.originId; + return existingOrigin.originGroupId ?? existingOrigin.originId; } else { const originIndex = this.boundOrigins.length + 1; const scope = new Construct(this, `Origin${originIndex}`); const originId = scope.node.uniqueId; const originBindConfig = origin.bind(scope, { originId }); - this.boundOrigins.push({ origin, originId, ...originBindConfig }); - if (originBindConfig.failoverConfig) { + if (!originBindConfig.failoverConfig) { + this.boundOrigins.push({origin, originId, ...originBindConfig}); + } else { if (isFailoverOrigin) { throw new Error('An Origin cannot use an Origin with its own failover configuration as its fallback origin!'); } + const groupIndex = this.originGroups.length + 1; + const originGroupId = new Construct(this, `OriginGroup${groupIndex}`).node.uniqueId; + this.boundOrigins.push({origin, originId, originGroupId, ...originBindConfig}); + const failoverOriginId = this.addOrigin(originBindConfig.failoverConfig.failoverOrigin, true); - this.addOriginGroup(originBindConfig.failoverConfig.statusCodes, originId, failoverOriginId); + this.addOriginGroup(originGroupId, originBindConfig.failoverConfig.statusCodes, originId, failoverOriginId); + return originGroupId; } return originId; } } - private addOriginGroup(statusCodes: number[] | undefined, originId: string, failoverOriginId: string): void { + private addOriginGroup(originGroupId: string, statusCodes: number[] | undefined, originId: string, failoverOriginId: string): void { statusCodes = statusCodes ?? [500, 502, 503, 504]; if (statusCodes.length === 0) { throw new Error('fallbackStatusCodes cannot be empty'); } - const groupIndex = this.originGroups.length + 1; this.originGroups.push({ failoverCriteria: { statusCodes: { @@ -322,7 +328,7 @@ export class Distribution extends Resource implements IDistribution { quantity: statusCodes.length, }, }, - id: new Construct(this, `OriginGroup${groupIndex}`).node.uniqueId, + id: originGroupId, members: { items: [ { originId }, From 09e994a204697930987ee9fa420ccbe1b20fb97b Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Thu, 13 Aug 2020 14:56:58 +0200 Subject: [PATCH 012/422] chore: apply white background on default logos (#9634) This will ensure they remain legible even on a dark backrgound, as for example where they are used as the icon image for NuGet packages within a dark mode IDE window. Fixes #9577 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- logo/default-128-dark.png | Bin 14088 -> 22325 bytes logo/default-256-dark.png | Bin 30620 -> 46245 bytes logo/default-64-dark.png | Bin 1532 -> 936 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/logo/default-128-dark.png b/logo/default-128-dark.png index bc0a4bf6aba975591ead83d8c55ce347508ea74d..deb5398d04f246b8d256a5f2351a7474994c3b8d 100644 GIT binary patch delta 22211 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 WG&edhI4dwPIxsK*qT`&CYBrvPNZeZh delta 13909 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 P_Za5Q^)f5hwBY{$*x#0R diff --git a/logo/default-256-dark.png b/logo/default-256-dark.png index bd504049ca2b58f5360a74615ea63f5fa8020ecf..94748f473f4514e5631f3d15244589f626f6818c 100644 GIT binary patch literal 46245 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 literal 30620 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 z%0A8jpOag{hJ6-yqNaj)&0n4i>lwT+cs*gs>hxGk$wkv{SVXW?4X^Kb4XBQyZLw*M zuj)iqoUC|I~K2Gf7Z_c|X;#vfL z$6gUnR(PH@(PwN!zymYkN^~)GIIt;;eSws6wN|Cve!HP_wNvz@`3{#yG%#f3lrXQ) zvYfrWu#1lCL*98b5L-&Q8ZEy{i?j$LutaAiJgs z?hX2;aYas97gPmoU{ypLdFL3)BV(~`mR@HgU$9R>6!G0S=$MLbEq_HY3F}~_G_QF7 z7NoOa9&_HPV5ssguixOWJ+?9alUWk3<*kuqgtjn68bMZx6_J+})etzn6GOe&Qz$m7 z?-4+bsISb zUu1@xS*_+DPU}cx!R<$1^GMJv4X&UiI3(~rH4?4`+Zx|v3BqD@UdG(v+8R^Ah?FULuiAf<>P9#ZM%0nljG%PRkiG zKGEM-2s2LYn=JJSw;DfcK$&|LNsr=nva(_?pNs%n8Ih)(QkJR!Vc&Br{D0p8&HtpFnqTgiTXf!l5tO<@ z;S+_2T;|)q%q@deHB$I6_4;;t6c<1>ptGz)JYVR5&lU&U_9%h%wc!F&SFp)$SG?Rf}wM?{EJV*Ee11aP{HcBU2F zKRgi&1S!8kih6xcN7IfPo^ysYk?dHWmP8-rf5l<#OsoI50UzAnIllM( zzYlm!x<-i|pz_}t+`a6jZQVY6uyggmVNtVlwfki4;r2$l&MVc|Dpg8WA^!_dfm6`{ diff --git a/logo/default-64-dark.png b/logo/default-64-dark.png index e62461c03dd3f1ab7185f3c36b6a2c750de61656..b4efc5f1cc640d7028a2fea7dd39bae7100b92e8 100755 GIT binary patch delta 905 zcmV;419trU3#bQ>8Gi!+005o0f$RVP00d`2O+f$vv5yP zfP?@5`Tzg`fam}Kbua(`>RI+y?e7jT@qQ9J+u05ecbR7L;)|MBwktg*IdZ+c8s zVk0j;b$o{7<>xRvNs*VH$ji^VzQTiwlGfPUK1fwrVs4(Ksu6N+Sg!y800Cl4M??UK z1szC{As2rD2XskIMF->v3=IYjf}I|p0007ANklo_iNm@>fM{hIMnei`n5PUL}R`H z_%qic=+k?|{C+`KV@%UA9L>`_>SP_5g01D=K+S*k!Z+Y-`Aa*-qo0NAw~L9?2dM_EP%xn#4XXPNVE-9F4pvP8 zV5^!{U|P)qtT-rWu7|=#HBiYwkS;8(qTr}n5*b%*?i|j03(9y3ZG=NRl2713up~7f zaE^a~BC6FQ9R}(bo%cgwXQ8``aG2!>?-oO;5UJz5Z><>u?5TDlhO++x;3iTi#t`77lPJAp1|U+I z0_3|C=nnd`j{;oTU=U<%PXO4L2iOtu72tm=#BWLj=Et}xQh$q)j920>?$j%)_<8{{ z&vQbVHA97)P}SIe6U7t&4)hrF$ct&)kiU)->AVRT<&2r)!zwd@BhdxDETvIKid+KQ z{hi}-kJq&joy&#UN`w}Gdb-zFC>d2Tg&YGA71N}K096rr@`NI;sjNeQ)URqw59$QiXaddHuZrN%V9KWK*&a@M2Q#s9qh0s1c-7*U~%_5f`F03~!qSaf7zbY(hYa%Ew3WdJfTGB7PLGA%MV zR536*F*Q0dF)J`IIxsK~a*;DzH+)$rV_tAan&MR*=D zZ{^WGDDHzg zC2Bc~PPKPd!+((wt$?De=3@4eI_R-p;gkS|=w$?;+ z!7i;+ZD}=%1Y|F<9GFigV|#~~IH1Ph8ClmV54I>PmiYtkV3MqHkFAcCeMv+uJPecV z+7c9i7zp^Q?a*qI)Ju44$pz z2^JECAPXqUiaYj&h&G*SCsu=uFbMJ9oMprjjo^ziW;ui=Lz?i85wS7XEZ_|+6USh0 zge6QZ2?G#Hf?5H}dXgDn|6bE(CBP~lpp0OIT7YK%B<9TwZ5JNRLALVqI6vl6#k%(kMpn!v2DP2{^vBv;yM7G{SuB8CD481;Y!P0}lR)ey(#!ZYRiE{?}dKxMRgsa2)))j?X z3ZP#~sNs$XN@#|>9NJzRTjg9uUgO!&j%ztz#O;Tna>OL)bbjmOG%FdBLcj+yuYYE1 zn43h>lf=9jgV-fQ_zed>tnd%?mblCeIocYXavXiGQB2h`deeEY0)$ZW@WZYm)RV^U zsxUq2a3jp~8r&n>JR==GBxf&WF3L&{Xo2Tm4>M?E1TWW`onz4E;f57nX00>w7eriW z5fFvQ*`>^avj(NmvAe@cm{?E*2Y+IkIx@UeIZ?Y+P7*wT($V=)j1gZ%^(n>zcI0JX z$PzG#(uL@yq{%b{soCJC0jcg}K&tDK(>UZbkYnx`MOzhTd*@@M%qM?Z{Hy@$&WRyO0Wmo`Wy8qtE=UBX*A#qVO4n)Z_B5dcZSM z=byO#18wBK+|ZgqjZG6m$ZmV%YU3+*3_-3hMqDAR3(iW8kmF5s4D`wgLDml^Q9Y_1 z8=FBPtRvT#a*~hHNM!$V)NwH4U|GtA+y|+A812(poyw*jsu~;TZ-=crCx){E-a#M! bI}p*oDtROoZrM%n00000NkvXXu0mjf(b3C* From fb5068ded6116b996b108037aa593684a2078351 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Thu, 13 Aug 2020 18:23:35 +0200 Subject: [PATCH 013/422] feat(cfnspec): cloudformation spec v16.3.0 (#9452) Suspicious changes: * IoT: **AWS::IoT::ProvisioningTemplate.Tags** used to be specified as a `List`. They now moved that same broken definition into changed to a `Tags` property type. It's still incorrect but in a different place. Updating the patch to match new the location of the broken definition. * ECS: a number of `Options` types used to be defined as `Map`, but are now typed as `Options` which is itself a property typed defined with 0 properties. This leads us to drop all properties added there on the floor (because they're all not part of the schema). See: https://github.com/aws/aws-cdk/issues/9676 --- allowed-breaking-changes.txt | 6 + .../test/integ.vpc-flow-logs.expected.json | 8 +- packages/@aws-cdk/aws-efs/package.json | 3 +- packages/@aws-cdk/cfnspec/CHANGELOG.md | 161 ++ packages/@aws-cdk/cfnspec/cfn.version | 2 +- ...0_CloudFormationResourceSpecification.json | 1430 ++++++++++++++++- .../cfnspec/spec-source/000_sam.spec.json | 346 ++++ .../500_ECS_Options_Types_patch.json | 35 + ...ngTemplate_Tags_CorrectItemType_patch.json | 6 +- 9 files changed, 1942 insertions(+), 55 deletions(-) create mode 100644 packages/@aws-cdk/cfnspec/spec-source/500_ECS_Options_Types_patch.json diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index d85b698a59ce6..56ecdf3636d0f 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -22,3 +22,9 @@ removed:@aws-cdk/cdk-assets-schema.FileAssetPackaging changed-type:@aws-cdk/aws-codedeploy.IServerDeploymentGroup.autoScalingGroups changed-type:@aws-cdk/aws-codedeploy.ServerDeploymentGroup.autoScalingGroups + +# We were leaking L1 types in L2 APIs, which now have changed required -> optional +# when ECS moved to the CloudFormation Registry spec. +change-return-type:@aws-cdk/aws-ecs.ContainerDefinition.renderContainerDefinition +change-return-type:@aws-cdk/aws-ecs.FirelensLogRouter.renderContainerDefinition +change-return-type:@aws-cdk/aws-ecs.LinuxParameters.renderLinuxParameters diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json index d0831e24d6ab8..cedf8d1bdea84 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.expected.json @@ -541,7 +541,13 @@ "Arn" ] }, - "LogDestinationType": "s3" + "LogDestinationType": "s3", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC" + } + ] } }, "FlowLogsCWIAMRole017AD736": { diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index 0710626c4c7e2..3b7a5db1e0b78 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -91,7 +91,8 @@ }, "awslint": { "exclude": [ - "props-physical-name:@aws-cdk/aws-efs.AccessPointProps" + "props-physical-name:@aws-cdk/aws-efs.AccessPointProps", + "resource-attribute:@aws-cdk/aws-efs.FileSystem.fileSystemArn" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 176c426cca4d5..168379fce3ccd 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,164 @@ +# CloudFormation Resource Specification v16.3.0 + +## New Resource Types + +* AWS::ApiGatewayV2::ApiGatewayManagedOverrides +* AWS::ApiGatewayV2::VpcLink +* AWS::AppMesh::GatewayRoute +* AWS::AppMesh::VirtualGateway + +## Attribute Changes + +* AWS::ECS::TaskDefinition TaskDefinitionArn (__added__) +* AWS::EFS::FileSystem Arn (__added__) + +## Property Changes + +* AWS::AmazonMQ::Broker AuthenticationStrategy (__added__) +* AWS::AmazonMQ::Broker LdapMetadata (__added__) +* AWS::AmazonMQ::Broker LdapServerMetadata (__added__) +* AWS::ApiGateway::DomainName DomainName.Required (__changed__) + * Old: true + * New: false +* AWS::ApiGateway::DomainName DomainName.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::ApiGateway::DomainName Tags.DuplicatesAllowed (__deleted__) +* AWS::CodeBuild::Project BuildBatchConfig (__added__) +* AWS::CodeBuild::ReportGroup Type.UpdateType (__changed__) + * Old: Mutable + * New: Immutable +* AWS::CodeGuruProfiler::ProfilingGroup AnomalyDetectionNotificationConfiguration (__added__) +* AWS::CodeGuruProfiler::ProfilingGroup Tags (__added__) +* AWS::CodeStarConnections::Connection HostArn (__added__) +* AWS::CodeStarConnections::Connection ProviderType.Required (__changed__) + * Old: true + * New: false +* AWS::EC2::FlowLog LogFormat (__added__) +* AWS::EC2::FlowLog MaxAggregationInterval (__added__) +* AWS::EC2::FlowLog Tags (__added__) +* AWS::ECS::TaskDefinition TaskDefinitionStatus (__added__) +* AWS::ECS::TaskDefinition Tags.DuplicatesAllowed (__deleted__) +* AWS::EFS::FileSystem BackupPolicy (__added__) +* AWS::ImageBuilder::InfrastructureConfiguration ResourceTags (__added__) +* AWS::IoT::ProvisioningTemplate Tags.ItemType (__deleted__) +* AWS::IoT::ProvisioningTemplate Tags.Type (__changed__) + * Old: List + * New: Tags +* AWS::KinesisFirehose::DeliveryStream HttpEndpointDestinationConfiguration (__added__) +* AWS::SecretsManager::RotationSchedule HostedRotationLambda (__added__) +* AWS::StepFunctions::StateMachine TracingConfiguration (__added__) + +## Property Type Changes + +* AWS::AmazonMQ::Broker.InterBrokerCred (__added__) +* AWS::AmazonMQ::Broker.LdapMetadata (__added__) +* AWS::AmazonMQ::Broker.LdapServerMetadata (__added__) +* AWS::AmazonMQ::Broker.ServerMetadata (__added__) +* AWS::CodeBuild::Project.BatchRestrictions (__added__) +* AWS::CodeBuild::Project.ProjectBuildBatchConfig (__added__) +* AWS::CodeGuruProfiler::ProfilingGroup.Channel (__added__) +* AWS::ECS::TaskDefinition.Options (__added__) +* AWS::EFS::FileSystem.BackupPolicy (__added__) +* AWS::IoT::ProvisioningTemplate.Tags (__added__) +* AWS::KinesisFirehose::DeliveryStream.HttpEndpointCommonAttribute (__added__) +* AWS::KinesisFirehose::DeliveryStream.HttpEndpointConfiguration (__added__) +* AWS::KinesisFirehose::DeliveryStream.HttpEndpointDestinationConfiguration (__added__) +* AWS::KinesisFirehose::DeliveryStream.HttpEndpointRequestConfiguration (__added__) +* AWS::KinesisFirehose::DeliveryStream.RetryOptions (__added__) +* AWS::SecretsManager::RotationSchedule.HostedRotationLambda (__added__) +* AWS::StepFunctions::StateMachine.TracingConfiguration (__added__) +* AWS::ApiGateway::DomainName.EndpointConfiguration Types.DuplicatesAllowed (__deleted__) +* AWS::CloudFront::Distribution.CacheBehavior CachePolicyId (__added__) +* AWS::CloudFront::Distribution.CacheBehavior OriginRequestPolicyId (__added__) +* AWS::CloudFront::Distribution.CacheBehavior ForwardedValues.Required (__changed__) + * Old: true + * New: false +* AWS::CloudFront::Distribution.DefaultCacheBehavior CachePolicyId (__added__) +* AWS::CloudFront::Distribution.DefaultCacheBehavior OriginRequestPolicyId (__added__) +* AWS::CloudFront::Distribution.DefaultCacheBehavior ForwardedValues.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.ContainerDefinition Command.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition DependsOn.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition DnsSearchDomains.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition DnsServers.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition DockerLabels.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition DockerSecurityOptions.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition EntryPoint.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition ExtraHosts.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition ResourceRequirements.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition Secrets.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition SystemControls.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDefinition Ulimits.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.ContainerDependency Condition.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.ContainerDependency ContainerName.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.Device HostPath.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.DockerVolumeConfiguration DriverOpts.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.DockerVolumeConfiguration Labels.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.FirelensConfiguration Options.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.FirelensConfiguration Options.PrimitiveItemType (__deleted__) +* AWS::ECS::TaskDefinition.FirelensConfiguration Options.Type (__changed__) + * Old: Map + * New: Options +* AWS::ECS::TaskDefinition.FirelensConfiguration Type.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.HealthCheck Command.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.HealthCheck Command.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.HostEntry Hostname.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.HostEntry IpAddress.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.KernelCapabilities Add.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.KernelCapabilities Drop.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.LinuxParameters Devices.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.LinuxParameters Tmpfs.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.LogConfiguration Options.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.LogConfiguration Options.PrimitiveItemType (__deleted__) +* AWS::ECS::TaskDefinition.LogConfiguration Options.Type (__changed__) + * Old: Map + * New: Options +* AWS::ECS::TaskDefinition.LogConfiguration SecretOptions.DuplicatesAllowed (__deleted__) +* AWS::ECS::TaskDefinition.SystemControl Namespace.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.SystemControl Value.Required (__changed__) + * Old: true + * New: false +* AWS::ECS::TaskDefinition.Tmpfs MountOptions.DuplicatesAllowed (__deleted__) +* AWS::FSx::FileSystem.LustreConfiguration AutoImportPolicy (__added__) +* AWS::ImageBuilder::DistributionConfiguration.Distribution Region.Required (__changed__) + * Old: false + * New: true + +# Serverless Application Model (SAM) Resource Specification v2016-10-31 + +## New Resource Types + +* AWS::Serverless::StateMachine + +## Attribute Changes + + +## Property Changes + +* AWS::Serverless::Function FileSystemConfigs (__added__) + +## Property Type Changes + +* AWS::Serverless::Function.FileSystemConfig (__added__) + # Serverless Application Model (SAM) Resource Specification v2016-10-31 diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index e0228cf182652..d9a8a35788584 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -16.1.0 +16.3.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 32e331c7f9fb0..e5595ee2b6466 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -236,6 +236,113 @@ } } }, + "AWS::AmazonMQ::Broker.InterBrokerCred": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-interbrokercred.html", + "Properties": { + "Password": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-interbrokercred.html#cfn-amazonmq-broker-interbrokercred-password", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Username": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-interbrokercred.html#cfn-amazonmq-broker-interbrokercred-username", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AmazonMQ::Broker.LdapMetadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapmetadata.html", + "Properties": { + "InterBrokerCreds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapmetadata.html#cfn-amazonmq-broker-ldapmetadata-interbrokercreds", + "ItemType": "InterBrokerCred", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ServerMetadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapmetadata.html#cfn-amazonmq-broker-ldapmetadata-servermetadata", + "Required": true, + "Type": "ServerMetadata", + "UpdateType": "Mutable" + } + } + }, + "AWS::AmazonMQ::Broker.LdapServerMetadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html", + "Properties": { + "Hosts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-hosts", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "RoleBase": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-rolebase", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-rolename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "RoleSearchMatching": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-rolesearchmatching", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleSearchSubtree": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-rolesearchsubtree", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "ServiceAccountPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-serviceaccountpassword", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ServiceAccountUsername": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-serviceaccountusername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "UserBase": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-userbase", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "UserRoleName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-userrolename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "UserSearchMatching": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-usersearchmatching", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "UserSearchSubtree": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-ldapservermetadata.html#cfn-amazonmq-broker-ldapservermetadata-usersearchsubtree", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AmazonMQ::Broker.LogList": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-loglist.html", "Properties": { @@ -276,6 +383,78 @@ } } }, + "AWS::AmazonMQ::Broker.ServerMetadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html", + "Properties": { + "Hosts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-hosts", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "RoleBase": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-rolebase", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-rolename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "RoleSearchMatching": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-rolesearchmatching", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "RoleSearchSubtree": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-rolesearchsubtree", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "ServiceAccountPassword": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-serviceaccountpassword", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ServiceAccountUsername": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-serviceaccountusername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "UserBase": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-userbase", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "UserRoleName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-userrolename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "UserSearchMatching": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-usersearchmatching", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "UserSearchSubtree": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-servermetadata.html#cfn-amazonmq-broker-servermetadata-usersearchsubtree", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AmazonMQ::Broker.TagsEntry": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-amazonmq-broker-tagsentry.html", "Properties": { @@ -858,7 +1037,6 @@ "Properties": { "Types": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-domainname-endpointconfiguration.html#cfn-apigateway-domainname-endpointconfiguration-types", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -1331,6 +1509,164 @@ } } }, + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides.AccessLogSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-accesslogsettings.html", + "Properties": { + "DestinationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-accesslogsettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-accesslogsettings-destinationarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Format": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-accesslogsettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-accesslogsettings-format", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides.IntegrationOverrides": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IntegrationMethod": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides-integrationmethod", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PayloadFormatVersion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides-payloadformatversion", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TimeoutInMillis": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-integrationoverrides-timeoutinmillis", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides.RouteOverrides": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routeoverrides.html", + "Properties": { + "AuthorizationScopes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routeoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routeoverrides-authorizationscopes", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "AuthorizationType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routeoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routeoverrides-authorizationtype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "AuthorizerId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routeoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routeoverrides-authorizerid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "OperationName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routeoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routeoverrides-operationname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Target": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routeoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routeoverrides-target", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides.RouteSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routesettings.html", + "Properties": { + "DataTraceEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routesettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routesettings-datatraceenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "DetailedMetricsEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routesettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routesettings-detailedmetricsenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "LoggingLevel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routesettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routesettings-logginglevel", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ThrottlingBurstLimit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routesettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routesettings-throttlingburstlimit", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "ThrottlingRateLimit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-routesettings.html#cfn-apigatewayv2-apigatewaymanagedoverrides-routesettings-throttlingratelimit", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides.StageOverrides": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html", + "Properties": { + "AccessLogSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-accesslogsettings", + "Required": false, + "Type": "AccessLogSettings", + "UpdateType": "Mutable" + }, + "AutoDeploy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-autodeploy", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "DefaultRouteSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-defaultroutesettings", + "Required": false, + "Type": "RouteSettings", + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "RouteSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-routesettings", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "StageVariables": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-apigatewaymanagedoverrides-stageoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stageoverrides-stagevariables", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ApiGatewayV2::Authorizer.JWTConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-authorizer-jwtconfiguration.html", "Properties": { @@ -1576,6 +1912,129 @@ } } }, + "AWS::AppMesh::GatewayRoute.GatewayRouteSpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutespec.html", + "Properties": { + "GrpcRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutespec.html#cfn-appmesh-gatewayroute-gatewayroutespec-grpcroute", + "Required": false, + "Type": "GrpcGatewayRoute", + "UpdateType": "Mutable" + }, + "Http2Route": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutespec.html#cfn-appmesh-gatewayroute-gatewayroutespec-http2route", + "Required": false, + "Type": "HttpGatewayRoute", + "UpdateType": "Mutable" + }, + "HttpRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutespec.html#cfn-appmesh-gatewayroute-gatewayroutespec-httproute", + "Required": false, + "Type": "HttpGatewayRoute", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.GatewayRouteTarget": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutetarget.html", + "Properties": { + "VirtualService": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutetarget.html#cfn-appmesh-gatewayroute-gatewayroutetarget-virtualservice", + "Required": true, + "Type": "GatewayRouteVirtualService", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.GatewayRouteVirtualService": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutevirtualservice.html", + "Properties": { + "VirtualServiceName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-gatewayroutevirtualservice.html#cfn-appmesh-gatewayroute-gatewayroutevirtualservice-virtualservicename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.GrpcGatewayRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayroute.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayroute.html#cfn-appmesh-gatewayroute-grpcgatewayroute-action", + "Required": true, + "Type": "GrpcGatewayRouteAction", + "UpdateType": "Mutable" + }, + "Match": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayroute.html#cfn-appmesh-gatewayroute-grpcgatewayroute-match", + "Required": true, + "Type": "GrpcGatewayRouteMatch", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.GrpcGatewayRouteAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayrouteaction.html", + "Properties": { + "Target": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayrouteaction.html#cfn-appmesh-gatewayroute-grpcgatewayrouteaction-target", + "Required": true, + "Type": "GatewayRouteTarget", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.GrpcGatewayRouteMatch": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayroutematch.html", + "Properties": { + "ServiceName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-grpcgatewayroutematch.html#cfn-appmesh-gatewayroute-grpcgatewayroutematch-servicename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.HttpGatewayRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayroute.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayroute.html#cfn-appmesh-gatewayroute-httpgatewayroute-action", + "Required": true, + "Type": "HttpGatewayRouteAction", + "UpdateType": "Mutable" + }, + "Match": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayroute.html#cfn-appmesh-gatewayroute-httpgatewayroute-match", + "Required": true, + "Type": "HttpGatewayRouteMatch", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.HttpGatewayRouteAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayrouteaction.html", + "Properties": { + "Target": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayrouteaction.html#cfn-appmesh-gatewayroute-httpgatewayrouteaction-target", + "Required": true, + "Type": "GatewayRouteTarget", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::GatewayRoute.HttpGatewayRouteMatch": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayroutematch.html", + "Properties": { + "Prefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-gatewayroute-httpgatewayroutematch.html#cfn-appmesh-gatewayroute-httpgatewayroutematch-prefix", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::AppMesh::Mesh.EgressFilter": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-egressfilter.html", "Properties": { @@ -2079,6 +2538,309 @@ } } }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayAccessLog": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayaccesslog.html", + "Properties": { + "File": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayaccesslog.html#cfn-appmesh-virtualgateway-virtualgatewayaccesslog-file", + "Required": false, + "Type": "VirtualGatewayFileAccessLog", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayBackendDefaults": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaybackenddefaults.html", + "Properties": { + "ClientPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaybackenddefaults.html#cfn-appmesh-virtualgateway-virtualgatewaybackenddefaults-clientpolicy", + "Required": false, + "Type": "VirtualGatewayClientPolicy", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayClientPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayclientpolicy.html", + "Properties": { + "TLS": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayclientpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayclientpolicy-tls", + "Required": false, + "Type": "VirtualGatewayClientPolicyTls", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayClientPolicyTls": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayclientpolicytls.html", + "Properties": { + "Enforce": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayclientpolicytls.html#cfn-appmesh-virtualgateway-virtualgatewayclientpolicytls-enforce", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Ports": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayclientpolicytls.html#cfn-appmesh-virtualgateway-virtualgatewayclientpolicytls-ports", + "PrimitiveItemType": "Integer", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Validation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayclientpolicytls.html#cfn-appmesh-virtualgateway-virtualgatewayclientpolicytls-validation", + "Required": true, + "Type": "VirtualGatewayTlsValidationContext", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayFileAccessLog": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayfileaccesslog.html", + "Properties": { + "Path": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayfileaccesslog.html#cfn-appmesh-virtualgateway-virtualgatewayfileaccesslog-path", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayHealthCheckPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html", + "Properties": { + "HealthyThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-healthythreshold", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "IntervalMillis": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-intervalmillis", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Path": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-path", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-port", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Protocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-protocol", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TimeoutMillis": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-timeoutmillis", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "UnhealthyThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy.html#cfn-appmesh-virtualgateway-virtualgatewayhealthcheckpolicy-unhealthythreshold", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayListener": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistener.html", + "Properties": { + "HealthCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistener.html#cfn-appmesh-virtualgateway-virtualgatewaylistener-healthcheck", + "Required": false, + "Type": "VirtualGatewayHealthCheckPolicy", + "UpdateType": "Mutable" + }, + "PortMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistener.html#cfn-appmesh-virtualgateway-virtualgatewaylistener-portmapping", + "Required": true, + "Type": "VirtualGatewayPortMapping", + "UpdateType": "Mutable" + }, + "TLS": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistener.html#cfn-appmesh-virtualgateway-virtualgatewaylistener-tls", + "Required": false, + "Type": "VirtualGatewayListenerTls", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayListenerTls": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertls.html", + "Properties": { + "Certificate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertls.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertls-certificate", + "Required": true, + "Type": "VirtualGatewayListenerTlsCertificate", + "UpdateType": "Mutable" + }, + "Mode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertls.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertls-mode", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayListenerTlsAcmCertificate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlsacmcertificate.html", + "Properties": { + "CertificateArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlsacmcertificate.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertlsacmcertificate-certificatearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayListenerTlsCertificate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlscertificate.html", + "Properties": { + "ACM": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlscertificate.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertlscertificate-acm", + "Required": false, + "Type": "VirtualGatewayListenerTlsAcmCertificate", + "UpdateType": "Mutable" + }, + "File": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlscertificate.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertlscertificate-file", + "Required": false, + "Type": "VirtualGatewayListenerTlsFileCertificate", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayListenerTlsFileCertificate": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlsfilecertificate.html", + "Properties": { + "CertificateChain": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlsfilecertificate.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertlsfilecertificate-certificatechain", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "PrivateKey": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylistenertlsfilecertificate.html#cfn-appmesh-virtualgateway-virtualgatewaylistenertlsfilecertificate-privatekey", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayLogging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylogging.html", + "Properties": { + "AccessLog": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaylogging.html#cfn-appmesh-virtualgateway-virtualgatewaylogging-accesslog", + "Required": false, + "Type": "VirtualGatewayAccessLog", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayPortMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayportmapping.html", + "Properties": { + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayportmapping.html#cfn-appmesh-virtualgateway-virtualgatewayportmapping-port", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Protocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayportmapping.html#cfn-appmesh-virtualgateway-virtualgatewayportmapping-protocol", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewaySpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayspec.html", + "Properties": { + "BackendDefaults": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayspec.html#cfn-appmesh-virtualgateway-virtualgatewayspec-backenddefaults", + "Required": false, + "Type": "VirtualGatewayBackendDefaults", + "UpdateType": "Mutable" + }, + "Listeners": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayspec.html#cfn-appmesh-virtualgateway-virtualgatewayspec-listeners", + "ItemType": "VirtualGatewayListener", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewayspec.html#cfn-appmesh-virtualgateway-virtualgatewayspec-logging", + "Required": false, + "Type": "VirtualGatewayLogging", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayTlsValidationContext": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontext.html", + "Properties": { + "Trust": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontext.html#cfn-appmesh-virtualgateway-virtualgatewaytlsvalidationcontext-trust", + "Required": true, + "Type": "VirtualGatewayTlsValidationContextTrust", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayTlsValidationContextAcmTrust": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontextacmtrust.html", + "Properties": { + "CertificateAuthorityArns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontextacmtrust.html#cfn-appmesh-virtualgateway-virtualgatewaytlsvalidationcontextacmtrust-certificateauthorityarns", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayTlsValidationContextFileTrust": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontextfiletrust.html", + "Properties": { + "CertificateChain": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontextfiletrust.html#cfn-appmesh-virtualgateway-virtualgatewaytlsvalidationcontextfiletrust-certificatechain", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualGateway.VirtualGatewayTlsValidationContextTrust": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontexttrust.html", + "Properties": { + "ACM": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontexttrust.html#cfn-appmesh-virtualgateway-virtualgatewaytlsvalidationcontexttrust-acm", + "Required": false, + "Type": "VirtualGatewayTlsValidationContextAcmTrust", + "UpdateType": "Mutable" + }, + "File": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualgateway-virtualgatewaytlsvalidationcontexttrust.html#cfn-appmesh-virtualgateway-virtualgatewaytlsvalidationcontexttrust-file", + "Required": false, + "Type": "VirtualGatewayTlsValidationContextFileTrust", + "UpdateType": "Mutable" + } + } + }, "AWS::AppMesh::VirtualNode.AccessLog": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-accesslog.html", "Properties": { @@ -5370,6 +6132,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "CachePolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-cachepolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "CachedMethods": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-cachedmethods", "PrimitiveItemType": "String", @@ -5397,7 +6165,7 @@ }, "ForwardedValues": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-forwardedvalues", - "Required": true, + "Required": false, "Type": "ForwardedValues", "UpdateType": "Mutable" }, @@ -5420,6 +6188,12 @@ "Required": false, "UpdateType": "Mutable" }, + "OriginRequestPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-originrequestpolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "PathPattern": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-pathpattern", "PrimitiveType": "String", @@ -5552,6 +6326,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "CachePolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-cachepolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "CachedMethods": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-cachedmethods", "PrimitiveItemType": "String", @@ -5579,7 +6359,7 @@ }, "ForwardedValues": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-forwardedvalues", - "Required": true, + "Required": false, "Type": "ForwardedValues", "UpdateType": "Mutable" }, @@ -5602,6 +6382,12 @@ "Required": false, "UpdateType": "Mutable" }, + "OriginRequestPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-originrequestpolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "SmoothStreaming": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-defaultcachebehavior.html#cfn-cloudfront-distribution-defaultcachebehavior-smoothstreaming", "PrimitiveType": "Boolean", @@ -6436,6 +7222,24 @@ } } }, + "AWS::CodeBuild::Project.BatchRestrictions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-batchrestrictions.html", + "Properties": { + "ComputeTypesAllowed": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-batchrestrictions.html#cfn-codebuild-project-batchrestrictions-computetypesallowed", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "MaximumBuildsAllowed": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-batchrestrictions.html#cfn-codebuild-project-batchrestrictions-maximumbuildsallowed", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::CodeBuild::Project.BuildStatusConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-buildstatusconfig.html", "Properties": { @@ -6588,6 +7392,35 @@ } } }, + "AWS::CodeBuild::Project.ProjectBuildBatchConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html", + "Properties": { + "CombineArtifacts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html#cfn-codebuild-project-projectbuildbatchconfig-combineartifacts", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "Restrictions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html#cfn-codebuild-project-projectbuildbatchconfig-restrictions", + "Required": false, + "Type": "BatchRestrictions", + "UpdateType": "Mutable" + }, + "ServiceRole": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html#cfn-codebuild-project-projectbuildbatchconfig-servicerole", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "TimeoutInMins": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectbuildbatchconfig.html#cfn-codebuild-project-projectbuildbatchconfig-timeoutinmins", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::CodeBuild::Project.ProjectCache": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectcache.html", "Properties": { @@ -7328,6 +8161,23 @@ } } }, + "AWS::CodeGuruProfiler::ProfilingGroup.Channel": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codeguruprofiler-profilinggroup-channel.html", + "Properties": { + "channelId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codeguruprofiler-profilinggroup-channel.html#cfn-codeguruprofiler-profilinggroup-channel-channelid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "channelUri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codeguruprofiler-profilinggroup-channel.html#cfn-codeguruprofiler-profilinggroup-channel-channeluri", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::CodePipeline::CustomActionType.ArtifactDetails": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-customactiontype-artifactdetails.html", "Properties": { @@ -12251,7 +13101,6 @@ "Properties": { "Command": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-command", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12265,7 +13114,6 @@ }, "DependsOn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-dependson", - "DuplicatesAllowed": false, "ItemType": "ContainerDependency", "Required": false, "Type": "List", @@ -12279,7 +13127,6 @@ }, "DnsSearchDomains": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-dnssearchdomains", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12287,7 +13134,6 @@ }, "DnsServers": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-dnsservers", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12295,7 +13141,6 @@ }, "DockerLabels": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-dockerlabels", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -12303,7 +13148,6 @@ }, "DockerSecurityOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-dockersecurityoptions", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12311,7 +13155,6 @@ }, "EntryPoint": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-entrypoint", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12333,7 +13176,6 @@ }, "ExtraHosts": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-extrahosts", - "DuplicatesAllowed": false, "ItemType": "HostEntry", "Required": false, "Type": "List", @@ -12449,7 +13291,6 @@ }, "ResourceRequirements": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-resourcerequirements", - "DuplicatesAllowed": false, "ItemType": "ResourceRequirement", "Required": false, "Type": "List", @@ -12457,7 +13298,6 @@ }, "Secrets": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-secrets", - "DuplicatesAllowed": false, "ItemType": "Secret", "Required": false, "Type": "List", @@ -12477,7 +13317,6 @@ }, "SystemControls": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-systemcontrols", - "DuplicatesAllowed": false, "ItemType": "SystemControl", "Required": false, "Type": "List", @@ -12485,7 +13324,6 @@ }, "Ulimits": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-ulimits", - "DuplicatesAllowed": false, "ItemType": "Ulimit", "Required": false, "Type": "List", @@ -12519,13 +13357,13 @@ "Condition": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdependency.html#cfn-ecs-taskdefinition-containerdependency-condition", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "ContainerName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdependency.html#cfn-ecs-taskdefinition-containerdependency-containername", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" } } @@ -12542,7 +13380,7 @@ "HostPath": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-device.html#cfn-ecs-taskdefinition-device-hostpath", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "Permissions": { @@ -12572,7 +13410,6 @@ }, "DriverOpts": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-dockervolumeconfiguration.html#cfn-ecs-taskdefinition-dockervolumeconfiguration-driveropts", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -12580,7 +13417,6 @@ }, "Labels": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-dockervolumeconfiguration.html#cfn-ecs-taskdefinition-dockervolumeconfiguration-labels", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "Map", @@ -12599,16 +13435,14 @@ "Properties": { "Options": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-firelensconfiguration.html#cfn-ecs-taskdefinition-firelensconfiguration-options", - "DuplicatesAllowed": false, - "PrimitiveItemType": "String", "Required": false, - "Type": "Map", + "Type": "Options", "UpdateType": "Immutable" }, "Type": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-firelensconfiguration.html#cfn-ecs-taskdefinition-firelensconfiguration-type", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" } } @@ -12618,9 +13452,8 @@ "Properties": { "Command": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-healthcheck.html#cfn-ecs-taskdefinition-healthcheck-command", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", - "Required": true, + "Required": false, "Type": "List", "UpdateType": "Immutable" }, @@ -12656,13 +13489,13 @@ "Hostname": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-hostentry.html#cfn-ecs-taskdefinition-containerdefinition-hostentry-hostname", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "IpAddress": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-hostentry.html#cfn-ecs-taskdefinition-containerdefinition-hostentry-ipaddress", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" } } @@ -12700,7 +13533,6 @@ "Properties": { "Add": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-kernelcapabilities.html#cfn-ecs-taskdefinition-kernelcapabilities-add", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12708,7 +13540,6 @@ }, "Drop": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-kernelcapabilities.html#cfn-ecs-taskdefinition-kernelcapabilities-drop", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -12744,7 +13575,6 @@ }, "Devices": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-linuxparameters.html#cfn-ecs-taskdefinition-linuxparameters-devices", - "DuplicatesAllowed": false, "ItemType": "Device", "Required": false, "Type": "List", @@ -12776,7 +13606,6 @@ }, "Tmpfs": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-linuxparameters.html#cfn-ecs-taskdefinition-linuxparameters-tmpfs", - "DuplicatesAllowed": false, "ItemType": "Tmpfs", "Required": false, "Type": "List", @@ -12795,15 +13624,12 @@ }, "Options": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-logconfiguration.html#cfn-ecs-taskdefinition-containerdefinition-logconfiguration-options", - "DuplicatesAllowed": false, - "PrimitiveItemType": "String", "Required": false, - "Type": "Map", + "Type": "Options", "UpdateType": "Immutable" }, "SecretOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-logconfiguration.html#cfn-ecs-taskdefinition-logconfiguration-secretoptions", - "DuplicatesAllowed": false, "ItemType": "Secret", "Required": false, "Type": "List", @@ -12834,6 +13660,9 @@ } } }, + "AWS::ECS::TaskDefinition.Options": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-options.html" + }, "AWS::ECS::TaskDefinition.PortMapping": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-portmappings.html", "Properties": { @@ -12933,13 +13762,13 @@ "Namespace": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-systemcontrol.html#cfn-ecs-taskdefinition-systemcontrol-namespace", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "Value": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-systemcontrol.html#cfn-ecs-taskdefinition-systemcontrol-value", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" } } @@ -12972,7 +13801,6 @@ }, "MountOptions": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-tmpfs.html#cfn-ecs-taskdefinition-tmpfs-mountoptions", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": false, "Type": "List", @@ -13241,6 +14069,17 @@ } } }, + "AWS::EFS::FileSystem.BackupPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-backuppolicy.html", + "Properties": { + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-backuppolicy.html#cfn-efs-filesystem-backuppolicy-status", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::EFS::FileSystem.ElasticFileSystemTag": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-efs-filesystem-elasticfilesystemtag.html", "Properties": { @@ -16597,6 +17436,12 @@ "AWS::FSx::FileSystem.LustreConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html", "Properties": { + "AutoImportPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-autoimportpolicy", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "AutomaticBackupRetentionDays": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-fsx-filesystem-lustreconfiguration.html#cfn-fsx-filesystem-lustreconfiguration-automaticbackupretentiondays", "PrimitiveType": "Integer", @@ -19460,7 +20305,7 @@ "Region": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-distributionconfiguration-distribution.html#cfn-imagebuilder-distributionconfiguration-distribution-region", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" } } @@ -19682,6 +20527,18 @@ } } }, + "AWS::IoT::ProvisioningTemplate.Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-provisioningtemplate-tags.html", + "Properties": { + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-provisioningtemplate-tags.html#cfn-iot-provisioningtemplate-tags-tags", + "ItemType": "Json", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::IoT::Thing.AttributePayload": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot-thing-attributepayload.html", "Properties": { @@ -23411,6 +24268,124 @@ } } }, + "AWS::KinesisFirehose::DeliveryStream.HttpEndpointCommonAttribute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointcommonattribute.html", + "Properties": { + "AttributeName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointcommonattribute.html#cfn-kinesisfirehose-deliverystream-httpendpointcommonattribute-attributename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "AttributeValue": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointcommonattribute.html#cfn-kinesisfirehose-deliverystream-httpendpointcommonattribute-attributevalue", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::KinesisFirehose::DeliveryStream.HttpEndpointConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointconfiguration.html", + "Properties": { + "AccessKey": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointconfiguration-accesskey", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointconfiguration-name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Url": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointconfiguration-url", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::KinesisFirehose::DeliveryStream.HttpEndpointDestinationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html", + "Properties": { + "BufferingHints": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-bufferinghints", + "Required": false, + "Type": "BufferingHints", + "UpdateType": "Mutable" + }, + "CloudWatchLoggingOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-cloudwatchloggingoptions", + "Required": false, + "Type": "CloudWatchLoggingOptions", + "UpdateType": "Mutable" + }, + "EndpointConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-endpointconfiguration", + "Required": true, + "Type": "HttpEndpointConfiguration", + "UpdateType": "Mutable" + }, + "ProcessingConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-processingconfiguration", + "Required": false, + "Type": "ProcessingConfiguration", + "UpdateType": "Mutable" + }, + "RequestConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-requestconfiguration", + "Required": false, + "Type": "HttpEndpointRequestConfiguration", + "UpdateType": "Mutable" + }, + "RetryOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-retryoptions", + "Required": false, + "Type": "RetryOptions", + "UpdateType": "Mutable" + }, + "RoleARN": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-rolearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3BackupMode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-s3backupmode", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3Configuration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration-s3configuration", + "Required": true, + "Type": "S3DestinationConfiguration", + "UpdateType": "Mutable" + } + } + }, + "AWS::KinesisFirehose::DeliveryStream.HttpEndpointRequestConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointrequestconfiguration.html", + "Properties": { + "CommonAttributes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointrequestconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointrequestconfiguration-commonattributes", + "DuplicatesAllowed": false, + "ItemType": "HttpEndpointCommonAttribute", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "ContentEncoding": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-httpendpointrequestconfiguration.html#cfn-kinesisfirehose-deliverystream-httpendpointrequestconfiguration-contentencoding", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisFirehose::DeliveryStream.InputFormatConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-inputformatconfiguration.html", "Properties": { @@ -23730,6 +24705,17 @@ } } }, + "AWS::KinesisFirehose::DeliveryStream.RetryOptions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-retryoptions.html", + "Properties": { + "DurationInSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-retryoptions.html#cfn-kinesisfirehose-deliverystream-retryoptions-durationinseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::KinesisFirehose::DeliveryStream.S3DestinationConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-kinesisfirehose-deliverystream-s3destinationconfiguration.html", "Properties": { @@ -30378,6 +31364,53 @@ } } }, + "AWS::SecretsManager::RotationSchedule.HostedRotationLambda": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html", + "Properties": { + "KmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-kmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MasterSecretArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-mastersecretarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MasterSecretKmsKeyArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-mastersecretkmskeyarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "RotationLambdaName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-rotationlambdaname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "RotationType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-rotationtype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "VpcSecurityGroupIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-vpcsecuritygroupids", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "VpcSubnetIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-hostedrotationlambda.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda-vpcsubnetids", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::SecretsManager::RotationSchedule.RotationRules": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-rotationschedule-rotationrules.html", "Properties": { @@ -30732,6 +31765,17 @@ } } }, + "AWS::StepFunctions::StateMachine.TracingConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-tracingconfiguration.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-tracingconfiguration.html#cfn-stepfunctions-statemachine-tracingconfiguration-enabled", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Synthetics::Canary.Code": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-code.html", "Properties": { @@ -32994,7 +34038,7 @@ } } }, - "ResourceSpecificationVersion": "16.1.0", + "ResourceSpecificationVersion": "16.3.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -33198,6 +34242,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html", "Properties": { + "AuthenticationStrategy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html#cfn-amazonmq-broker-authenticationstrategy", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "AutoMinorVersionUpgrade": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html#cfn-amazonmq-broker-autominorversionupgrade", "PrimitiveType": "Boolean", @@ -33246,6 +34296,18 @@ "Required": true, "UpdateType": "Mutable" }, + "LdapMetadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html#cfn-amazonmq-broker-ldapmetadata", + "Required": false, + "Type": "LdapMetadata", + "UpdateType": "Mutable" + }, + "LdapServerMetadata": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html#cfn-amazonmq-broker-ldapservermetadata", + "Required": false, + "Type": "LdapServerMetadata", + "UpdateType": "Mutable" + }, "Logs": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html#cfn-amazonmq-broker-logs", "Required": false, @@ -33911,8 +34973,8 @@ "DomainName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-domainname", "PrimitiveType": "String", - "Required": true, - "UpdateType": "Immutable" + "Required": false, + "UpdateType": "Mutable" }, "EndpointConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-endpointconfiguration", @@ -33934,7 +34996,6 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -34541,6 +35602,35 @@ } } }, + "AWS::ApiGatewayV2::ApiGatewayManagedOverrides": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apigatewaymanagedoverrides.html", + "Properties": { + "ApiId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apigatewaymanagedoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-apiid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Integration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apigatewaymanagedoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-integration", + "Required": false, + "Type": "IntegrationOverrides", + "UpdateType": "Mutable" + }, + "Route": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apigatewaymanagedoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-route", + "Required": false, + "Type": "RouteOverrides", + "UpdateType": "Mutable" + }, + "Stage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apigatewaymanagedoverrides.html#cfn-apigatewayv2-apigatewaymanagedoverrides-stage", + "Required": false, + "Type": "StageOverrides", + "UpdateType": "Mutable" + } + } + }, "AWS::ApiGatewayV2::ApiMapping": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apimapping.html", "Properties": { @@ -35058,6 +36148,37 @@ } } }, + "AWS::ApiGatewayV2::VpcLink": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-vpclink.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-vpclink.html#cfn-apigatewayv2-vpclink-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "SecurityGroupIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-vpclink.html#cfn-apigatewayv2-vpclink-securitygroupids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "SubnetIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-vpclink.html#cfn-apigatewayv2-vpclink-subnetids", + "PrimitiveItemType": "String", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-vpclink.html#cfn-apigatewayv2-vpclink-tags", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::AppConfig::Application": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appconfig-application.html", "Properties": { @@ -35311,6 +36432,71 @@ } } }, + "AWS::AppMesh::GatewayRoute": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "GatewayRouteName": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "MeshOwner": { + "PrimitiveType": "String" + }, + "ResourceOwner": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + }, + "VirtualGatewayName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html", + "Properties": { + "GatewayRouteName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html#cfn-appmesh-gatewayroute-gatewayroutename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html#cfn-appmesh-gatewayroute-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "MeshOwner": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html#cfn-appmesh-gatewayroute-meshowner", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html#cfn-appmesh-gatewayroute-spec", + "Required": true, + "Type": "GatewayRouteSpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html#cfn-appmesh-gatewayroute-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VirtualGatewayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-gatewayroute.html#cfn-appmesh-gatewayroute-virtualgatewayname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::AppMesh::Mesh": { "Attributes": { "Arn": { @@ -35417,6 +36603,62 @@ } } }, + "AWS::AppMesh::VirtualGateway": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "MeshOwner": { + "PrimitiveType": "String" + }, + "ResourceOwner": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + }, + "VirtualGatewayName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualgateway.html", + "Properties": { + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualgateway.html#cfn-appmesh-virtualgateway-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "MeshOwner": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualgateway.html#cfn-appmesh-virtualgateway-meshowner", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualgateway.html#cfn-appmesh-virtualgateway-spec", + "Required": true, + "Type": "VirtualGatewaySpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualgateway.html#cfn-appmesh-virtualgateway-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VirtualGatewayName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualgateway.html#cfn-appmesh-virtualgateway-virtualgatewayname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::AppMesh::VirtualNode": { "Attributes": { "Arn": { @@ -38133,6 +39375,12 @@ "Required": false, "UpdateType": "Mutable" }, + "BuildBatchConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-project.html#cfn-codebuild-project-buildbatchconfig", + "Required": false, + "Type": "ProjectBuildBatchConfig", + "UpdateType": "Mutable" + }, "Cache": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-project.html#cfn-codebuild-project-cache", "Required": false, @@ -38279,7 +39527,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-reportgroup.html#cfn-codebuild-reportgroup-type", "PrimitiveType": "String", "Required": true, - "UpdateType": "Mutable" + "UpdateType": "Immutable" } } }, @@ -38514,6 +39762,13 @@ "Required": false, "UpdateType": "Mutable" }, + "AnomalyDetectionNotificationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html#cfn-codeguruprofiler-profilinggroup-anomalydetectionnotificationconfiguration", + "ItemType": "Channel", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "ComputePlatform": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html#cfn-codeguruprofiler-profilinggroup-computeplatform", "PrimitiveType": "String", @@ -38525,6 +39780,14 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html#cfn-codeguruprofiler-profilinggroup-tags", + "DuplicatesAllowed": false, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, @@ -38777,10 +40040,16 @@ "Required": true, "UpdateType": "Immutable" }, + "HostArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarconnections-connection.html#cfn-codestarconnections-connection-hostarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "ProviderType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarconnections-connection.html#cfn-codestarconnections-connection-providertype", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Immutable" }, "Tags": { @@ -41491,12 +42760,24 @@ "Required": false, "UpdateType": "Immutable" }, + "LogFormat": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-logformat", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "LogGroupName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-loggroupname", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" }, + "MaxAggregationInterval": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-maxaggregationinterval", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, "ResourceId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-resourceid", "PrimitiveType": "String", @@ -41509,6 +42790,14 @@ "Required": true, "UpdateType": "Immutable" }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-tags", + "DuplicatesAllowed": true, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "TrafficType": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-traffictype", "PrimitiveType": "String", @@ -43756,6 +45045,11 @@ } }, "AWS::ECS::TaskDefinition": { + "Attributes": { + "TaskDefinitionArn": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html", "Properties": { "ContainerDefinitions": { @@ -43840,12 +45134,17 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", "UpdateType": "Mutable" }, + "TaskDefinitionStatus": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-taskdefinitionstatus", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "TaskRoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-taskrolearn", "PrimitiveType": "String", @@ -43981,12 +45280,21 @@ }, "AWS::EFS::FileSystem": { "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, "FileSystemId": { "PrimitiveType": "String" } }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html", "Properties": { + "BackupPolicy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-backuppolicy", + "Required": false, + "Type": "BackupPolicy", + "UpdateType": "Mutable" + }, "Encrypted": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html#cfn-efs-filesystem-encrypted", "PrimitiveType": "Boolean", @@ -48707,6 +50015,13 @@ "Required": true, "UpdateType": "Immutable" }, + "ResourceTags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-resourcetags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + }, "SecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-securitygroupids", "PrimitiveItemType": "String", @@ -49013,9 +50328,8 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-tags", - "ItemType": "Json", "Required": false, - "Type": "List", + "Type": "Tags", "UpdateType": "Mutable" }, "TemplateBody": { @@ -49655,6 +50969,12 @@ "Type": "ExtendedS3DestinationConfiguration", "UpdateType": "Mutable" }, + "HttpEndpointDestinationConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisfirehose-deliverystream.html#cfn-kinesisfirehose-deliverystream-httpendpointdestinationconfiguration", + "Required": false, + "Type": "HttpEndpointDestinationConfiguration", + "UpdateType": "Mutable" + }, "KinesisStreamSourceConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisfirehose-deliverystream.html#cfn-kinesisfirehose-deliverystream-kinesisstreamsourceconfiguration", "Required": false, @@ -56554,6 +57874,12 @@ "AWS::SecretsManager::RotationSchedule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-rotationschedule.html", "Properties": { + "HostedRotationLambda": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-rotationschedule.html#cfn-secretsmanager-rotationschedule-hostedrotationlambda", + "Required": false, + "Type": "HostedRotationLambda", + "UpdateType": "Mutable" + }, "RotationLambdaARN": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-rotationschedule.html#cfn-secretsmanager-rotationschedule-rotationlambdaarn", "PrimitiveType": "String", @@ -57474,6 +58800,12 @@ "Required": false, "Type": "List", "UpdateType": "Mutable" + }, + "TracingConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html#cfn-stepfunctions-statemachine-tracingconfiguration", + "Required": false, + "Type": "TracingConfiguration", + "UpdateType": "Mutable" } } }, diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json b/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json index 40aeb5f55049b..6e8c93f83f210 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json @@ -410,6 +410,23 @@ } } }, + "AWS::Serverless::Function.FileSystemConfig": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-filesystemconfig.html#cfn-lambda-function-filesystemconfig-localmountpath", + "Properties": { + "Arn": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-filesystemconfig.html#cfn-lambda-function-filesystemconfig-localmountpath", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "LocalMountPath": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-filesystemconfig.html#cfn-lambda-function-filesystemconfig-localmountpath", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::Serverless::Function.FunctionEnvironment": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object", "Properties": { @@ -971,6 +988,243 @@ "UpdateType": "Immutable" } } + }, + "AWS::Serverless::StateMachine.ApiEvent": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api", + "Properties": { + "Method": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Path": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RestApiId": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.CloudWatchEventEvent": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "Properties": { + "EventBusName": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Input": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "InputPath": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Pattern": { + "Documentation": "http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.CloudWatchLogsLogGroup": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-logdestination-cloudwatchlogsloggroup.html", + "Properties": { + "LogGroupArn": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-logdestination-cloudwatchlogsloggroup.html", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.EventBridgeRuleEvent": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "Properties": { + "EventBusName": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Input": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "InputPath": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-cloudwatchevent.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Pattern": { + "Documentation": "http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.EventSource": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-object", + "Properties": { + "Properties": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-types", + "Required": true, + "Types": [ + "CloudWatchEventEvent", + "EventBridgeRuleEvent", + "ScheduleEvent", + "ApiEvent" + ], + "UpdateType": "Immutable" + }, + "Type": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-object", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.FunctionSAMPT": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "Properties": { + "FunctionName": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.IAMPolicyDocument": { + "Documentation": "http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html", + "Properties": { + "Statement": { + "Documentation": "http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html", + "PrimitiveType": "Json", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.LogDestination": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-logdestination.html#cfn-stepfunctions-statemachine-logdestination-cloudwatchlogsloggroup", + "Properties": { + "CloudWatchLogsLogGroup": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-logdestination.html#cfn-stepfunctions-statemachine-logdestination-cloudwatchlogsloggroup", + "Required": true, + "Type": "CloudWatchLogsLogGroup", + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.LoggingConfiguration": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-loggingconfiguration.html", + "Properties": { + "Destinations": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-loggingconfiguration.html", + "ItemType": "LogDestination", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "IncludeExecutionData": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-loggingconfiguration.html", + "PrimitiveType": "Boolean", + "Required": true, + "UpdateType": "Immutable" + }, + "Level": { + "Documentation": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-loggingconfiguration.html", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.S3Location": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3-location-object", + "Properties": { + "Bucket": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Key": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Version": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.SAMPolicyTemplate": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "Properties": { + "LambdaInvokePolicy": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "Type": "FunctionSAMPT", + "UpdateType": "Immutable" + }, + "StepFunctionsExecutionPolicy": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "Type": "StateMachineSAMPT", + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.ScheduleEvent": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule", + "Properties": { + "Input": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Schedule": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Serverless::StateMachine.StateMachineSAMPT": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "Properties": { + "StateMachineName": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/docs/policy_templates.rst", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } } }, "ResourceSpecificationTransform": "AWS::Serverless-2016-10-31", @@ -1178,6 +1432,13 @@ "Type": "Map", "UpdateType": "Immutable" }, + "FileSystemConfigs": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html", + "ItemType": "FileSystemConfig", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, "FunctionName": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction", "PrimitiveType": "String", @@ -1355,6 +1616,91 @@ "UpdateType": "Immutable" } } + }, + "AWS::Serverless::StateMachine": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "Properties": { + "Definition": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, + "DefinitionSubstitutions": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + }, + "DefinitionUri": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveTypes": [ + "String" + ], + "Required": false, + "Types": [ + "S3Location" + ], + "UpdateType": "Immutable" + }, + "Events": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "ItemType": "EventSource", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + }, + "Logging": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "Required": false, + "Type": "LoggingConfiguration", + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Policies": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "ItemTypes": [ + "IAMPolicyDocument", + "SAMPolicyTemplate" + ], + "PrimitiveItemTypes": [ + "String" + ], + "PrimitiveTypes": [ + "String" + ], + "Required": false, + "Types": [ + "IAMPolicyDocument" + ], + "UpdateType": "Immutable" + }, + "Role": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + }, + "Type": { + "Documentation": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } } } } diff --git a/packages/@aws-cdk/cfnspec/spec-source/500_ECS_Options_Types_patch.json b/packages/@aws-cdk/cfnspec/spec-source/500_ECS_Options_Types_patch.json new file mode 100644 index 0000000000000..b72b943a5c388 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/500_ECS_Options_Types_patch.json @@ -0,0 +1,35 @@ +{ + "PropertyTypes": { + "patch": { + "description": "Remove AWS::ECS::TaskDefinition.Options, replace back with Map", + "operations": [ + { + "op": "remove", + "path": "/AWS::ECS::TaskDefinition.Options" + }, + { + "op": "replace", + "path": "/AWS::ECS::TaskDefinition.FirelensConfiguration/Properties/Options", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-firelensconfiguration.html#cfn-ecs-taskdefinition-firelensconfiguration-options", + "Required": false, + "Type": "Map", + "PrimitiveItemType": "String", + "UpdateType": "Immutable" + } + }, + { + "op": "replace", + "path": "/AWS::ECS::TaskDefinition.LogConfiguration/Properties/Options", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-logconfiguration.html#cfn-ecs-taskdefinition-containerdefinition-logconfiguration-options", + "Required": false, + "Type": "Map", + "PrimitiveItemType": "String", + "UpdateType": "Immutable" + } + } + ] + } + } +} diff --git a/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json b/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json index d8e3332662ebd..4282477502f05 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json +++ b/packages/@aws-cdk/cfnspec/spec-source/500_IoT_ProvisioningTemplate_Tags_CorrectItemType_patch.json @@ -1,8 +1,8 @@ { - "ResourceTypes": { - "AWS::IoT::ProvisioningTemplate": { + "PropertyTypes": { + "AWS::IoT::ProvisioningTemplate.Tags": { "patch": { - "description": "AWS::IoT::ProvisioningTemplate.Properties.Tag.ItemType should have been PrimitiveItemType", + "description": "AWS::IoT::ProvisioningTemplate.Tag.ItemType should have been PrimitiveItemType", "operations": [ { "op": "remove", From cb6de0adaec9e9942c7568939b33d7cb29cdeef2 Mon Sep 17 00:00:00 2001 From: comcalvi <66279577+comcalvi@users.noreply.github.com> Date: Thu, 13 Aug 2020 14:47:32 -0400 Subject: [PATCH 014/422] feat(cfn-include): allow passing Parameters to the included template (#9543) ---- Closes #4994 Cfn-Include can now be passed a mapping of parameters and their values. Specified parameters will have all references to them replaced with the passed value at build time, and their definitions will be removed from the template. Unspecified parameters and references to them will not be modified. *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/cloudformation-include/README.md | 14 ++ .../cloudformation-include/lib/cfn-include.ts | 67 +++++++-- .../bucket-with-parameters.json | 2 +- .../test-templates/fn-sub-parameters.json | 19 +++ .../fn-sub-shadow-parameter.json | 22 +++ .../test-templates/parameter-references.json | 54 ++++++++ .../test/valid-templates.test.ts | 130 +++++++++++++++++- packages/@aws-cdk/core/lib/cfn-parse.ts | 91 +++++++++--- packages/@aws-cdk/core/lib/from-cfn.ts | 45 ------ packages/@aws-cdk/core/lib/index.ts | 1 - tools/cfn2ts/lib/codegen.ts | 11 +- 11 files changed, 375 insertions(+), 81 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-parameters.json create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-shadow-parameter.json create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/parameter-references.json delete mode 100644 packages/@aws-cdk/core/lib/from-cfn.ts diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 0e51dc0402f16..180f7b7ea0ecc 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -131,6 +131,20 @@ and any changes you make to it will be reflected in the resulting template: param.default = 'MyDefault'; ``` +You can also provide values for them when including the template: + +```typescript +new inc.CfnInclude(stack, 'includeTemplate', { + templateFile: 'path/to/my/template' + parameters: { + 'MyParam': 'my-value', + }, +}); +``` + +This will replace all references to `MyParam` with the string 'my-value', +and `MyParam` will be removed from the Parameters section of the template. + ## Conditions If your template uses [CloudFormation Conditions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html), diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 157a0ebaf6ccf..584a5f97b420b 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -23,8 +23,20 @@ export interface CfnIncludeProps { * If you include a stack here with an ID that isn't in the template, * or is in the template but is not a nested stack, * template creation will fail and an error will be thrown. + * + * @default - no nested stacks will be included */ readonly nestedStacks?: { [stackName: string]: CfnIncludeProps }; + + /** + * Specifies parameters to be replaced by the values in this mapping. + * Any parameters in the template that aren't specified here will be left unmodified. + * If you include a parameter here with an ID that isn't in the template, + * template creation will fail and an error will be thrown. + * + * @default - no parameters will be replaced + */ + readonly parameters?: { [parameterName: string]: any }; } /** @@ -55,6 +67,7 @@ export class CfnInclude extends core.CfnElement { private readonly conditionsScope: core.Construct; private readonly resources: { [logicalId: string]: core.CfnResource } = {}; private readonly parameters: { [logicalId: string]: core.CfnParameter } = {}; + private readonly parametersToReplace: { [parameterName: string]: any }; private readonly outputs: { [logicalId: string]: core.CfnOutput } = {}; private readonly nestedStacks: { [logicalId: string]: IncludedNestedStack } = {}; private readonly nestedStacksToInclude: { [name: string]: CfnIncludeProps }; @@ -64,12 +77,21 @@ export class CfnInclude extends core.CfnElement { constructor(scope: core.Construct, id: string, props: CfnIncludeProps) { super(scope, id); + this.parametersToReplace = props.parameters || {}; + // read the template into a JS object this.template = futils.readYamlSync(props.templateFile); // ToDo implement preserveLogicalIds=false this.preserveLogicalIds = true; + // check if all user specified parameter values exist in the template + for (const logicalId of Object.keys(this.parametersToReplace)) { + if (!(logicalId in (this.template.Parameters || {}))) { + throw new Error(`Parameter with logical ID '${logicalId}' was not found in the template`); + } + } + // instantiate all parameters for (const logicalId of Object.keys(this.template.Parameters || {})) { this.createParameter(logicalId); @@ -203,10 +225,32 @@ export class CfnInclude extends core.CfnElement { const ret: { [section: string]: any } = {}; for (const section of Object.keys(this.template)) { - // render all sections of the template unchanged, - // except Conditions, Resources, Parameters, and Outputs which will be taken care of by the created L1s - if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters' && section !== 'Outputs') { - ret[section] = this.template[section]; + const self = this; + const finder: cfn_parse.ICfnFinder = { + findResource(lId): core.CfnResource | undefined { + return self.resources[lId]; + }, + findRefTarget(elementName: string): core.CfnElement | undefined { + return self.resources[elementName] ?? self.parameters[elementName]; + }, + findCondition(conditionName: string): core.CfnCondition | undefined { + return self.conditions[conditionName]; + }, + }; + const cfnParser = new cfn_parse.CfnParser({ + finder, + parameters: this.parametersToReplace, + }); + + switch (section) { + case 'Conditions': + case 'Resources': + case 'Parameters': + case 'Outputs': + // these are rendered as a side effect of instantiating the L1s + break; + default: + ret[section] = cfnParser.parseValue(this.template[section]); } } @@ -214,6 +258,10 @@ export class CfnInclude extends core.CfnElement { } private createParameter(logicalId: string): void { + if (logicalId in this.parametersToReplace) { + return; + } + const expression = new cfn_parse.CfnParser({ finder: { findResource() { throw new Error('Using GetAtt expressions in Parameter definitions is not allowed'); }, @@ -253,6 +301,7 @@ export class CfnInclude extends core.CfnElement { return undefined; }, }, + parameters: this.parametersToReplace, }).parseValue(this.template.Outputs[logicalId]); const cfnOutput = new core.CfnOutput(scope, logicalId, { value: outputAttributes.Value, @@ -294,6 +343,7 @@ export class CfnInclude extends core.CfnElement { }, }, context: cfn_parse.CfnParsingContext.CONDITIONS, + parameters: this.parametersToReplace, }); const cfnCondition = new core.CfnCondition(this.conditionsScope, conditionName, { expression: cfnParser.parseValue(this.template.Conditions[conditionName]), @@ -326,7 +376,7 @@ export class CfnInclude extends core.CfnElement { } const self = this; - const finder: core.ICfnFinder = { + const finder: cfn_parse.ICfnFinder = { findCondition(conditionName: string): core.CfnCondition | undefined { return self.conditions[conditionName]; }, @@ -348,6 +398,7 @@ export class CfnInclude extends core.CfnElement { }; const cfnParser = new cfn_parse.CfnParser({ finder, + parameters: this.parametersToReplace, }); let l1Instance: core.CfnResource; @@ -356,13 +407,13 @@ export class CfnInclude extends core.CfnElement { } else { const l1ClassFqn = cfn_type_to_l1_mapping.lookup(resourceAttributes.Type); if (l1ClassFqn) { - const options: core.FromCloudFormationOptions = { - finder, + const options: cfn_parse.FromCloudFormationOptions = { + parser: cfnParser, }; const [moduleName, ...className] = l1ClassFqn.split('.'); const module = require(moduleName); // eslint-disable-line @typescript-eslint/no-require-imports const jsClassFromModule = module[className.join('.')]; - l1Instance = jsClassFromModule.fromCloudFormation(this, logicalId, resourceAttributes, options); + l1Instance = jsClassFromModule._fromCloudFormation(this, logicalId, resourceAttributes, options); } else { l1Instance = new core.CfnResource(this, logicalId, { type: resourceAttributes.Type, diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json index 0cf4552cb2951..5268e6495b074 100644 --- a/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/bucket-with-parameters.json @@ -45,4 +45,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-parameters.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-parameters.json new file mode 100644 index 0000000000000..d1bfe825b2514 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-parameters.json @@ -0,0 +1,19 @@ +{ + "Parameters": { + "MyParam": { + "Type": "String" + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Sub": [ + "${MyParam}" + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-shadow-parameter.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-shadow-parameter.json new file mode 100644 index 0000000000000..1e47c582ba875 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-shadow-parameter.json @@ -0,0 +1,22 @@ +{ + "Parameters": { + "MyParam": { + "Type": "String" + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Sub": [ + "${MyParam}", + { + "MyParam": { "Ref" : "MyParam" } + } + ] + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/parameter-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/parameter-references.json new file mode 100644 index 0000000000000..2da6e28215241 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/parameter-references.json @@ -0,0 +1,54 @@ +{ + "Transform" : { + "Name" : "AWS::Include", + "Parameters" : { + "Location" : { + "Ref": "MyParam" + } + } + }, + "Parameters": { + "MyParam": { + "Type": "String", + "Default": "MyValue" + } + }, + "Conditions": { + "AlwaysFalse": { + "Fn::Equals": [ { "Ref": "MyParam" }, "Invalid?BucketName"] + } + }, + "Metadata": { + "Field": { + "Fn::If": [ + "AlwaysFalse", + "AWS::NoValue", + { + "Ref": "MyParam" + } + ] + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Metadata": { + "Field": { + "Ref": "MyParam" + } + }, + "Properties": { + "BucketName": { + "Ref": "MyParam" + } + } + } + }, + "Outputs": { + "MyOutput": { + "Value": { + "Ref": "MyParam" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 845aa001ba3d2..fec9ebf7f368c 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -657,16 +657,144 @@ describe('CDK Include', () => { cfnTemplate.getOutput('FakeOutput'); }).toThrow(/Output with logical ID 'FakeOutput' was not found in the template/); }); + + test('replaces references to parameters with the user-specified values in Resources, Conditions, Metadata, and Options', () => { + includeTestTemplate(stack, 'parameter-references.json', { + parameters: { + 'MyParam': 'my-s3-bucket', + }, + }); + + expect(stack).toMatchTemplate({ + "Transform": { + "Name": "AWS::Include", + "Parameters": { + "Location": "my-s3-bucket", + }, + }, + "Metadata": { + "Field": { + "Fn::If": [ + "AlwaysFalse", + "AWS::NoValue", + "my-s3-bucket", + ], + }, + }, + "Conditions": { + "AlwaysFalse": { + "Fn::Equals": [ "my-s3-bucket", "Invalid?BucketName"], + }, + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Metadata": { + "Field": "my-s3-bucket", + }, + "Properties": { + "BucketName": "my-s3-bucket", + }, + }, + }, + "Outputs": { + "MyOutput": { + "Value": "my-s3-bucket", + }, + }, + }); + }); + + test('can replace parameters in Fn::Sub', () => { + includeTestTemplate(stack, 'fn-sub-parameters.json', { + parameters: { + 'MyParam': 'my-s3-bucket', + }, + }); + + expect(stack).toMatchTemplate({ + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Sub": "my-s3-bucket", + }, + }, + }, + }, + }); + }); + + test('does not modify Fn::Sub variables shadowing a replaced parameter', () => { + includeTestTemplate(stack, 'fn-sub-shadow-parameter.json', { + parameters: { + 'MyParam': 'MyValue', + }, + }); + + expect(stack).toMatchTemplate({ + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": { + "Fn::Sub": [ + "${MyParam}", + { + "MyParam": "MyValue", + }, + ], + }, + }, + }, + }, + }); + }); + + test('throws an exception when parameters are passed a resource name', () => { + expect(() => { + includeTestTemplate(stack, 'bucket-with-parameters.json', { + parameters: { + 'Bucket': 'noChange', + }, + }); + }).toThrow(/Parameter with logical ID 'Bucket' was not found in the template/); + }); + + test('throws an exception when provided a parameter to replace that is not in the template with parameters', () => { + expect(() => { + includeTestTemplate(stack, 'bucket-with-parameters.json', { + parameters: { + 'FakeParameter': 'DoesNotExist', + }, + }); + }).toThrow(/Parameter with logical ID 'FakeParameter' was not found in the template/); + }); + + test('throws an exception when provided a parameter to replace in a template with no parameters', () => { + expect(() => { + includeTestTemplate(stack, 'only-empty-bucket.json', { + parameters: { + 'FakeParameter': 'DoesNotExist', + }, + }); + }).toThrow(/Parameter with logical ID 'FakeParameter' was not found in the template/); + }); }); interface IncludeTestTemplateProps { /** @default true */ readonly preserveLogicalIds?: boolean; + + /** @default {} */ + readonly parameters?: { [parameterName: string]: any } } -function includeTestTemplate(scope: core.Construct, testTemplate: string, _props: IncludeTestTemplateProps = {}): inc.CfnInclude { +function includeTestTemplate(scope: core.Construct, testTemplate: string, props: IncludeTestTemplateProps = {}): inc.CfnInclude { return new inc.CfnInclude(scope, 'MyScope', { templateFile: _testTemplateFilePath(testTemplate), + parameters: props.parameters, // preserveLogicalIds: props.preserveLogicalIds, }); } diff --git a/packages/@aws-cdk/core/lib/cfn-parse.ts b/packages/@aws-cdk/core/lib/cfn-parse.ts index 5872399e0efee..13662e4633244 100644 --- a/packages/@aws-cdk/core/lib/cfn-parse.ts +++ b/packages/@aws-cdk/core/lib/cfn-parse.ts @@ -1,3 +1,5 @@ +import { CfnCondition } from './cfn-condition'; +import { CfnElement } from './cfn-element'; import { Fn } from './cfn-fn'; import { Aws } from './cfn-pseudo'; import { CfnResource } from './cfn-resource'; @@ -6,7 +8,6 @@ import { CfnCreationPolicy, CfnDeletionPolicy, CfnResourceAutoScalingCreationPolicy, CfnResourceSignal, CfnUpdatePolicy, } from './cfn-resource-policy'; import { CfnTag } from './cfn-tag'; -import { ICfnFinder } from './from-cfn'; import { Lazy } from './lazy'; import { CfnReference } from './private/cfn-reference'; import { IResolvable } from './resolvable'; @@ -142,6 +143,44 @@ export class FromCloudFormation { } } +/** + * An interface that represents callbacks into a CloudFormation template. + * Used by the fromCloudFormation methods in the generated L1 classes. + */ +export interface ICfnFinder { + /** + * Return the Condition with the given name from the template. + * If there is no Condition with that name in the template, + * returns undefined. + */ + findCondition(conditionName: string): CfnCondition | undefined; + + /** + * Returns the element referenced using a Ref expression with the given name. + * If there is no element with this name in the template, + * return undefined. + */ + findRefTarget(elementName: string): CfnElement | undefined; + + /** + * Returns the resource with the given logical ID in the template. + * If a resource with that logical ID was not found in the template, + * returns undefined. + */ + findResource(logicalId: string): CfnResource | undefined; +} + +/** + * The interface used as the last argument to the fromCloudFormation + * static method of the generated L1 classes. + */ +export interface FromCloudFormationOptions { + /** + * The parser used to convert CloudFormation to values the CDK understands. + */ + readonly parser: CfnParser; +} + /** * The context in which the parsing is taking place. * @@ -171,6 +210,12 @@ export interface ParseCfnOptions { * @default - the default context (no special behavior) */ readonly context?: CfnParsingContext; + + /** + * Values provided here will replace references to parameters in the parsed template. + * @default - no parameters will be replaced + */ + readonly parameters?: { [parameterName: string]: any } } /** @@ -357,7 +402,7 @@ export class CfnParser { return undefined; case 'Ref': { const refTarget = object[key]; - const specialRef = specialCaseRefs(refTarget); + const specialRef = this.specialCaseRefs(refTarget); if (specialRef) { return specialRef; } else { @@ -509,7 +554,7 @@ export class CfnParser { } // since it's not in the map, check if it's a pseudo parameter - const specialRef = specialCaseSubRefs(refTarget); + const specialRef = this.specialCaseSubRefs(refTarget); if (specialRef) { return leftHalf + specialRef + this.parseFnSubString(rightHalf, map); } @@ -532,24 +577,34 @@ export class CfnParser { return leftHalf + CfnReference.for(refResource, attribute, true).toString() + this.parseFnSubString(rightHalf, map); } } -} -function specialCaseRefs(value: any): any { - switch (value) { - case 'AWS::AccountId': return Aws.ACCOUNT_ID; - case 'AWS::Region': return Aws.REGION; - case 'AWS::Partition': return Aws.PARTITION; - case 'AWS::URLSuffix': return Aws.URL_SUFFIX; - case 'AWS::NotificationARNs': return Aws.NOTIFICATION_ARNS; - case 'AWS::StackId': return Aws.STACK_ID; - case 'AWS::StackName': return Aws.STACK_NAME; - case 'AWS::NoValue': return Aws.NO_VALUE; - default: return undefined; + private specialCaseRefs(value: any): any { + if (value in this.parameters) { + return this.parameters[value]; + } + switch (value) { + case 'AWS::AccountId': return Aws.ACCOUNT_ID; + case 'AWS::Region': return Aws.REGION; + case 'AWS::Partition': return Aws.PARTITION; + case 'AWS::URLSuffix': return Aws.URL_SUFFIX; + case 'AWS::NotificationARNs': return Aws.NOTIFICATION_ARNS; + case 'AWS::StackId': return Aws.STACK_ID; + case 'AWS::StackName': return Aws.STACK_NAME; + case 'AWS::NoValue': return Aws.NO_VALUE; + default: return undefined; + } } -} -function specialCaseSubRefs(value: string): string | undefined { - return value.indexOf('::') === -1 ? undefined: '${' + value + '}'; + private specialCaseSubRefs(value: string): string | undefined { + if (value in this.parameters) { + return this.parameters[value]; + } + return value.indexOf('::') === -1 ? undefined: '${' + value + '}'; + } + + private get parameters(): { [parameterName: string]: any } { + return this.options.parameters || {}; + } } function undefinedIfAllValuesAreEmpty(object: object): object | undefined { diff --git a/packages/@aws-cdk/core/lib/from-cfn.ts b/packages/@aws-cdk/core/lib/from-cfn.ts deleted file mode 100644 index 771af7775a74b..0000000000000 --- a/packages/@aws-cdk/core/lib/from-cfn.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { CfnCondition } from './cfn-condition'; -import { CfnElement } from './cfn-element'; -import { CfnResource } from './cfn-resource'; - -/** - * An interface that represents callbacks into a CloudFormation template. - * Used by the fromCloudFormation methods in the generated L1 classes. - * - * @experimental - */ -export interface ICfnFinder { - /** - * Return the Condition with the given name from the template. - * If there is no Condition with that name in the template, - * returns undefined. - */ - findCondition(conditionName: string): CfnCondition | undefined; - - /** - * Returns the element referenced using a Ref expression with the given name. - * If there is no element with this name in the template, - * return undefined. - */ - findRefTarget(elementName: string): CfnElement | undefined; - - /** - * Returns the resource with the given logical ID in the template. - * If a resource with that logical ID was not found in the template, - * returns undefined. - */ - findResource(logicalId: string): CfnResource | undefined; -} - -/** - * The interface used as the last argument to the fromCloudFormation - * static method of the generated L1 classes. - * - * @experimental - */ -export interface FromCloudFormationOptions { - /** - * The finder interface used to resolve references across the template. - */ - readonly finder: ICfnFinder; -} diff --git a/packages/@aws-cdk/core/lib/index.ts b/packages/@aws-cdk/core/lib/index.ts index 6c54a222901d6..92a6ae2f825ed 100644 --- a/packages/@aws-cdk/core/lib/index.ts +++ b/packages/@aws-cdk/core/lib/index.ts @@ -30,7 +30,6 @@ export * from './cfn-json'; export * from './removal-policy'; export * from './arn'; export * from './duration'; -export * from './from-cfn'; export * from './size'; export * from './stack-trace'; diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 6624053395dc2..264f4c87c26b2 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -230,18 +230,15 @@ export default class CodeGenerator { this.code.line(' * containing the CloudFormation properties of this resource.'); this.code.line(' * Used in the @aws-cdk/cloudformation-include module.'); this.code.line(' *'); - this.code.line(' * @experimental'); + this.code.line(' * @internal'); this.code.line(' */'); // eslint-disable-next-line max-len - this.code.openBlock(`public static fromCloudFormation(scope: ${CONSTRUCT_CLASS}, id: string, resourceAttributes: any, options: ${CORE}.FromCloudFormationOptions): ` + + this.code.openBlock(`public static _fromCloudFormation(scope: ${CONSTRUCT_CLASS}, id: string, resourceAttributes: any, options: ${CFN_PARSE}.FromCloudFormationOptions): ` + `${resourceName.className}`); this.code.line('resourceAttributes = resourceAttributes || {};'); - this.code.indent('const cfnParser = new cfn_parse.CfnParser({'); - this.code.line('finder: options.finder,'); - this.code.unindent('});'); if (propsType) { // translate the template properties to CDK objects - this.code.line('const resourceProperties = cfnParser.parseValue(resourceAttributes.Properties);'); + this.code.line('const resourceProperties = options.parser.parseValue(resourceAttributes.Properties);'); // translate to props, using a (module-private) factory function this.code.line(`const props = ${genspec.fromCfnFactoryName(propsType).fqn}(resourceProperties);`); // finally, instantiate the resource class @@ -252,7 +249,7 @@ export default class CodeGenerator { } // handle all non-property attributes // (retention policies, conditions, metadata, etc.) - this.code.line('cfnParser.handleAttributes(ret, resourceAttributes, id);'); + this.code.line('options.parser.handleAttributes(ret, resourceAttributes, id);'); this.code.line('return ret;'); this.code.closeBlock(); From 43214b4059aa7af40389d5d762c387d8e6093959 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Thu, 13 Aug 2020 15:39:50 -0700 Subject: [PATCH 015/422] fix(codepipeline): S3 source Action with trigger=Events fails for bucketKey a Token (#9575) We use bucketKey to differentiate between multiple source actions that observe the same bucket using trigger=Events. However, we can't do that if bucketKey is a lazy value, as Tokens can't be used as parts of identifier for the created Event. So, check for that case explicitly. Fixes #9554 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/s3/source-action.ts | 37 ++++++++++++++--- .../test/s3/test.s3-source-action.ts | 41 ++++++++++++++++++- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts index 7608aa3d94ae0..6e867bcaad6c5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -1,7 +1,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as targets from '@aws-cdk/aws-events-targets'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct } from '@aws-cdk/core'; +import { Construct, Token } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; @@ -110,11 +110,7 @@ export class S3SourceAction extends Action { protected bound(_scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { if (this.props.trigger === S3Trigger.EVENTS) { - const id = stage.pipeline.node.uniqueId + 'SourceEventRule' + this.props.bucketKey; - if (this.props.bucket.node.tryFindChild(id)) { - // this means a duplicate path for the same bucket - error out - throw new Error(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`); - } + const id = this.generateEventId(stage); this.props.bucket.onCloudTrailWriteObject(id, { target: new targets.CodePipeline(stage.pipeline), paths: [this.props.bucketKey], @@ -135,4 +131,33 @@ export class S3SourceAction extends Action { }, }; } + + private generateEventId(stage: codepipeline.IStage): string { + let ret: string; + const baseId = stage.pipeline.node.uniqueId + 'SourceEventRule'; + + if (Token.isUnresolved(this.props.bucketKey)) { + // If bucketKey is a Token, don't include it in the ID. + // Instead, use numbers to differentiate if multiple actions observe the same bucket + let candidate = baseId; + let counter = 0; + while (this.props.bucket.node.tryFindChild(candidate) !== undefined) { + counter += 1; + candidate = baseId + counter; + } + ret = candidate; + } else { + // we can't use Tokens in construct IDs, + // however, if bucketKey is not a Token, + // we want it to differentiate between multiple actions + // observing the same Bucket with different keys + ret = baseId + this.props.bucketKey; + if (this.props.bucket.node.tryFindChild(ret)) { + // this means a duplicate path for the same bucket - error out + throw new Error(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`); + } + } + + return ret; + } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts index 6c927ed36d1cb..6b9dc716961e5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts @@ -2,7 +2,7 @@ import { countResources, expect, haveResourceLike, not } from '@aws-cdk/assert'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as s3 from '@aws-cdk/aws-s3'; -import { Stack } from '@aws-cdk/core'; +import { Lazy, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; @@ -176,6 +176,45 @@ export = { test.done(); }, + 'allows using a Token bucketKey with trigger = Events, multiple times'(test: Test) { + const stack = new Stack(); + + const bucket = new s3.Bucket(stack, 'MyBucket'); + const sourceStage = minimalPipeline(stack, { + bucket, + bucketKey: Lazy.stringValue({ produce: () => 'my-bucket-key1' }), + trigger: cpactions.S3Trigger.EVENTS, + }); + sourceStage.addAction(new cpactions.S3SourceAction({ + actionName: 'Source2', + bucket, + bucketKey: Lazy.stringValue({ produce: () => 'my-bucket-key2' }), + trigger: cpactions.S3Trigger.EVENTS, + output: new codepipeline.Artifact(), + })); + + expect(stack, /* skipValidation = */ true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + { + 'Actions': [ + { + 'Configuration': { + 'S3ObjectKey': 'my-bucket-key1', + }, + }, + { + 'Configuration': { + 'S3ObjectKey': 'my-bucket-key2', + }, + }, + ], + }, + ], + })); + + test.done(); + }, + 'exposes variables for other actions to consume'(test: Test) { const stack = new Stack(); From 4bc8188f38544f8e873728d908583ca8afe1714e Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Fri, 14 Aug 2020 03:29:40 +0300 Subject: [PATCH 016/422] feat(eks): ability to query runtime information from the cluster (#9535) Introduce a `KubernetesResourceAttribute` construct that executes `kubectl get` commands to fetch runtime information on kubernetes resources. Resolves #8394 BREAKING CHANGE: `cluster.addResource` was renamed to `cluster.addManifest` and `KubernetesResource` was renamed to `KubernetesManifest` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 63 ++- packages/@aws-cdk/aws-eks/lib/aws-auth.ts | 4 +- .../aws-eks/lib/cluster-resource-provider.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 59 ++- packages/@aws-cdk/aws-eks/lib/index.ts | 3 +- .../lib/{k8s-resource.ts => k8s-manifest.ts} | 30 +- .../@aws-cdk/aws-eks/lib/k8s-object-value.ts | 87 +++ .../lib/kubectl-handler/get/__init__.py | 84 +++ .../aws-eks/lib/kubectl-handler/index.py | 4 + .../@aws-cdk/aws-eks/lib/kubectl-layer.ts | 2 +- .../@aws-cdk/aws-eks/lib/service-account.ts | 2 +- ...eks-cluster-private-endpoint.expected.json | 76 +-- .../integ.eks-cluster-private-endpoint.ts | 2 +- .../test/integ.eks-cluster.expected.json | 500 ++++++++++++++++-- .../aws-eks/test/integ.eks-cluster.ts | 216 ++++++-- .../aws-eks/test/pinger/function/index.py | 24 + .../@aws-cdk/aws-eks/test/pinger/pinger.ts | 43 ++ .../@aws-cdk/aws-eks/test/test.awsauth.ts | 12 +- .../@aws-cdk/aws-eks/test/test.cluster.ts | 82 ++- ...{test.manifest.ts => test.k8s-manifest.ts} | 6 +- .../aws-eks/test/test.k8s-object-value.ts | 93 ++++ .../aws-eks/test/test.legacy-cluster.ts | 6 +- .../@aws-cdk/aws-eks/test/test.nodegroup.ts | 2 +- .../aws-eks/test/test.service-account.ts | 4 +- 24 files changed, 1194 insertions(+), 212 deletions(-) rename packages/@aws-cdk/aws-eks/lib/{k8s-resource.ts => k8s-manifest.ts} (63%) create mode 100644 packages/@aws-cdk/aws-eks/lib/k8s-object-value.ts create mode 100644 packages/@aws-cdk/aws-eks/lib/kubectl-handler/get/__init__.py create mode 100644 packages/@aws-cdk/aws-eks/test/pinger/function/index.py create mode 100644 packages/@aws-cdk/aws-eks/test/pinger/pinger.ts rename packages/@aws-cdk/aws-eks/test/{test.manifest.ts => test.k8s-manifest.ts} (90%) create mode 100644 packages/@aws-cdk/aws-eks/test/test.k8s-object-value.ts diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 003167058532b..649f881102fed 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -31,7 +31,7 @@ const cluster = new eks.Cluster(this, 'hello-eks', { }); // apply a kubernetes manifest to the cluster -cluster.addResource('mypod', { +cluster.addManifest('mypod', { apiVersion: 'v1', kind: 'Pod', metadata: { name: 'mypod' }, @@ -60,10 +60,10 @@ ClusterConfigCommand43AAE40F = aws eks update-kubeconfig --name cluster-xxxxx -- ``` > The IAM role specified in this command is called the "**masters role**". This is -> an IAM role that is associated with the `system:masters` [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) +> an IAM role that is associated with the `system:masters` [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) > group and has super-user access to the cluster. > -> You can specify this role using the `mastersRole` option, or otherwise a role will be +> You can specify this role using the `mastersRole` option, or otherwise a role will be > automatically created for you. This role can be assumed by anyone in the account with > `sts:AssumeRole` permissions for this role. @@ -214,7 +214,7 @@ const cluster = new eks.FargateCluster(this, 'MyCluster', { }); // apply k8s resources on this cluster -cluster.addResource(...); +cluster.addManifest(...); ``` **NOTE**: Classic Load Balancers and Network Load Balancers are not supported on @@ -266,7 +266,7 @@ the capacity. ### Kubernetes Resources -The `KubernetesResource` construct or `cluster.addResource` method can be used +The `KubernetesManifest` construct or `cluster.addManifest` method can be used to apply Kubernetes resource manifests to this cluster. The following examples will deploy the [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes) @@ -309,13 +309,13 @@ const service = { }; // option 1: use a construct -new KubernetesResource(this, 'hello-kub', { +new KubernetesManifest(this, 'hello-kub', { cluster, manifest: [ deployment, service ] }); -// or, option2: use `addResource` -cluster.addResource('hello-kub', service, deployment); +// or, option2: use `addManifest` +cluster.addManifest('hello-kub', service, deployment); ``` ##### Kubectl Environment @@ -342,7 +342,7 @@ import * as request from 'sync-request'; const manifestUrl = 'https://url/of/manifest.yaml'; const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); -cluster.addResource('my-resource', ...manifest); +cluster.addManifest('my-resource', ...manifest); ``` Since Kubernetes resources are implemented as CloudFormation resources in the @@ -356,17 +356,17 @@ There are cases where Kubernetes resources must be deployed in a specific order. For example, you cannot define a resource in a Kubernetes namespace before the namespace was created. -You can represent dependencies between `KubernetesResource`s using +You can represent dependencies between `KubernetesManifest`s using `resource.node.addDependency()`: ```ts -const namespace = cluster.addResource('my-namespace', { +const namespace = cluster.addManifest('my-namespace', { apiVersion: 'v1', kind: 'Namespace', metadata: { name: 'my-app' } }); -const service = cluster.addResource('my-service', { +const service = cluster.addManifest('my-service', { metadata: { name: 'myservice', namespace: 'my-app' @@ -377,14 +377,14 @@ const service = cluster.addResource('my-service', { service.node.addDependency(namespace); // will apply `my-namespace` before `my-service`. ``` -NOTE: when a `KubernetesResource` includes multiple resources (either directly -or through `cluster.addResource()`) (e.g. `cluster.addResource('foo', r1, r2, +NOTE: when a `KubernetesManifest` includes multiple resources (either directly +or through `cluster.addManifest()`) (e.g. `cluster.addManifest('foo', r1, r2, r3,...))`), these resources will be applied as a single manifest via `kubectl` and will be applied sequentially (the standard behavior in `kubectl`). ### Patching Kubernetes Resources -The KubernetesPatch construct can be used to update existing kubernetes +The `KubernetesPatch` construct can be used to update existing kubernetes resources. The following example can be used to patch the `hello-kubernetes` deployment from the example above with 5 replicas. @@ -397,6 +397,37 @@ new KubernetesPatch(this, 'hello-kub-deployment-label', { }) ``` +### Querying Kubernetes Object Values + +The `KubernetesObjectValue` construct can be used to query for information about kubernetes objects, +and use that as part of your CDK application. + +For example, you can fetch the address of a [`LoadBalancer`](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) type service: + +```typescript +// query the load balancer address +const myServiceAddress = new KubernetesObjectValue(this, 'LoadBalancerAttribute', { + cluster: cluster, + resourceType: 'service', + resourceName: 'my-service', + jsonPath: '.status.loadBalancer.ingress[0].hostname', // https://kubernetes.io/docs/reference/kubectl/jsonpath/ +}); + +// pass the address to a lambda function +const proxyFunction = new lambda.Function(this, 'ProxyFunction', { + ... + environment: { + myServiceAddress: myServiceAddress.value + }, +}) +``` + +Specifically, since the above use-case is quite common, there is an easier way to access that information: + +```typescript +const loadBalancerAddress = cluster.getServiceLoadBalancerAddress('my-service'); +``` + ### AWS IAM Mapping As described in the [Amazon EKS User Guide](https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html), @@ -548,7 +579,7 @@ const sa = cluster.addServiceAccount('MyServiceAccount'); const bucket = new Bucket(this, 'Bucket'); bucket.grantReadWrite(serviceAccount); -const mypod = cluster.addResource('mypod', { +const mypod = cluster.addManifest('mypod', { apiVersion: 'v1', kind: 'Pod', metadata: { name: 'mypod' }, diff --git a/packages/@aws-cdk/aws-eks/lib/aws-auth.ts b/packages/@aws-cdk/aws-eks/lib/aws-auth.ts index 390d730652c83..ad6734e3f952d 100644 --- a/packages/@aws-cdk/aws-eks/lib/aws-auth.ts +++ b/packages/@aws-cdk/aws-eks/lib/aws-auth.ts @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam'; import { Construct, Lazy, Stack } from '@aws-cdk/core'; import { AwsAuthMapping } from './aws-auth-mapping'; import { Cluster } from './cluster'; -import { KubernetesResource } from './k8s-resource'; +import { KubernetesManifest } from './k8s-manifest'; /** * Configuration props for the AwsAuth construct. @@ -32,7 +32,7 @@ export class AwsAuth extends Construct { this.stack = Stack.of(this); - new KubernetesResource(this, 'manifest', { + new KubernetesManifest(this, 'manifest', { cluster: props.cluster, manifest: [ { diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts index f9c1a592586ca..a9a9983cdd3a3 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts @@ -1,8 +1,8 @@ +import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, NestedStack, Stack } from '@aws-cdk/core'; import * as cr from '@aws-cdk/custom-resources'; -import * as path from 'path'; const HANDLER_DIR = path.join(__dirname, 'cluster-resource-handler'); const HANDLER_RUNTIME = lambda.Runtime.NODEJS_12_X; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index dbc34c53353af..9bbc71ca73f5a 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -4,15 +4,16 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as ssm from '@aws-cdk/aws-ssm'; -import { CfnOutput, CfnResource, Construct, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; +import { CfnOutput, CfnResource, Construct, IResource, Resource, Stack, Tag, Token, Duration } from '@aws-cdk/core'; import * as YAML from 'yaml'; import { AwsAuth } from './aws-auth'; import { clusterArnComponents, ClusterResource } from './cluster-resource'; import { CfnClusterProps } from './eks.generated'; import { FargateProfile, FargateProfileOptions } from './fargate-profile'; import { HelmChart, HelmChartOptions } from './helm-chart'; +import { KubernetesManifest } from './k8s-manifest'; +import { KubernetesObjectValue } from './k8s-object-value'; import { KubernetesPatch } from './k8s-patch'; -import { KubernetesResource } from './k8s-resource'; import { KubectlProvider, KubectlProviderProps } from './kubectl-provider'; import { Nodegroup, NodegroupOptions } from './managed-nodegroup'; import { ServiceAccount, ServiceAccountOptions } from './service-account'; @@ -416,6 +417,27 @@ export class KubernetesVersion { private constructor(public readonly version: string) { } } +/** + * Options for fetching a ServiceLoadBalancerAddress. + */ +export interface ServiceLoadBalancerAddressOptions { + + /** + * Timeout for waiting on the load balancer address. + * + * @default Duration.minutes(5) + */ + readonly timeout?: Duration; + + /** + * The namespace the service belongs to. + * + * @default 'default' + */ + readonly namespace?: string; + +} + /** * A Cluster represents a managed Kubernetes Service (EKS) * @@ -524,7 +546,7 @@ export class Cluster extends Resource implements ICluster { private _spotInterruptHandler?: HelmChart; - private _neuronDevicePlugin?: KubernetesResource; + private _neuronDevicePlugin?: KubernetesManifest; private readonly endpointAccess: EndpointAccess; @@ -714,6 +736,27 @@ export class Cluster extends Resource implements ICluster { this.defineCoreDnsComputeType(props.coreDnsComputeType ?? CoreDnsComputeType.EC2); } + /** + * Fetch the load balancer address of a service of type 'LoadBalancer'. + * + * @param serviceName The name of the service. + * @param options Additional operation options. + */ + public getServiceLoadBalancerAddress(serviceName: string, options: ServiceLoadBalancerAddressOptions = {}): string { + + const loadBalancerAddress = new KubernetesObjectValue(this, `${serviceName}LoadBalancerAddress`, { + cluster: this, + objectType: 'service', + objectName: serviceName, + objectNamespace: options.namespace, + jsonPath: '.status.loadBalancer.ingress[0].hostname', + timeout: options.timeout, + }); + + return loadBalancerAddress.value; + + } + /** * Add nodes to this EKS cluster * @@ -913,16 +956,16 @@ export class Cluster extends Resource implements ICluster { } /** - * Defines a Kubernetes resource in this cluster. + * Defines a Kubernetes manifest in this cluster. * * The manifest will be applied/deleted using kubectl as needed. * * @param id logical id of this manifest * @param manifest a list of Kubernetes resource specifications - * @returns a `KubernetesResource` object. + * @returns a `KubernetesManifest` object. */ - public addResource(id: string, ...manifest: any[]) { - return new KubernetesResource(this, `manifest-${id}`, { cluster: this, manifest }); + public addManifest(id: string, ...manifest: any[]) { + return new KubernetesManifest(this, `manifest-${id}`, { cluster: this, manifest }); } /** @@ -1109,7 +1152,7 @@ export class Cluster extends Resource implements ICluster { if (!this._neuronDevicePlugin) { const fileContents = fs.readFileSync(path.join(__dirname, 'addons/neuron-device-plugin.yaml'), 'utf8'); const sanitized = YAML.parse(fileContents); - this._neuronDevicePlugin = this.addResource('NeuronDevicePlugin', sanitized); + this._neuronDevicePlugin = this.addManifest('NeuronDevicePlugin', sanitized); } return this._neuronDevicePlugin; diff --git a/packages/@aws-cdk/aws-eks/lib/index.ts b/packages/@aws-cdk/aws-eks/lib/index.ts index 5773e46ffc2bc..e1d353959096f 100644 --- a/packages/@aws-cdk/aws-eks/lib/index.ts +++ b/packages/@aws-cdk/aws-eks/lib/index.ts @@ -6,7 +6,8 @@ export * from './eks.generated'; export * from './fargate-profile'; export * from './helm-chart'; export * from './k8s-patch'; -export * from './k8s-resource'; +export * from './k8s-manifest'; +export * from './k8s-object-value'; export * from './fargate-cluster'; export * from './service-account'; export * from './managed-nodegroup'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts similarity index 63% rename from packages/@aws-cdk/aws-eks/lib/k8s-resource.ts rename to packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts index ae0fa409c8727..fc4f171ec63d4 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-manifest.ts @@ -2,55 +2,55 @@ import { Construct, CustomResource, Stack } from '@aws-cdk/core'; import { Cluster } from './cluster'; /** - * Properties for KubernetesResources + * Properties for KubernetesManifest */ -export interface KubernetesResourceProps { +export interface KubernetesManifestProps { /** - * The EKS cluster to apply this configuration to. + * The EKS cluster to apply this manifest to. * * [disable-awslint:ref-via-interface] */ readonly cluster: Cluster; /** - * The resource manifest. + * The manifest to apply. * * Consists of any number of child resources. * - * When the resource is created/updated, this manifest will be applied to the - * cluster through `kubectl apply` and when the resource or the stack is - * deleted, the manifest will be deleted through `kubectl delete`. + * When the resources are created/updated, this manifest will be applied to the + * cluster through `kubectl apply` and when the resources or the stack is + * deleted, the resources in the manifest will be deleted through `kubectl delete`. * * @example * - * { + * [{ * apiVersion: 'v1', * kind: 'Pod', * metadata: { name: 'mypod' }, * spec: { * containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ] } ] * } - * } + * }] * */ readonly manifest: any[]; } /** - * Represents a resource within the Kubernetes system. + * Represents a manifest within the Kubernetes system. * - * Alternatively, you can use `cluster.addResource(resource[, resource, ...])` + * Alternatively, you can use `cluster.addManifest(resource[, resource, ...])` * to define resources on this cluster. * - * Applies/deletes the resources using `kubectl` in sync with the resource. + * Applies/deletes the manifest using `kubectl`. */ -export class KubernetesResource extends Construct { +export class KubernetesManifest extends Construct { /** * The CloudFormation reosurce type. */ public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-KubernetesResource'; - constructor(scope: Construct, id: string, props: KubernetesResourceProps) { + constructor(scope: Construct, id: string, props: KubernetesManifestProps) { super(scope, id); const stack = Stack.of(this); @@ -58,7 +58,7 @@ export class KubernetesResource extends Construct { new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, - resourceType: KubernetesResource.RESOURCE_TYPE, + resourceType: KubernetesManifest.RESOURCE_TYPE, properties: { // `toJsonString` enables embedding CDK tokens in the manifest and will // render a CloudFormation-compatible JSON string (similar to diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-object-value.ts b/packages/@aws-cdk/aws-eks/lib/k8s-object-value.ts new file mode 100644 index 0000000000000..5c60116618e58 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/k8s-object-value.ts @@ -0,0 +1,87 @@ +import { Construct, CustomResource, Token, Duration } from '@aws-cdk/core'; +import { Cluster } from './cluster'; + +/** + * Properties for KubernetesObjectValue. + */ +export interface KubernetesObjectValueProps { + /** + * The EKS cluster to fetch attributes from. + * + * [disable-awslint:ref-via-interface] + */ + readonly cluster: Cluster; + + /** + * The object type to query. (e.g 'service', 'pod'...) + */ + readonly objectType: string; + + /** + * The name of the object to query. + */ + readonly objectName: string; + + /** + * The namespace the object belongs to. + * + * @default 'default' + */ + readonly objectNamespace?: string; + + /** + * JSONPath to the specific value. + * + * @see https://kubernetes.io/docs/reference/kubectl/jsonpath/ + */ + readonly jsonPath: string; + + /** + * Timeout for waiting on a value. + * + * @default Duration.minutes(5) + */ + readonly timeout?: Duration; + +} + +/** + * Represents a value of a specific object deployed in the cluster. + * Use this to fetch any information available by the `kubectl get` command. + */ +export class KubernetesObjectValue extends Construct { + /** + * The CloudFormation reosurce type. + */ + public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-KubernetesObjectValue'; + + private _resource: CustomResource; + + constructor(scope: Construct, id: string, props: KubernetesObjectValueProps) { + super(scope, id); + + const provider = props.cluster._attachKubectlResourceScope(this); + + this._resource = new CustomResource(this, 'Resource', { + resourceType: KubernetesObjectValue.RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + ClusterName: props.cluster.clusterName, + RoleArn: props.cluster._kubectlCreationRole.roleArn, + ObjectType: props.objectType, + ObjectName: props.objectName, + ObjectNamespace: props.objectNamespace ?? 'default', + JsonPath: props.jsonPath, + TimeoutSeconds: (props?.timeout ?? Duration.minutes(5)).toSeconds(), + }, + }); + + } + + /** + * The value as a string token. + */ + public get value(): string { + return Token.asString(this._resource.getAtt('Value')); + } +} diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/get/__init__.py b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/get/__init__.py new file mode 100644 index 0000000000000..6058c8371e5bd --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/get/__init__.py @@ -0,0 +1,84 @@ +import json +import logging +import os +import subprocess +import time + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/kubectl:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + +def get_handler(event, context): + logger.info(json.dumps(event)) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties (all required) + cluster_name = props['ClusterName'] + role_arn = props['RoleArn'] + + # "log in" to the cluster + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--role-arn', role_arn, + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + object_type = props['ObjectType'] + object_name = props['ObjectName'] + object_namespace = props['ObjectNamespace'] + json_path = props['JsonPath'] + timeout_seconds = props['TimeoutSeconds'] + + # json path should be surrouded with '{}' + path = '{{{0}}}'.format(json_path) + if request_type == 'Create' or request_type == 'Update': + output = wait_for_output(['get', '-n', object_namespace, object_type, object_name, "-o=jsonpath='{{{0}}}'".format(json_path)], int(timeout_seconds)) + return {'Data': {'Value': output}} + elif request_type == 'Delete': + pass + else: + raise Exception("invalid request type %s" % request_type) + +def wait_for_output(args, timeout_seconds): + + end_time = time.time() + timeout_seconds + error = None + + while time.time() < end_time: + try: + # the output is surrounded with '', so we unquote + output = kubectl(args).decode('utf-8')[1:-1] + if output: + return output + except Exception as e: + error = str(e) + # also a recoverable error + if 'NotFound' in error: + pass + time.sleep(10) + + raise RuntimeError(f'Timeout waiting for output from kubectl command: {args} (last_error={error})') + +def kubectl(args): + retry = 3 + while retry > 0: + try: + cmd = [ 'kubectl', '--kubeconfig', kubeconfig ] + args + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + output = exc.output + if b'i/o timeout' in output and retry > 0: + logger.info("kubectl timed out, retries left: %s" % retry) + retry = retry - 1 + else: + raise Exception(output) + else: + logger.info(output) + return output diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/index.py b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/index.py index 5d248e6441fae..60b6ce78b2af6 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-handler/index.py +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-handler/index.py @@ -4,6 +4,7 @@ from apply import apply_handler from helm import helm_handler from patch import patch_handler +from get import get_handler def handler(event, context): print(json.dumps(event)) @@ -18,4 +19,7 @@ def handler(event, context): if resource_type == 'Custom::AWSCDK-EKS-KubernetesPatch': return patch_handler(event, context) + if resource_type == 'Custom::AWSCDK-EKS-KubernetesObjectValue': + return get_handler(event, context) + raise Exception("unknown resource type %s" % resource_type) diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts index 84efa5325854a..b4f79b44d33ad 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-layer.ts @@ -1,6 +1,6 @@ +import * as crypto from 'crypto'; import * as lambda from '@aws-cdk/aws-lambda'; import { CfnResource, Construct, Stack, Token } from '@aws-cdk/core'; -import * as crypto from 'crypto'; const KUBECTL_APP_ARN = 'arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl'; const KUBECTL_APP_CN_ARN = 'arn:aws-cn:serverlessrepo:cn-north-1:487369736442:applications/lambda-layer-kubectl'; diff --git a/packages/@aws-cdk/aws-eks/lib/service-account.ts b/packages/@aws-cdk/aws-eks/lib/service-account.ts index 83da66fbfef73..c0ff95229d5be 100644 --- a/packages/@aws-cdk/aws-eks/lib/service-account.ts +++ b/packages/@aws-cdk/aws-eks/lib/service-account.ts @@ -78,7 +78,7 @@ export class ServiceAccount extends Construct implements IPrincipal { this.grantPrincipal = this.role.grantPrincipal; this.policyFragment = this.role.policyFragment; - cluster.addResource(`${id}ServiceAccountResource`, { + cluster.addManifest(`${id}ServiceAccountResource`, { apiVersion: 'v1', kind: 'ServiceAccount', metadata: { diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json index 0889cc63a0237..fbeeaafcd7f98 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.expected.json @@ -1114,7 +1114,7 @@ }, "/", { - "Ref": "AssetParameters3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789S3BucketBC18629C" + "Ref": "AssetParameters5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127fS3Bucket5F68514E" }, "/", { @@ -1124,7 +1124,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789S3VersionKeyE68C888F" + "Ref": "AssetParameters5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127fS3VersionKeyBDAFCBA6" } ] } @@ -1137,7 +1137,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789S3VersionKeyE68C888F" + "Ref": "AssetParameters5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127fS3VersionKeyBDAFCBA6" } ] } @@ -1147,11 +1147,11 @@ ] }, "Parameters": { - "referencetoawscdkeksclusterprivateendpointtestAssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3Bucket87F4EA82Ref": { - "Ref": "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3Bucket26C90BA0" + "referencetoawscdkeksclusterprivateendpointtestAssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketB0853396Ref": { + "Ref": "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketAEF9EB6C" }, - "referencetoawscdkeksclusterprivateendpointtestAssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3VersionKey3BEF8ACDRef": { - "Ref": "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3VersionKeyD269C675" + "referencetoawscdkeksclusterprivateendpointtestAssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey89A51DCERef": { + "Ref": "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey912C763C" }, "referencetoawscdkeksclusterprivateendpointtestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3Bucket7CB66361Ref": { "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" @@ -1175,7 +1175,7 @@ }, "/", { - "Ref": "AssetParametersf2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0S3BucketACD6057C" + "Ref": "AssetParameters742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bdS3BucketA257B564" }, "/", { @@ -1185,7 +1185,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0S3VersionKey20D7AC7B" + "Ref": "AssetParameters742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bdS3VersionKey3F8059DC" } ] } @@ -1198,7 +1198,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0S3VersionKey20D7AC7B" + "Ref": "AssetParameters742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bdS3VersionKey3F8059DC" } ] } @@ -1208,11 +1208,11 @@ ] }, "Parameters": { - "referencetoawscdkeksclusterprivateendpointtestAssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3Bucket5848D8F5Ref": { - "Ref": "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3BucketE7D09A6B" + "referencetoawscdkeksclusterprivateendpointtestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket59F91893Ref": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2" }, - "referencetoawscdkeksclusterprivateendpointtestAssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3VersionKeyD69255C2Ref": { - "Ref": "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3VersionKey1DA734B2" + "referencetoawscdkeksclusterprivateendpointtestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey4D2CDF61Ref": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A" }, "referencetoawscdkeksclusterprivateendpointtestVpcPrivateSubnet1Subnet94DAD769Ref": { "Ref": "VpcPrivateSubnet1Subnet536B997A" @@ -1282,17 +1282,17 @@ } }, "Parameters": { - "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3Bucket26C90BA0": { + "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketAEF9EB6C": { "Type": "String", - "Description": "S3 bucket for asset \"00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791\"" + "Description": "S3 bucket for asset \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" }, - "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3VersionKeyD269C675": { + "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey912C763C": { "Type": "String", - "Description": "S3 key for asset version \"00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791\"" + "Description": "S3 key for asset version \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" }, - "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791ArtifactHashAADC8B03": { + "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3ArtifactHashD59B0951": { "Type": "String", - "Description": "Artifact hash for asset \"00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791\"" + "Description": "Artifact hash for asset \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" }, "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256": { "Type": "String", @@ -1306,41 +1306,41 @@ "Type": "String", "Description": "Artifact hash for asset \"974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74c\"" }, - "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3BucketE7D09A6B": { + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2": { "Type": "String", - "Description": "S3 bucket for asset \"649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502\"" + "Description": "S3 bucket for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" }, - "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3VersionKey1DA734B2": { + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A": { "Type": "String", - "Description": "S3 key for asset version \"649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502\"" + "Description": "S3 key for asset version \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" }, - "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502ArtifactHash815E1969": { + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2ArtifactHashE86B38C7": { "Type": "String", - "Description": "Artifact hash for asset \"649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502\"" + "Description": "Artifact hash for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" }, - "AssetParameters3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789S3BucketBC18629C": { + "AssetParameters5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127fS3Bucket5F68514E": { "Type": "String", - "Description": "S3 bucket for asset \"3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789\"" + "Description": "S3 bucket for asset \"5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127f\"" }, - "AssetParameters3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789S3VersionKeyE68C888F": { + "AssetParameters5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127fS3VersionKeyBDAFCBA6": { "Type": "String", - "Description": "S3 key for asset version \"3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789\"" + "Description": "S3 key for asset version \"5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127f\"" }, - "AssetParameters3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789ArtifactHash026B7D88": { + "AssetParameters5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127fArtifactHashE8539D9F": { "Type": "String", - "Description": "Artifact hash for asset \"3c8e15207108696f26eb3900c56b9ed4a81535ed7d0fdb4477972f1741ad9789\"" + "Description": "Artifact hash for asset \"5a6e80a336f1483948e6ea2840535cd58002e08674439e709be882d27ff6127f\"" }, - "AssetParametersf2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0S3BucketACD6057C": { + "AssetParameters742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bdS3BucketA257B564": { "Type": "String", - "Description": "S3 bucket for asset \"f2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0\"" + "Description": "S3 bucket for asset \"742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bd\"" }, - "AssetParametersf2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0S3VersionKey20D7AC7B": { + "AssetParameters742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bdS3VersionKey3F8059DC": { "Type": "String", - "Description": "S3 key for asset version \"f2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0\"" + "Description": "S3 key for asset version \"742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bd\"" }, - "AssetParametersf2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0ArtifactHash05CD8D10": { + "AssetParameters742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bdArtifactHash15503EA1": { "Type": "String", - "Description": "Artifact hash for asset \"f2ad7629f5f54ad293dccc2fb60891424f9149f12d84f2f12728543b145962a0\"" + "Description": "Artifact hash for asset \"742639efa420e236373ec969a3b6acfe6585597ddbfb4553e02bf7bc32d423bd\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts index c8bcd43bdebfa..581acb75d033e 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster-private-endpoint.ts @@ -26,7 +26,7 @@ class EksClusterStack extends TestStack { }); // this is the valdiation. it won't work if the private access is not setup properly. - cluster.addResource('config-map', { + cluster.addManifest('config-map', { kind: 'ConfigMap', apiVersion: 'v1', data: { 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 2be0396673fb6..471201d249202 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 @@ -2844,6 +2844,102 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "ClustermanifestsimplewebpodC2D35484": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": "[{\"kind\":\"Pod\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"webpod\",\"labels\":{\"app\":\"simple-web\"}},\"spec\":{\"containers\":[{\"name\":\"simplewebcontainer\",\"image\":\"nginx\",\"ports\":[{\"containerPort\":80}]}]}}]", + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + } + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Clustermanifestsimplewebservice4594DB30": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"webservice\",\"annotations\":{\"service.beta.kubernetes.io/aws-load-balancer-internal\":\"true\",\"service.beta.kubernetes.io/aws-load-balancer-extra-security-groups\":\"", + { + "Fn::GetAtt": [ + "WebServiceSecurityGroupA556AEB5", + "GroupId" + ] + }, + "\"}},\"spec\":{\"type\":\"LoadBalancer\",\"ports\":[{\"port\":9000,\"targetPort\":80}],\"selector\":{\"app\":\"simple-web\"}}}]" + ] + ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + } + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterwebserviceLoadBalancerAddress38B566FF": { + "Type": "Custom::AWSCDK-EKS-KubernetesObjectValue", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + }, + "ObjectType": "service", + "ObjectName": "webservice", + "ObjectNamespace": "default", + "JsonPath": ".status.loadBalancer.ingress[0].hostname", + "TimeoutSeconds": 300 + }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { "Type": "AWS::CloudFormation::Stack", "Properties": { @@ -2857,7 +2953,7 @@ }, "/", { - "Ref": "AssetParameterse8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57bS3Bucket393DA96E" + "Ref": "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3BucketC7BC4682" }, "/", { @@ -2867,7 +2963,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57bS3VersionKey0633C6DF" + "Ref": "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3VersionKeyEFC9702C" } ] } @@ -2880,7 +2976,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterse8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57bS3VersionKey0633C6DF" + "Ref": "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3VersionKeyEFC9702C" } ] } @@ -2890,11 +2986,11 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3Bucket363F6F79Ref": { - "Ref": "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3Bucket26C90BA0" + "referencetoawscdkeksclustertestAssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3Bucket49014916Ref": { + "Ref": "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketAEF9EB6C" }, - "referencetoawscdkeksclustertestAssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3VersionKeyDC22C51CRef": { - "Ref": "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3VersionKeyD269C675" + "referencetoawscdkeksclustertestAssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey2D51245BRef": { + "Ref": "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey912C763C" }, "referencetoawscdkeksclustertestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketA9A24CF5Ref": { "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" @@ -2918,7 +3014,7 @@ }, "/", { - "Ref": "AssetParameters5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2feS3Bucket864A12C7" + "Ref": "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3BucketB126CD55" }, "/", { @@ -2928,7 +3024,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2feS3VersionKeyD0F4176F" + "Ref": "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3VersionKey6DD5D685" } ] } @@ -2941,7 +3037,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2feS3VersionKeyD0F4176F" + "Ref": "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3VersionKey6DD5D685" } ] } @@ -2951,11 +3047,11 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3Bucket8095B011Ref": { - "Ref": "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3BucketE7D09A6B" + "referencetoawscdkeksclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3BucketC2E03523Ref": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2" }, - "referencetoawscdkeksclustertestAssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3VersionKeyFE6DC258Ref": { - "Ref": "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3VersionKey1DA734B2" + "referencetoawscdkeksclustertestAssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey901D947ARef": { + "Ref": "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A" }, "referencetoawscdkeksclustertestAssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketA9A24CF5Ref": { "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" @@ -3142,6 +3238,314 @@ "DependsOn": [ "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderRole517FED65" ] + }, + "WebServiceSecurityGroupA556AEB5": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-eks-cluster-test/WebServiceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "WebServiceSecurityGroupfromawscdkeksclustertestWebServiceSecurityGroup62BA456890005BF0F34B": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "allow http 9000 access from myself", + "FromPort": 9000, + "GroupId": { + "Fn::GetAtt": [ + "WebServiceSecurityGroupA556AEB5", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "WebServiceSecurityGroupA556AEB5", + "GroupId" + ] + }, + "ToPort": 9000 + } + }, + "ServicePingerFunctionServiceRole3120191B": { + "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" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" + ] + ] + } + ] + } + }, + "ServicePingerFunctionADF51BAF": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43S3BucketB43AFE04" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43S3VersionKeyD4B858BC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43S3VersionKeyD4B858BC" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "ServicePingerFunctionServiceRole3120191B", + "Arn" + ] + }, + "Runtime": "python3.6", + "Timeout": 600, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "WebServiceSecurityGroupA556AEB5", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + } + }, + "DependsOn": [ + "ServicePingerFunctionServiceRole3120191B" + ] + }, + "ServicePingerProviderframeworkonEventServiceRole3DB083B7": { + "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" + ] + ] + } + ] + } + }, + "ServicePingerProviderframeworkonEventServiceRoleDefaultPolicyD142E8F7": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ServicePingerFunctionADF51BAF", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ServicePingerProviderframeworkonEventServiceRoleDefaultPolicyD142E8F7", + "Roles": [ + { + "Ref": "ServicePingerProviderframeworkonEventServiceRole3DB083B7" + } + ] + } + }, + "ServicePingerProviderframeworkonEventEC59DE20": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3VersionKeyF47FA401" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "framework.onEvent", + "Role": { + "Fn::GetAtt": [ + "ServicePingerProviderframeworkonEventServiceRole3DB083B7", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Description": "AWS CDK resource provider framework - onEvent (aws-cdk-eks-cluster-test/ServicePinger/Provider)", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "ServicePingerFunctionADF51BAF", + "Arn" + ] + } + } + }, + "Timeout": 900 + }, + "DependsOn": [ + "ServicePingerProviderframeworkonEventServiceRoleDefaultPolicyD142E8F7", + "ServicePingerProviderframeworkonEventServiceRole3DB083B7" + ] + }, + "ServicePinger01F6DA06": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ServicePingerProviderframeworkonEventEC59DE20", + "Arn" + ] + }, + "Url": { + "Fn::Join": [ + "", + [ + "http://", + { + "Fn::GetAtt": [ + "ClusterwebserviceLoadBalancerAddress38B566FF", + "Value" + ] + }, + ":9000" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } }, "Outputs": { @@ -3185,6 +3589,14 @@ ] } }, + "Response": { + "Value": { + "Fn::GetAtt": [ + "ServicePinger01F6DA06", + "Value" + ] + } + }, "ClusterEndpoint": { "Value": { "Fn::GetAtt": [ @@ -3232,17 +3644,17 @@ } }, "Parameters": { - "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3Bucket26C90BA0": { + "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3BucketAEF9EB6C": { "Type": "String", - "Description": "S3 bucket for asset \"00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791\"" + "Description": "S3 bucket for asset \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" }, - "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791S3VersionKeyD269C675": { + "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3S3VersionKey912C763C": { "Type": "String", - "Description": "S3 key for asset version \"00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791\"" + "Description": "S3 key for asset version \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" }, - "AssetParameters00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791ArtifactHashAADC8B03": { + "AssetParameters1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3ArtifactHashD59B0951": { "Type": "String", - "Description": "Artifact hash for asset \"00ba02e613a29439c93f9aef4e82e253763eb70cd32026df071449485c692791\"" + "Description": "Artifact hash for asset \"1ea72e469c0d3a62f2500d1c42aa05a5034a518637239066718928d7e6f748d3\"" }, "AssetParameters974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74cS3BucketF1BD2256": { "Type": "String", @@ -3256,17 +3668,17 @@ "Type": "String", "Description": "Artifact hash for asset \"974a6fb29abbd1d98fce56346da3743e79277f0f52e0e2cdf3f1867ac5b1e74c\"" }, - "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3BucketE7D09A6B": { + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3Bucket9ABBD5A2": { "Type": "String", - "Description": "S3 bucket for asset \"649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502\"" + "Description": "S3 bucket for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" }, - "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502S3VersionKey1DA734B2": { + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2S3VersionKey40FF2C4A": { "Type": "String", - "Description": "S3 key for asset version \"649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502\"" + "Description": "S3 key for asset version \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" }, - "AssetParameters649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502ArtifactHash815E1969": { + "AssetParametersb7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2ArtifactHashE86B38C7": { "Type": "String", - "Description": "Artifact hash for asset \"649b09403c8414e624c965d6c2f0e41c341c2afa5d8e7bae4ac5746fe230f502\"" + "Description": "Artifact hash for asset \"b7d8a9750f8bfded8ac76be100e3bee1c3d4824df006766110d023f42952f5c2\"" }, "AssetParameters952bd1c03e8201c4c1c67d6de0f3fdaaf88fda05f89a1232c3f6364343cd5344S3Bucket055DC235": { "Type": "String", @@ -3292,29 +3704,41 @@ "Type": "String", "Description": "Artifact hash for asset \"eb7a9b73a02dcd848325fc3abc22c1923c364d7480e06bd68a337dc3f33143d3\"" }, - "AssetParameterse8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57bS3Bucket393DA96E": { + "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43S3BucketB43AFE04": { + "Type": "String", + "Description": "S3 bucket for asset \"2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43\"" + }, + "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43S3VersionKeyD4B858BC": { + "Type": "String", + "Description": "S3 key for asset version \"2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43\"" + }, + "AssetParameters2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43ArtifactHashC3527E8B": { + "Type": "String", + "Description": "Artifact hash for asset \"2acc31b34c05692ab3ea9831a27e5f241cffb21857e633d8256b8f0ebf5f3f43\"" + }, + "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3BucketC7BC4682": { "Type": "String", - "Description": "S3 bucket for asset \"e8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57b\"" + "Description": "S3 bucket for asset \"63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1\"" }, - "AssetParameterse8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57bS3VersionKey0633C6DF": { + "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1S3VersionKeyEFC9702C": { "Type": "String", - "Description": "S3 key for asset version \"e8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57b\"" + "Description": "S3 key for asset version \"63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1\"" }, - "AssetParameterse8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57bArtifactHashA64B37F7": { + "AssetParameters63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1ArtifactHash36EF9FF1": { "Type": "String", - "Description": "Artifact hash for asset \"e8f5d2a182613ad64e98c81d59e2ad3ecb46c92c5b51c3612a5c614a0715e57b\"" + "Description": "Artifact hash for asset \"63d6ca207248ed16c522293c00ba826669cf96470fc3eac8707647d8d0877ac1\"" }, - "AssetParameters5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2feS3Bucket864A12C7": { + "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3BucketB126CD55": { "Type": "String", - "Description": "S3 bucket for asset \"5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2fe\"" + "Description": "S3 bucket for asset \"2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364e\"" }, - "AssetParameters5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2feS3VersionKeyD0F4176F": { + "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eS3VersionKey6DD5D685": { "Type": "String", - "Description": "S3 key for asset version \"5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2fe\"" + "Description": "S3 key for asset version \"2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364e\"" }, - "AssetParameters5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2feArtifactHash2B9F340F": { + "AssetParameters2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364eArtifactHash70870D76": { "Type": "String", - "Description": "Artifact hash for asset \"5db67dc64d67f3574c3c3e10970910e121e77f67974ab320c4dc47af2f88d2fe\"" + "Description": "Artifact hash for asset \"2d77caab030d62f3e59ed7da4715b9f1ad594235c93aa3b21e9a31ec7cf8364e\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index 6340be221a298..30bd5144a2015 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -4,9 +4,14 @@ import * as iam from '@aws-cdk/aws-iam'; import { App, CfnOutput, Duration, Token } from '@aws-cdk/core'; import * as eks from '../lib'; import * as hello from './hello-k8s'; +import { Pinger } from './pinger/pinger'; import { TestStack } from './util'; class EksClusterStack extends TestStack { + + private cluster: eks.Cluster; + private vpc: ec2.Vpc; + constructor(scope: App, id: string) { super(scope, id); @@ -16,37 +21,105 @@ class EksClusterStack extends TestStack { }); // just need one nat gateway to simplify the test - const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 3, natGateways: 1 }); + this.vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 3, natGateways: 1 }); // create the cluster with a default nodegroup capacity - const cluster = new eks.Cluster(this, 'Cluster', { - vpc, + this.cluster = new eks.Cluster(this, 'Cluster', { + vpc: this.vpc, mastersRole, defaultCapacity: 2, version: eks.KubernetesVersion.V1_16, }); - // fargate profile for resources in the "default" namespace - cluster.addFargateProfile('default', { - selectors: [{ namespace: 'default' }], + this.assertFargateProfile(); + + this.assertCapacity(); + + this.assertBottlerocket(); + + this.assertSpotCapacity(); + + this.assertInferenceInstances(); + + this.assertNodeGroup(); + + this.assertSimpleManifest(); + + this.assertSimpleHelmChart(); + + this.assertCreateNamespace(); + + this.assertServiceAccount(); + + this.assertServiceLoadBalancerAddress(); + + new CfnOutput(this, 'ClusterEndpoint', { value: this.cluster.clusterEndpoint }); + new CfnOutput(this, 'ClusterArn', { value: this.cluster.clusterArn }); + new CfnOutput(this, 'ClusterCertificateAuthorityData', { value: this.cluster.clusterCertificateAuthorityData }); + new CfnOutput(this, 'ClusterSecurityGroupId', { value: this.cluster.clusterSecurityGroupId }); + new CfnOutput(this, 'ClusterEncryptionConfigKeyArn', { value: this.cluster.clusterEncryptionConfigKeyArn }); + new CfnOutput(this, 'ClusterName', { value: this.cluster.clusterName }); + } + + private assertServiceAccount() { + // add a service account connected to a IAM role + this.cluster.addServiceAccount('MyServiceAccount'); + } + + private assertCreateNamespace() { + // deploy an nginx ingress in a namespace + const nginxNamespace = this.cluster.addManifest('nginx-namespace', { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: 'nginx', + }, }); - // add some capacity to the cluster. The IAM instance role will - // automatically be mapped via aws-auth to allow nodes to join the cluster. - cluster.addCapacity('Nodes', { - instanceType: new ec2.InstanceType('t2.medium'), - minCapacity: 3, + const nginxIngress = this.cluster.addChart('nginx-ingress', { + chart: 'nginx-ingress', + repository: 'https://helm.nginx.com/stable', + namespace: 'nginx', + wait: true, + createNamespace: false, + timeout: Duration.minutes(15), }); - // add bottlerocket nodes - cluster.addCapacity('BottlerocketNodes', { + // make sure namespace is deployed before the chart + nginxIngress.node.addDependency(nginxNamespace); + + + } + private assertSimpleHelmChart() { + // deploy the Kubernetes dashboard through a helm chart + this.cluster.addChart('dashboard', { + chart: 'kubernetes-dashboard', + repository: 'https://kubernetes.github.io/dashboard/', + }); + } + private assertSimpleManifest() { + // apply a kubernetes manifest + this.cluster.addManifest('HelloApp', ...hello.resources); + } + private assertNodeGroup() { + // add a extra nodegroup + this.cluster.addNodegroup('extra-ng', { instanceType: new ec2.InstanceType('t3.small'), - minCapacity: 2, - machineImageType: eks.MachineImageType.BOTTLEROCKET, + minSize: 1, + // reusing the default capacity nodegroup instance role when available + nodeRole: this.cluster.defaultCapacity ? this.cluster.defaultCapacity.role : undefined, }); - + } + private assertInferenceInstances() { + // inference instances + this.cluster.addCapacity('InferenceInstances', { + instanceType: new ec2.InstanceType('inf1.2xlarge'), + minCapacity: 1, + }); + } + private assertSpotCapacity() { // spot instances (up to 10) - cluster.addCapacity('spot', { + this.cluster.addCapacity('spot', { spotPrice: '0.1094', instanceType: new ec2.InstanceType('t3.large'), maxCapacity: 10, @@ -55,61 +128,92 @@ class EksClusterStack extends TestStack { awsApiRetryAttempts: 5, }, }); - - // inference instances - cluster.addCapacity('InferenceInstances', { - instanceType: new ec2.InstanceType('inf1.2xlarge'), - minCapacity: 1, + } + private assertBottlerocket() { + // add bottlerocket nodes + this.cluster.addCapacity('BottlerocketNodes', { + instanceType: new ec2.InstanceType('t3.small'), + minCapacity: 2, + machineImageType: eks.MachineImageType.BOTTLEROCKET, }); - // add a extra nodegroup - cluster.addNodegroup('extra-ng', { - instanceType: new ec2.InstanceType('t3.small'), - minSize: 1, - // reusing the default capacity nodegroup instance role when available - nodeRole: cluster.defaultCapacity ? cluster.defaultCapacity.role : undefined, + } + private assertCapacity() { + // add some capacity to the cluster. The IAM instance role will + // automatically be mapped via aws-auth to allow nodes to join the cluster. + this.cluster.addCapacity('Nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + minCapacity: 3, + }); + } + private assertFargateProfile() { + // fargate profile for resources in the "default" namespace + this.cluster.addFargateProfile('default', { + selectors: [{ namespace: 'default' }], }); - // apply a kubernetes manifest - cluster.addResource('HelloApp', ...hello.resources); + } - // deploy the Kubernetes dashboard through a helm chart - cluster.addChart('dashboard', { - chart: 'kubernetes-dashboard', - repository: 'https://kubernetes.github.io/dashboard/', + private assertServiceLoadBalancerAddress() { + + const serviceName = 'webservice'; + const labels = { app: 'simple-web' }; + const containerPort = 80; + const servicePort = 9000; + + const pingerSecurityGroup = new ec2.SecurityGroup(this, 'WebServiceSecurityGroup', { + vpc: this.vpc, }); - // deploy an nginx ingress in a namespace + pingerSecurityGroup.addIngressRule(pingerSecurityGroup, ec2.Port.tcp(servicePort), `allow http ${servicePort} access from myself`); - const nginxNamespace = cluster.addResource('nginx-namespace', { + this.cluster.addManifest('simple-web-pod', { + kind: 'Pod', + apiVersion: 'v1', + metadata: { name: 'webpod', labels: labels }, + spec: { + containers: [{ + name: 'simplewebcontainer', + image: 'nginx', + ports: [{ containerPort: containerPort }], + }], + }, + }); + + this.cluster.addManifest('simple-web-service', { + kind: 'Service', apiVersion: 'v1', - kind: 'Namespace', metadata: { - name: 'nginx', + name: serviceName, + annotations: { + // this is furtile soil for cdk8s-plus! :) + 'service.beta.kubernetes.io/aws-load-balancer-internal': 'true', + 'service.beta.kubernetes.io/aws-load-balancer-extra-security-groups': pingerSecurityGroup.securityGroupId, + }, + }, + spec: { + type: 'LoadBalancer', + ports: [{ port: servicePort, targetPort: containerPort }], + selector: labels, }, }); - const nginxIngress = cluster.addChart('nginx-ingress', { - chart: 'nginx-ingress', - repository: 'https://helm.nginx.com/stable', - namespace: 'nginx', - wait: true, - createNamespace: false, - timeout: Duration.minutes(15), + const loadBalancerAddress = this.cluster.getServiceLoadBalancerAddress(serviceName); + + // create a resource that hits the load balancer to make sure + // everything is wired properly. + const pinger = new Pinger(this, 'ServicePinger', { + url: `http://${loadBalancerAddress}:${servicePort}`, + securityGroup: pingerSecurityGroup, + vpc: this.vpc, }); - // make sure namespace is deployed before the chart - nginxIngress.node.addDependency(nginxNamespace); + // this should display a proper nginx response + // Welcome to nginx!... + new CfnOutput(this, 'Response', { + value: pinger.response, + }); - // add a service account connected to a IAM role - cluster.addServiceAccount('MyServiceAccount'); - - new CfnOutput(this, 'ClusterEndpoint', { value: cluster.clusterEndpoint }); - new CfnOutput(this, 'ClusterArn', { value: cluster.clusterArn }); - new CfnOutput(this, 'ClusterCertificateAuthorityData', { value: cluster.clusterCertificateAuthorityData }); - new CfnOutput(this, 'ClusterSecurityGroupId', { value: cluster.clusterSecurityGroupId }); - new CfnOutput(this, 'ClusterEncryptionConfigKeyArn', { value: cluster.clusterEncryptionConfigKeyArn }); - new CfnOutput(this, 'ClusterName', { value: cluster.clusterName }); } } diff --git a/packages/@aws-cdk/aws-eks/test/pinger/function/index.py b/packages/@aws-cdk/aws-eks/test/pinger/function/index.py new file mode 100644 index 0000000000000..fc8db8fa8ba17 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/pinger/function/index.py @@ -0,0 +1,24 @@ +import json +import logging +import urllib3 + +logger = logging.getLogger() +logger.setLevel(logging.INFO) +http = urllib3.PoolManager() + +def handler(event, context): + print(json.dumps(event)) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + url = props['Url'] + + if request_type in ['Create', 'Update']: + logger.info(f'Sending request to {url}') + # this should a substantial retry because it has to wait for the ELB to actually + # be functioning + response = http.request('GET', url, retries=urllib3.Retry(10, backoff_factor=1)) + if response.status != 200: + raise RuntimeError(f'Request failed: {status} ({response.reason})') + return {'Data': {'Value': response.data.decode('utf-8')}} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts b/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts new file mode 100644 index 0000000000000..c2931c1de3478 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/pinger/pinger.ts @@ -0,0 +1,43 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { Construct, CustomResource, Token, Duration } from '@aws-cdk/core'; +import * as cr from '@aws-cdk/custom-resources'; + +export interface PingerProps { + readonly url: string; + readonly securityGroup?: ec2.SecurityGroup; + readonly vpc?: ec2.Vpc; +} +export class Pinger extends Construct { + + private _resource: CustomResource; + + constructor(scope: Construct, id: string, props: PingerProps) { + super(scope, id); + + const func = new lambda.Function(this, 'Function', { + code: lambda.Code.fromAsset(`${__dirname}/function`), + handler: 'index.handler', + runtime: lambda.Runtime.PYTHON_3_6, + vpc: props.vpc, + securityGroups: props.securityGroup ? [props.securityGroup] : undefined, + timeout: Duration.minutes(10), + }); + + const provider = new cr.Provider(this, 'Provider', { + onEventHandler: func, + }); + + this._resource = new CustomResource(this, 'Resource', { + serviceToken: provider.serviceToken, + properties: { + Url: props.url, + }, + }); + } + + public get response() { + return Token.asString(this._resource.getAtt('Value')); + } + +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts index f0f624730f2a5..846bbd9d92a3a 100644 --- a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts @@ -1,7 +1,7 @@ import { countResources, expect, haveResource } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; import { Test } from 'nodeunit'; -import { Cluster, KubernetesResource, KubernetesVersion } from '../lib'; +import { Cluster, KubernetesManifest, KubernetesVersion } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; import { testFixtureNoVpc } from './util'; @@ -19,7 +19,7 @@ export = { new AwsAuth(stack, 'AwsAuth', { cluster }); // THEN - expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([{ apiVersion: 'v1', kind: 'ConfigMap', @@ -46,8 +46,8 @@ export = { cluster.awsAuth.addAccount('5566776655'); // THEN - expect(stack).to(countResources(KubernetesResource.RESOURCE_TYPE, 1)); - expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(countResources(KubernetesManifest.RESOURCE_TYPE, 1)); + expect(stack).to(haveResource(KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -136,7 +136,7 @@ export = { cluster.awsAuth.addUserMapping(user, { groups: ['group2'] }); // THEN - expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -197,7 +197,7 @@ export = { cluster.awsAuth.addMastersRole(role); // THEN - expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 576e4081e83c0..6c1d01a1f02ba 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -368,7 +368,7 @@ export = { }); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -396,7 +396,7 @@ export = { test.done(); }, - 'addResource can be used to apply k8s manifests on this cluster'(test: Test) { + 'addManifest can be used to apply k8s manifests on this cluster'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { @@ -406,15 +406,15 @@ export = { }); // WHEN - cluster.addResource('manifest1', { foo: 123 }); - cluster.addResource('manifest2', { bar: 123 }, { boor: [1, 2, 3] }); + cluster.addManifest('manifest1', { foo: 123 }); + cluster.addManifest('manifest2', { bar: 123 }, { boor: [1, 2, 3] }); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: '[{"foo":123}]', })); - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: '[{"bar":123},{"boor":[1,2,3]}]', })); @@ -428,7 +428,7 @@ export = { // WHEN resource is under stack2 const stack2 = new cdk.Stack(app, 'stack2', { env: { account: stack.account, region: stack.region } }); - new eks.KubernetesResource(stack2, 'myresource', { + new eks.KubernetesManifest(stack2, 'myresource', { cluster, manifest: [{ foo: 'bar' }], }); @@ -473,7 +473,7 @@ export = { }); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -524,7 +524,7 @@ export = { }); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', @@ -1277,7 +1277,7 @@ export = { const sanitized = YAML.parse(fileContents); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify([sanitized]), })); test.done(); @@ -1290,7 +1290,7 @@ export = { // WHEN cluster.addFargateProfile('profile1', { selectors: [{ namespace: 'profile1' }] }); - cluster.addResource('resource1', { foo: 123 }); + cluster.addManifest('resource1', { foo: 123 }); cluster.addFargateProfile('profile2', { selectors: [{ namespace: 'profile2' }] }); new eks.HelmChart(stack, 'chart', { cluster, chart: 'mychart' }); cluster.addFargateProfile('profile3', { selectors: [{ namespace: 'profile3' }] }); @@ -1340,9 +1340,9 @@ export = { // WHEN // activate kubectl provider - c1.addResource('c1a', { foo: 123 }); - c1.addResource('c1b', { foo: 123 }); - c2.addResource('c2', { foo: 123 }); + c1.addManifest('c1a', { foo: 123 }); + c1.addManifest('c1b', { foo: 123 }); + c2.addManifest('c2', { foo: 123 }); // THEN const template = app.synth().getStackArtifact(stack.artifactId).template; @@ -1374,7 +1374,7 @@ export = { }, }); - cluster.addResource('resource', { + cluster.addManifest('resource', { kind: 'ConfigMap', apiVersion: 'v1', data: { @@ -1576,7 +1576,7 @@ export = { vpc, }); - cluster.addResource('resource', { + cluster.addManifest('resource', { kind: 'ConfigMap', apiVersion: 'v1', data: { @@ -1640,7 +1640,7 @@ export = { vpc: vpc2, }); - cluster.addResource('resource', { + cluster.addManifest('resource', { kind: 'ConfigMap', apiVersion: 'v1', data: { @@ -1689,7 +1689,7 @@ export = { vpcSubnets: [{subnetGroupName: 'Private1'}, {subnetGroupName: 'Private2'}], }); - cluster.addResource('resource', { + cluster.addManifest('resource', { kind: 'ConfigMap', apiVersion: 'v1', data: { @@ -1768,4 +1768,48 @@ export = { }, }, -}; \ No newline at end of file + + 'getServiceLoadBalancerAddress'(test: Test) { + + const { stack } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster1', { version: CLUSTER_VERSION }); + + const loadBalancerAddress = cluster.getServiceLoadBalancerAddress('myservice'); + + new cdk.CfnOutput(stack, 'LoadBalancerAddress', { + value: loadBalancerAddress, + }); + + const expectedKubernetesGetId = 'Cluster1myserviceLoadBalancerAddress198CCB03'; + + const rawTemplate = expect(stack).value; + + // make sure the custom resource is created correctly + test.deepEqual(rawTemplate.Resources[expectedKubernetesGetId].Properties, { + ServiceToken: { + 'Fn::GetAtt': [ + 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', + 'Outputs.StackawscdkawseksKubectlProviderframeworkonEvent8897FD9BArn', + ], + }, + ClusterName: { + Ref: 'Cluster1B02DD5A2', + }, + RoleArn: { + 'Fn::GetAtt': [ + 'Cluster1CreationRoleA231BE8D', + 'Arn', + ], + }, + ObjectType: 'service', + ObjectName: 'myservice', + ObjectNamespace: 'default', + JsonPath: '.status.loadBalancer.ingress[0].hostname', + TimeoutSeconds: 300, + }); + + // make sure the attribute points to the expected custom resource and extracts the correct attribute + test.deepEqual(rawTemplate.Outputs.LoadBalancerAddress.Value, { 'Fn::GetAtt': [ expectedKubernetesGetId, 'Value' ] }); + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-eks/test/test.manifest.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts similarity index 90% rename from packages/@aws-cdk/aws-eks/test/test.manifest.ts rename to packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts index 567a3d50abcde..d20608c3db847 100644 --- a/packages/@aws-cdk/aws-eks/test/test.manifest.ts +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-manifest.ts @@ -1,6 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Test } from 'nodeunit'; -import { Cluster, KubernetesResource, KubernetesVersion } from '../lib'; +import { Cluster, KubernetesManifest, KubernetesVersion } from '../lib'; import { testFixtureNoVpc } from './util'; /* eslint-disable max-len */ @@ -66,12 +66,12 @@ export = { ]; // WHEN - new KubernetesResource(stack, 'manifest', { + new KubernetesManifest(stack, 'manifest', { cluster, manifest, }); - expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(KubernetesManifest.RESOURCE_TYPE, { Manifest: JSON.stringify(manifest), })); test.done(); diff --git a/packages/@aws-cdk/aws-eks/test/test.k8s-object-value.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-object-value.ts new file mode 100644 index 0000000000000..96ba92f454dd0 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-object-value.ts @@ -0,0 +1,93 @@ +import { expect } from '@aws-cdk/assert'; +import { Stack, Duration } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as eks from '../lib'; +import { KubernetesObjectValue } from '../lib/k8s-object-value'; + +const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; + +export = { + + 'creates the correct custom resource with explicit values for all properties'(test: Test) { + // GIVEN + const stack = new Stack(); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); + + // WHEN + const attribute = new KubernetesObjectValue(stack, 'MyAttribute', { + cluster: cluster, + jsonPath: '.status', + objectName: 'mydeployment', + objectType: 'deployment', + objectNamespace: 'mynamespace', + timeout: Duration.seconds(5), + }); + + const expectedCustomResourceId = 'MyAttributeF1E9B10D'; + test.deepEqual(expect(stack).value.Resources[expectedCustomResourceId], { + Type: 'Custom::AWSCDK-EKS-KubernetesObjectValue', + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', + 'Outputs.awscdkawseksKubectlProviderframeworkonEvent0A650005Arn', + ], + }, + ClusterName: { Ref: 'MyCluster8AD82BF8' }, + RoleArn: { 'Fn::GetAtt': [ 'MyClusterCreationRoleB5FA4FF3', 'Arn' ] }, + ObjectType: 'deployment', + ObjectName: 'mydeployment', + ObjectNamespace: 'mynamespace', + JsonPath: '.status', + TimeoutSeconds: 5, + }, + DependsOn: [ 'MyClusterKubectlReadyBarrier7547948A' ], + UpdateReplacePolicy: 'Delete', + DeletionPolicy: 'Delete', + }); + + test.deepEqual(stack.resolve(attribute.value), { 'Fn::GetAtt': [ expectedCustomResourceId, 'Value' ] }); + test.done(); + }, + + 'creates the correct custom resource with defaults'(test: Test) { + // GIVEN + const stack = new Stack(); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); + + // WHEN + const attribute = new KubernetesObjectValue(stack, 'MyAttribute', { + cluster: cluster, + jsonPath: '.status', + objectName: 'mydeployment', + objectType: 'deployment', + }); + + const expectedCustomResourceId = 'MyAttributeF1E9B10D'; + test.deepEqual(expect(stack).value.Resources[expectedCustomResourceId], { + Type: 'Custom::AWSCDK-EKS-KubernetesObjectValue', + Properties: { + ServiceToken: { + 'Fn::GetAtt': [ + 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', + 'Outputs.awscdkawseksKubectlProviderframeworkonEvent0A650005Arn', + ], + }, + ClusterName: { Ref: 'MyCluster8AD82BF8' }, + RoleArn: { 'Fn::GetAtt': [ 'MyClusterCreationRoleB5FA4FF3', 'Arn' ] }, + ObjectType: 'deployment', + ObjectName: 'mydeployment', + ObjectNamespace: 'default', + JsonPath: '.status', + TimeoutSeconds: 300, + }, + DependsOn: [ 'MyClusterKubectlReadyBarrier7547948A' ], + UpdateReplacePolicy: 'Delete', + DeletionPolicy: 'Delete', + }); + + test.deepEqual(stack.resolve(attribute.value), { 'Fn::GetAtt': [ expectedCustomResourceId, 'Value' ] }); + test.done(); + }, + +}; diff --git a/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts b/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts index 625f9f8950c32..f70827649b7ef 100644 --- a/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.legacy-cluster.ts @@ -332,7 +332,7 @@ export = { }); // THEN - expect(stack).to(not(haveResource(eks.KubernetesResource.RESOURCE_TYPE))); + expect(stack).to(not(haveResource(eks.KubernetesManifest.RESOURCE_TYPE))); test.done(); }, @@ -351,7 +351,7 @@ export = { }); // THEN - expect(stack).to(not(haveResource(eks.KubernetesResource.RESOURCE_TYPE))); + expect(stack).to(not(haveResource(eks.KubernetesManifest.RESOURCE_TYPE))); test.done(); }, @@ -501,7 +501,7 @@ export = { }); // THEN - expect(stack).notTo(haveResource(eks.KubernetesResource.RESOURCE_TYPE)); + expect(stack).notTo(haveResource(eks.KubernetesManifest.RESOURCE_TYPE)); test.done(); }, diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index 4a0b4dbbee697..87ba5216cbbbf 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -67,7 +67,7 @@ export = { // THEN // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { Manifest: { 'Fn::Join': [ '', diff --git a/packages/@aws-cdk/aws-eks/test/test.service-account.ts b/packages/@aws-cdk/aws-eks/test/test.service-account.ts index 06c17a6bdd1a0..df60b6d1dadfa 100644 --- a/packages/@aws-cdk/aws-eks/test/test.service-account.ts +++ b/packages/@aws-cdk/aws-eks/test/test.service-account.ts @@ -16,7 +16,7 @@ export = { new eks.ServiceAccount(stack, 'MyServiceAccount', { cluster }); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { ServiceToken: { 'Fn::GetAtt': [ 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', @@ -74,7 +74,7 @@ export = { cluster.addServiceAccount('MyOtherServiceAccount'); // THEN - expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + expect(stack).to(haveResource(eks.KubernetesManifest.RESOURCE_TYPE, { ServiceToken: { 'Fn::GetAtt': [ 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', From 83a711883c136bb557220080b37fbd0ebf724e44 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Fri, 14 Aug 2020 03:59:21 -0700 Subject: [PATCH 017/422] chore(cli): bump up jest and ts-jest versions in TypeScript and JavaScript init templates (#9692) updating the package.json templates in JavaScript and TypeScript templates with the versions in our repo. ran the integ init template scripts for TypeScript and JavaScript templates successfully. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../init-templates/app/javascript/package.template.json | 2 +- .../init-templates/app/typescript/package.template.json | 8 ++++---- .../init-templates/lib/typescript/package.template.json | 8 ++++---- .../sample-app/javascript/package.template.json | 2 +- .../sample-app/typescript/package.template.json | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/aws-cdk/lib/init-templates/app/javascript/package.template.json b/packages/aws-cdk/lib/init-templates/app/javascript/package.template.json index 541f789edcc59..f4ce298e9069f 100644 --- a/packages/aws-cdk/lib/init-templates/app/javascript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/app/javascript/package.template.json @@ -12,7 +12,7 @@ "devDependencies": { "@aws-cdk/assert": "%cdk-version%", "aws-cdk": "%cdk-version%", - "jest": "^25.5.0" + "jest": "^26.0.4" }, "dependencies": { "@aws-cdk/core": "%cdk-version%" diff --git a/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json index 7f93485a1cc23..37f6790e66a58 100644 --- a/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json @@ -12,10 +12,10 @@ }, "devDependencies": { "@aws-cdk/assert": "%cdk-version%", - "@types/jest": "^25.2.1", - "@types/node": "10.17.5", - "jest": "^25.5.0", - "ts-jest": "^25.3.1", + "@types/jest": "^26.0.4", + "@types/node": "10.17.27", + "jest": "^26.0.4", + "ts-jest": "^26.1.3", "aws-cdk": "%cdk-version%", "ts-node": "^8.1.0", "typescript": "~3.7.2" diff --git a/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json index fcd17e5807146..c0238581cab18 100644 --- a/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json @@ -10,10 +10,10 @@ }, "devDependencies": { "@aws-cdk/assert": "%cdk-version%", - "@types/jest": "^25.2.1", - "@types/node": "10.17.5", - "jest": "^25.5.0", - "ts-jest": "^25.3.1", + "@types/jest": "^26.0.4", + "@types/node": "10.17.27", + "jest": "^26.0.4", + "ts-jest": "^26.1.3", "typescript": "~3.7.2" }, "peerDependencies": { diff --git a/packages/aws-cdk/lib/init-templates/sample-app/javascript/package.template.json b/packages/aws-cdk/lib/init-templates/sample-app/javascript/package.template.json index 08922350d206a..54633953bf6a9 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/javascript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/sample-app/javascript/package.template.json @@ -12,7 +12,7 @@ "devDependencies": { "@aws-cdk/assert": "%cdk-version%", "aws-cdk": "%cdk-version%", - "jest": "^25.5.0" + "jest": "^26.0.4" }, "dependencies": { "@aws-cdk/aws-sns": "%cdk-version%", diff --git a/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json index 8659f97094fdf..9c2c0e75aba6e 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json @@ -13,10 +13,10 @@ "devDependencies": { "aws-cdk": "%cdk-version%", "@aws-cdk/assert": "%cdk-version%", - "@types/jest": "^25.2.1", - "@types/node": "10.17.5", - "jest": "^25.5.0", - "ts-jest": "^25.3.1", + "@types/jest": "^26.0.4", + "@types/node": "10.17.27", + "jest": "^26.0.4", + "ts-jest": "^26.1.3", "ts-node": "^8.1.0", "typescript": "~3.7.2" }, From 58aad7895cef457c66dd18b694d262330801745e Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 14 Aug 2020 12:29:19 +0100 Subject: [PATCH 018/422] chore(cognito): emailTransmission is actually emailSettings (#9697) closes #9693 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-cognito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index b4f5a86dc5c26..6821d37f3f8f7 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -296,7 +296,7 @@ Read more about [email settings here](https://docs.aws.amazon.com/cognito/latest ```ts new UserPool(this, 'myuserpool', { // ... - emailTransmission: { + emailSettings: { from: 'noreply@myawesomeapp.com', replyTo: 'support@myawesomeapp.com', }, From 13d3534399b1931fb18e68f0138b76f83920119d Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 14 Aug 2020 15:09:33 +0100 Subject: [PATCH 019/422] chore: formatting fixes to DESIGN_GUIDELINES.md (#9640) I noticed the most recent update/port of the guidelines contained several formatting errors, mostly around lists. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- DESIGN_GUIDELINES.md | 156 ++++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/DESIGN_GUIDELINES.md b/DESIGN_GUIDELINES.md index 807528f8f6d42..6a3da024723c4 100644 --- a/DESIGN_GUIDELINES.md +++ b/DESIGN_GUIDELINES.md @@ -134,10 +134,10 @@ If all props are optional, the `props` argument must also be optional _[awslint:construct-ctor-props-optional]_. ```ts -constructor(scope: cdk.Construct, id: string, props: FooProps = { }) +constructor(scope: cdk.Construct, id: string, props: FooProps = {}) ``` -> Using `={}` as a default value is preferable to using an optional qualifier +> Using `= {}` as a default value is preferable to using an optional qualifier (`?`) since it will ensure that props will never be `undefined` and therefore easier to parse in the method body. @@ -203,13 +203,13 @@ expanding it's surface area. It also allows the **Key** class to change it's behavior (i.e. add an IAM action to enable encryption of certain types of keys) without affecting the API of the consumer. -#### owned vs. unowned constructs +#### Owned vs. Unowned Constructs Using object references instead of attribute references provides a richer API, but also introduces an inherent challenge: how do we reference constructs that are not defined inside the same app (“**owned**” by the app). These could be resources that were created by some other AWS CDK app, via the AWS console, -etc. We call these “**unowned**”**constructs.** +etc. We call these **“unowned” constructs.** In order to model this concept of owned and unowned constructs, all constructs in the AWS Construct Library should always have a corresponding **construct @@ -233,7 +233,7 @@ AWS Construct Library) should extend **IResource** (which, transitively, extends #### Abstract Base It is recommended to implement an abstract base class **FooBase** for each -resource **Foo****. **The base class would normally implement the entire +resource **Foo**. The base class would normally implement the entire construct interface and leave attributes as abstract properties. ```ts @@ -289,7 +289,7 @@ through a declarative interface [_awslint:props-coverage_]. This section describes guidelines for construct props. -#### types +#### Types Use **strong types** (and specifically, construct interfaces) instead of physical attributes when referencing other resources. For example, instead of @@ -306,14 +306,18 @@ be avoided wherever possible [_awslint:props-no-tokens_]. **deCDK** allows users to synthesize CDK stacks through a CloudFormation-like template, similar to SAM. CDK constructs are represented in deCDK templates like CloudFormation resources. Technically, this means that when a construct - is defined, users supply an ID, type and a set of properties.****In order to + is defined, users supply an ID, type and a set of properties. In order to allow users to instantiate all AWS Construct Library constructs through the deCDK syntax, we pose restrictions on prop types _[awslint:props-decdk]_: -* Primitives (string, number, boolean, date) Collections (list, map) Structs -* Enums Enum-like classes Union-like classes References to other constructs -* (through their construct interface) Integration interfaces (interfaces that -* have a “**bind**” method) +* Primitives (string, number, boolean, date) +* Collections (list, map) +* Structs +* Enums +* Enum-like classes +* Union-like classes +* References to other constructs (through their construct interface) +* Integration interfaces (interfaces that have a “**bind**” method) #### Defaults @@ -331,7 +335,7 @@ from harnessing the full power of the resource, and customizing its behavior. The **@default** documentation tag must be included on all optional properties of interfaces. Since there are cases where the default behavior is not a specific value but rather depends on circumstances/context, the default -documentation tag must always begin with a **“**-"**** and then include a +documentation tag must always begin with a “**-**" and then include a description of the default behavior _[awslint:props-default-doc]_. For example: @@ -649,10 +653,11 @@ export interface IFoo { Notice that: -* The method has an “add” prefix. It implies that users are adding something to -* their stack. The method is implemented on the construct interface (to allow -* adding secondary resources to unowned constructs). The method returns a “Bar” -* instance (owned). +* The method has an “add” prefix. + It implies that users are adding something to their stack. +* The method is implemented on the construct interface + (to allow adding secondary resources to unowned constructs). +* The method returns a “Bar” instance (owned). In order to reuse the set of props used to configure the secondary resource, define a base interface for **FooProps** called **FooOptions** to allow @@ -682,12 +687,12 @@ their app. The signature of all “from” methods should adhere to the following rules _[awslint:from-signature]_: -* First argument must be **scope** of type **Construct** Second argument is a -* **string**. This string will be used to determine the ID of the new -* construct. If the import method uses some value that is promised to be unique -* within the stack scope (such as ARN, export name), this value can be reused as -* the construct ID. Returns an object that implements the construct interface -* (**IFoo**). +* First argument must be **scope** of type **Construct** +* Second argument is a **string**. This string will be used to determine the + ID of the new construct. If the import method uses some value that is + promised to be unique within the stack scope (such as ARN, export name), + this value can be reused as the construct ID. +* Returns an object that implements the construct interface (**IFoo**). #### “from” Methods @@ -792,8 +797,6 @@ If the construct is unowned this method should no-op and issue a **permissions notice** (TODO) to the user indicating that they should ensure that the role of this resource should have the specified permission. -TODO: add a few sentences on grantable - Implementing **IGrantable** brings an implementation burden of **grantPrincipal: IPrincipal**. This property must be set to the **role** if available, or to a new **iam.ImportedResourcePrincipal** if the resource is imported and the role @@ -897,8 +900,9 @@ cases. For example, **dynamodb.Table.grantPutItem**, **s3.Bucket.grantReadWrite**, etc. In such cases, the signature of the grant method should adhere to the following rules _[awslint:grant-signature]_: -1. Name should have a “grant” prefix 2. Returns an **iam.Grant** object 3. First -argument must be **grantee: iam.IGrantable** +1. Name should have a “grant” prefix +2. Returns an **iam.Grant** object +3. First argument must be **grantee: iam.IGrantable** ```ts grantXxx(grantee: iam.IGrantable): iam.Grant; @@ -941,9 +945,9 @@ metric(metricName: string, options?: cloudwatch.MetricOptions): cloudwatch.Metri Additional metric methods should be exposed with the official metric name as a suffix and adhere to the following rules _[awslint:metrics-method-signature]:_ -* Name should be “metricXxx” where “Xxx” is the official metric name Accepts a -* single “options” argument of type **MetricOptions** Returns a **Metric** -* object. +* Name should be “metricXxx” where “Xxx” is the official metric name +* Accepts a single “options” argument of type **MetricOptions** +* Returns a **Metric** object. ```ts interface IFunction { @@ -1034,9 +1038,9 @@ interface IEventSource { A method “addXxx” should be defined on the construct interface and adhere to the following rules _[awslint:integrations-add-method]:_ -* Should accept any object that implements the integrations interface Should not -* return anything (void) Implementation should call “bind” on the integration -* object +* Should accept any object that implements the integrations interface +* Should not return anything (void) +* Implementation should call “bind” on the integration object ```ts interface IFunction extends IResource { @@ -1123,11 +1127,11 @@ property **stateful** which returns **true** or **false** to allow runtime checks query whether a resource is persistent _[awslint:state-stateful-property]_. -### Physical Names (NEW) - TODO +### Physical Names - TODO See -### Tags (NEW) +### Tags The AWS platform has a powerful tagging system that can be used to tag resources with key/values. The AWS CDK exposes this capability through the **Tag** @@ -1141,7 +1145,7 @@ myConstruct.node.apply(new cdk.Tag("myKey", "myValue")); Constructs for AWS resources that can be tagged must have an optional **tags** hash in their props [_awslint:tags-prop_]. -### Secrets (NEW) +### Secrets If you expect a secret in your API (such as passwords, tokens), use the **cdk.SecretValue** class to signal to users that they should not include @@ -1155,10 +1159,11 @@ use the SecretValue type [_awslint:secret-token_]. ### Code Organization -* Code should be under `lib/` Entry point should be `lib/index.ts` and should -* only contain “imports” for other files. No need to put every class in a -* separate file. Try to think of a reader-friendly organization of your source -* files. +* Code should be under `lib/` +* Entry point should be `lib/index.ts` and should only contain “imports” + for other files. +* No need to put every class in a separate file. Try to think of a + reader-friendly organization of your source files. ## Implementation @@ -1167,11 +1172,14 @@ implementation of AWS constructs. ### General Principles -* Do not future proof No fluent APIs Good APIs “speak” in the language of the -* user. The terminology your API uses should be intuitive and represent the -* mental model your user brings over, not one that you made up and you force -* them to learn. Multiple ways of achieving the same thing is legitimate -* Constantly maintain the invariants Fewer “if statements” the better +* Do not future proof. +* No fluent APIs. +* Good APIs “speak” in the language of the user. The terminology your API uses + should be intuitive and represent the mental model your user brings over, + not one that you made up and you force them to learn. +* Multiple ways of achieving the same thing is legitimate. +* Constantly maintain the invariants. +* Fewer “if statements” the better. ### Construct IDs @@ -1208,18 +1216,20 @@ for (const az of availabilityZones) { ### Errors -#### input validation +#### Input Validation Prefer to validate input as early as it is passed into your code (ctor, methods, etc) and bail out by throwing an **Error** (no need to create subclasses of Error since all errors in the CDK are unrecoverable): * All lowercase sentences (usually they are printed after “Error: \”) -* Include a descriptive message Include the value provided Include the -* expected/allowed values No need to include information that can be obtained -* from the stack trace. No need to add a period at the end of error messages. +* Include a descriptive message +* Include the value provided +* Include the expected/allowed values +* No need to include information that can be obtained from the stack trace. +* No need to add a period at the end of error messages. -#### avoid errors if possible +#### Avoid Errors if Possible Always prefer to do the right thing for the user instead of raising an error. Only fail if the user has explicitly specified bad configuration. For @@ -1227,7 +1237,7 @@ example, VPC has **enableDnsHostnames** and **enableDnsSupport**. DNS hostnames *require* DNS support, so only fail if the user enabled DNS hostnames but explicitly disabled DNS support. Otherwise, auto-enable DNS support for them. -#### Never catch exceptions +#### Never Catch Exceptions All CDK errors are unrecoverable. If a method wishes to signal a recoverable error, this should be modeled in a return value and not through exceptions. @@ -1238,12 +1248,12 @@ In the rare case where the integrity of your construct can only be checked right before synthesis, override the **Construct.validate()** method and return meaningful errors. Always prefer early input validation over post-validation. -#### attached Errors/warnings +#### Attached Errors/Warnings You can also “attach” an error or a warning to a construct via -**node.addWarning(s)** or **node.addError(s)**. These methods will attach CDK -metadata to your construct, which will be displayed to the user by the toolchain -when the stack is deployed. +the **Annotations** class. These methods (e.g., `Annotations.of(construct).addWarning`) +will attach CDK metadata to your construct, which will be displayed to the user +by the toolchain when the stack is deployed. Errors will not allow deployment and warnings will only be displayed in highlight (unless **--strict** mode is used). @@ -1274,20 +1284,22 @@ Use the following JSDoc tags: **@param**, **@returns**, **@default**, **@see**, ### Readme -* Header should include maturity level Example for the simple use case should be -* almost the first thing If there are multiple common use cases, provide an -* example for each one and describe what happens under the hood at a high level -* (e.g. which resources are created). Reference docs are not needed Use -* literate (`.lit.ts`) integration tests into README file (see example in +* Header should include maturity level. +* Example for the simple use case should be almost the first thing. +* If there are multiple common use cases, provide an example for each one and + describe what happens under the hood at a high level + (e.g. which resources are created). +* Reference docs are not needed. +* Use literate (`.lit.ts`) integration tests into README file. ## Testing ### Unit tests * Unit test utility functions and object models separately from constructs. If -* you want them to be “package-private”, just put them in a separate file and -* import `../lib/my-util` from your unit test code. Failing tests should be -* prefixed with “fails” + you want them to be “package-private”, just put them in a separate file and + import `../lib/my-util` from your unit test code. +* Failing tests should be prefixed with “fails” ### Integration tests @@ -1302,14 +1314,18 @@ Use the following JSDoc tags: **@param**, **@returns**, **@default**, **@see**, ### Naming Conventions -* **Class names**: PascalCase Properties**: camelCase Methods (static and -* **non-static)**: camelCase Interfaces** (“behavioral interface”) : -* **IMyInterface Structs** (“data interfaces”): MyDataStruct Enums**: -* **PascalCase,**Members**: SNAKE_UPPER +* **Class names**: PascalCase +* **Properties**: camelCase +* **Methods (static and non-static)**: camelCase +* **Interfaces** (“behavioral interface”): IMyInterface +* **Structs** (“data interfaces”): MyDataStruct +* **Enums**: PascalCase, **Members**: SNAKE_UPPER ### Coding Style -* **Indentation**: 2 spaces Line length**: 150 String literals**: use -* **single-quotes (`'`) or backticks (```) Semicolons**: at the end of each code -* **statement and declaration (incl. properties and imports). Comments**: start -* **with lower-case, end with a period. +* **Indentation**: 2 spaces +* **Line length**: 150 +* **String literals**: use single-quotes (`'`) or backticks (```) +* **Semicolons**: at the end of each code statement and declaration + (incl. properties and imports). +* **Comments**: start with lower-case, end with a period. From 1c4eae8e4052e7d56d944006577ee1d78785781a Mon Sep 17 00:00:00 2001 From: flemjame-at-amazon <57235867+flemjame-at-amazon@users.noreply.github.com> Date: Fri, 14 Aug 2020 10:31:26 -0400 Subject: [PATCH 020/422] fix(ec2): can't use imported Subnets in a SubnetSelection (#9579) ---- Closes https://github.com/aws/aws-cdk/issues/8301 *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 2 +- packages/@aws-cdk/aws-ec2/test/vpc.test.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index daaba4cd8a05f..55dbd5cb53533 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -345,7 +345,7 @@ abstract class VpcBase extends Resource implements IVpc { return { subnetIds: subnets.map(s => s.subnetId), - availabilityZones: subnets.map(s => s.availabilityZone), + get availabilityZones(): string[] { return subnets.map(s => s.availabilityZone); }, internetConnectivityEstablished: tap(new CompositeDependable(), d => subnets.forEach(s => d.add(s.internetConnectivityEstablished))), subnets, hasPublic: subnets.some(s => pubs.has(s)), diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 113a8eb7edd56..08bade38865bc 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -1364,7 +1364,21 @@ nodeunitShim({ })); test.done(); }, + 'SubnetSelection doesnt throw error when selecting imported subnets'(test: Test) { + // GIVEN + const stack = getTestStack(); + + // WHEN + const vpc = new Vpc(stack, 'VPC'); + // THEN + test.doesNotThrow(() => vpc.selectSubnets({ + subnets: [ + Subnet.fromSubnetId(stack, 'Subnet', 'sub-1'), + ], + })); + test.done(); + }, }, }); From 5a64bc518868301eb4c5ce8d2964d62d4a79a764 Mon Sep 17 00:00:00 2001 From: Barun Ray Date: Fri, 14 Aug 2020 22:31:38 +0530 Subject: [PATCH 021/422] feat(codeguruprofiler): add support for ComputePlatform in ProfilingGroup (#9391) fixes https://github.com/aws/aws-cdk/issues/9285 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-codeguruprofiler/README.md | 11 +++++++++ .../lib/profiling-group.ts | 24 +++++++++++++++++++ .../test/profiling-group.test.ts | 16 +++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-codeguruprofiler/README.md b/packages/@aws-cdk/aws-codeguruprofiler/README.md index 5fcb3137d296b..cf18c19ae296a 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/README.md +++ b/packages/@aws-cdk/aws-codeguruprofiler/README.md @@ -32,3 +32,14 @@ const publishAppRole = new Role(stack, 'PublishAppRole', { const profilingGroup = new ProfilingGroup(stack, 'MyProfilingGroup'); profilingGroup.grantPublish(publishAppRole); ``` + +### Compute Platform configuration + +Code Guru Profiler supports multiple compute environments. +They can be configured when creating a Profiling Group by using the `computePlatform` property: + +```ts +const profilingGroup = new ProfilingGroup(stack, 'MyProfilingGroup', { + computePlatform: ComputePlatform.AWS_LAMBDA, +}); +``` diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts index f4d356e093204..2a28fdcaa7822 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -2,6 +2,22 @@ import { Grant, IGrantable } from '@aws-cdk/aws-iam'; import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { CfnProfilingGroup } from './codeguruprofiler.generated'; +/** + * The compute platform of the profiling group. + */ +export enum ComputePlatform { + /** + * Use AWS_LAMBDA if your application runs on AWS Lambda. + */ + AWS_LAMBDA = 'AWSLambda', + + /** + * Use Default if your application runs on a compute platform that is not AWS Lambda, + * such an Amazon EC2 instance, an on-premises server, or a different platform. + */ + DEFAULT = 'Default', +} + /** * IResource represents a Profiling Group. */ @@ -97,6 +113,13 @@ export interface ProfilingGroupProps { */ readonly profilingGroupName?: string; + /** + * The compute platform of the profiling group. + * + * @default ComputePlatform.DEFAULT + */ + readonly computePlatform?: ComputePlatform; + } /** @@ -158,6 +181,7 @@ export class ProfilingGroup extends ProfilingGroupBase { const profilingGroup = new CfnProfilingGroup(this, 'ProfilingGroup', { profilingGroupName: this.physicalName, + computePlatform: props.computePlatform, }); this.profilingGroupName = this.getResourceNameAttribute(profilingGroup.ref); diff --git a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts index 8b26f916d26f1..27dc281589172 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts @@ -1,7 +1,7 @@ -import { expect } from '@aws-cdk/assert'; +import { expect, haveResourceLike } from '@aws-cdk/assert'; import { AccountRootPrincipal, Role } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; -import { ProfilingGroup } from '../lib'; +import { ProfilingGroup, ComputePlatform } from '../lib'; /* eslint-disable quote-props */ @@ -188,6 +188,18 @@ describe('profiling group', () => { }); }); + test('allows setting its ComputePlatform', () => { + const stack = new Stack(); + new ProfilingGroup(stack, 'MyProfilingGroup', { + profilingGroupName: 'MyAwesomeProfilingGroup', + computePlatform: ComputePlatform.AWS_LAMBDA, + }); + + expect(stack).to(haveResourceLike('AWS::CodeGuruProfiler::ProfilingGroup', { + 'ComputePlatform': 'AWSLambda', + })); + }); + test('default profiling group without name', () => { const stack = new Stack(); new ProfilingGroup(stack, 'MyProfilingGroup', { From 88709fee8edf690c22c25631aaf8d42f7a8b749e Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Fri, 14 Aug 2020 11:06:10 -0700 Subject: [PATCH 022/422] chore(assert): bump up jest version to align with @types/jest version (#9707) we bumped up @types/jest in this module to `^26.0.4` but the `jest` version is not aligned recent change made to init templates in #9692 errors on dev dependencies not being met. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assert/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 3ba471e665353..b5f55e1b3d389 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@types/jest": "^26.0.4", "cdk-build-tools": "0.0.0", - "jest": "^25.5.4", + "jest": "^26.0.4", "pkglint": "0.0.0", "ts-jest": "^26.1.3" }, From 3bc44a2ba47ddb9b4cd3bde87a2ac964598ac2cc Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Fri, 14 Aug 2020 11:34:57 -0700 Subject: [PATCH 023/422] chore(cli): bump up typescript version for TS init templates (#9708) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/init-templates/app/typescript/package.template.json | 2 +- .../lib/init-templates/lib/typescript/package.template.json | 2 +- .../init-templates/sample-app/typescript/package.template.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json index 37f6790e66a58..e84da8f2739cc 100644 --- a/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/app/typescript/package.template.json @@ -18,7 +18,7 @@ "ts-jest": "^26.1.3", "aws-cdk": "%cdk-version%", "ts-node": "^8.1.0", - "typescript": "~3.7.2" + "typescript": "~3.9.6" }, "dependencies": { "@aws-cdk/core": "%cdk-version%", diff --git a/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json index c0238581cab18..e3ae57482cfc5 100644 --- a/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/lib/typescript/package.template.json @@ -14,7 +14,7 @@ "@types/node": "10.17.27", "jest": "^26.0.4", "ts-jest": "^26.1.3", - "typescript": "~3.7.2" + "typescript": "~3.9.6" }, "peerDependencies": { "@aws-cdk/core": "%cdk-version%" diff --git a/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json b/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json index 9c2c0e75aba6e..7d199a6c68fc5 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json +++ b/packages/aws-cdk/lib/init-templates/sample-app/typescript/package.template.json @@ -18,7 +18,7 @@ "jest": "^26.0.4", "ts-jest": "^26.1.3", "ts-node": "^8.1.0", - "typescript": "~3.7.2" + "typescript": "~3.9.6" }, "dependencies": { "@aws-cdk/aws-sns": "%cdk-version%", From 30cb1e8603066669ef3209a1a49542497e4e6f8d Mon Sep 17 00:00:00 2001 From: Somaya Date: Fri, 14 Aug 2020 11:54:11 -0700 Subject: [PATCH 024/422] add new module to auto assign/label action workflow (#9611) --- .github/workflows/issue-label-assign.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/issue-label-assign.yml b/.github/workflows/issue-label-assign.yml index ba60295cd406b..4196811487aeb 100644 --- a/.github/workflows/issue-label-assign.yml +++ b/.github/workflows/issue-label-assign.yml @@ -45,6 +45,7 @@ jobs: {"keywords":["[@aws-cdk/aws-cloud9]","[aws-cloud9]","[cloud9]","[cloud 9]"],"labels":["@aws-cdk/aws-cloud9"],"assignees":["skinny85"]}, {"keywords":["[@aws-cdk/aws-cloudformation]","[aws-cloudformation]","[cloudformation]","[cloud formation]"],"labels":["@aws-cdk/aws-cloudformation"],"assignees":["eladb"]}, {"keywords":["[@aws-cdk/aws-cloudfront]","[aws-cloudfront]","[cloudfront]","[cloud front]"],"labels":["@aws-cdk/aws-cloudfront"],"assignees":["iliapolo"]}, + {"keywords":["[@aws-cdk/aws-cloudfront-origins]","[aws-cloudfront-origins]","[cloudfront-origins]","[cloudfront origins]"],"labels":["@aws-cdk/aws-cloudfront-origins"],"assignees":["iliapolo"]}, {"keywords":["[@aws-cdk/aws-cloudtrail]","[aws-cloudtrail]","[cloudtrail]","[cloud trail]"],"labels":["@aws-cdk/aws-cloudtrail"],"assignees":["rix0rrr"]}, {"keywords":["[@aws-cdk/aws-cloudwatch]","[aws-cloudwatch]","[cloudwatch]","[cloud watch]"],"labels":["@aws-cdk/aws-cloudwatch"],"assignees":["rix0rrr"]}, {"keywords":["[@aws-cdk/aws-cloudwatch-actions]","[aws-cloudwatch-actions]","[cloudwatch-actions]","[cloudwatch actions]"],"labels":["@aws-cdk/aws-cloudwatch-actions"],"assignees":["rix0rrr"]}, From 31789616d0590ef70715e2e375ee56b0613dda04 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Fri, 14 Aug 2020 13:07:31 -0700 Subject: [PATCH 025/422] chore(assert): missed updating peerDependencies when updating jest (#9722) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assert/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index b5f55e1b3d389..4d313e9a7002b 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -36,7 +36,7 @@ }, "peerDependencies": { "@aws-cdk/core": "0.0.0", - "jest": "^25.5.4", + "jest": "^26.0.4", "constructs": "^3.0.2" }, "repository": { From 691b349f55e8c8b52518ae40cac0ba3720c71ddf Mon Sep 17 00:00:00 2001 From: kaizen3031593 <36202692+kaizen3031593@users.noreply.github.com> Date: Fri, 14 Aug 2020 16:50:09 -0400 Subject: [PATCH 026/422] feat(synthetics): Synthetics L2 Support (#8824) This PR specifies an implementation for a CloudWatch Canary. It will be implemented in milestones specified below. Closes #7687. Currently, this PR implements up to **Milestone 2**. **Milestone 1**: This implementation allows customers to specify a canary with these properties: - [x] `artifactBucket: IBucket` - [x] `role: IRole` - [x] `timeToLive: Duration` - [x] `schedule: Schedule` - [x] `startAfterCreation: boolean` - [x] `successRetentionPeriod: Duration` - [x] `failureRetentionPeriod: Duration` - [x] `canaryName: string` And: - [x] generate `name` for users if not specified - [x] metric methods that reference those emitted by CloudWatch **Milestone 2**: This is the next step needed to allow customers to specify their own canary scripts: - [x] `test:Test` - [x] `Test` class with static method `Test.custom()` - [x] `Code` class with static method `Code.inline()` - [x] `Code.fromAsset()` - [x] validate correct folder structure for assets `nodejs/node_modules` - [x] validate correct file name - [x] `Code.fromBucket()` **Milestone 3**: These are features that make a more robust Canary API: - [ ] static method `Test.apiEndpoint()` - [ ] additional static method templates for `Test` - [ ] support runConfig properties - `memorySize` and `timeout` [README Rendered version](https://github.com/aws/aws-cdk/blob/feca976ed2dbd0dc1e2416210cc967fa53a4309a/packages/%40aws-cdk/aws-synthetics/README.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-synthetics/README.md | 136 +++++- .../images/endpoint-example.png | Bin 0 -> 11114 bytes .../aws-synthetics/images/ui-screenshot.png | Bin 0 -> 41774 bytes .../@aws-cdk/aws-synthetics/lib/canary.ts | 411 ++++++++++++++++++ packages/@aws-cdk/aws-synthetics/lib/code.ts | 183 ++++++++ packages/@aws-cdk/aws-synthetics/lib/index.ts | 4 + .../@aws-cdk/aws-synthetics/lib/schedule.ts | 50 +++ packages/@aws-cdk/aws-synthetics/package.json | 17 +- .../canaries/nodejs/node_modules/canary.js | 53 +++ .../aws-synthetics/test/canary.test.ts | 343 +++++++++++++++ .../@aws-cdk/aws-synthetics/test/canary.zip | Bin 0 -> 636 bytes .../@aws-cdk/aws-synthetics/test/code.test.ts | 143 ++++++ .../test/integ.asset.expected.json | 342 +++++++++++++++ .../aws-synthetics/test/integ.asset.ts | 32 ++ .../test/integ.canary.expected.json | 115 +++++ .../aws-synthetics/test/integ.canary.ts | 32 ++ .../aws-synthetics/test/metric.test.ts | 69 +++ .../aws-synthetics/test/synthetics.test.ts | 6 - 18 files changed, 1925 insertions(+), 11 deletions(-) create mode 100644 packages/@aws-cdk/aws-synthetics/images/endpoint-example.png create mode 100644 packages/@aws-cdk/aws-synthetics/images/ui-screenshot.png create mode 100644 packages/@aws-cdk/aws-synthetics/lib/canary.ts create mode 100644 packages/@aws-cdk/aws-synthetics/lib/code.ts create mode 100644 packages/@aws-cdk/aws-synthetics/lib/schedule.ts create mode 100644 packages/@aws-cdk/aws-synthetics/test/canaries/nodejs/node_modules/canary.js create mode 100644 packages/@aws-cdk/aws-synthetics/test/canary.test.ts create mode 100644 packages/@aws-cdk/aws-synthetics/test/canary.zip create mode 100644 packages/@aws-cdk/aws-synthetics/test/code.test.ts create mode 100644 packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json create mode 100644 packages/@aws-cdk/aws-synthetics/test/integ.asset.ts create mode 100644 packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json create mode 100644 packages/@aws-cdk/aws-synthetics/test/integ.canary.ts create mode 100644 packages/@aws-cdk/aws-synthetics/test/metric.test.ts delete mode 100644 packages/@aws-cdk/aws-synthetics/test/synthetics.test.ts diff --git a/packages/@aws-cdk/aws-synthetics/README.md b/packages/@aws-cdk/aws-synthetics/README.md index c69849f13882f..6b62b87db495e 100644 --- a/packages/@aws-cdk/aws-synthetics/README.md +++ b/packages/@aws-cdk/aws-synthetics/README.md @@ -1,4 +1,5 @@ -## AWS::Synthetics Construct Library +## Amazon CloudWatch Synthetics Construct Library + --- @@ -6,11 +7,142 @@ > All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +Amazon CloudWatch Synthetics allow you to monitor your application by generating **synthetic** traffic. The traffic is produced by a **canary**: a configurable script that runs on a schedule. You configure the canary script to follow the same routes and perform the same actions as a user, which allows you to continually verify your user experience even when you don't have any traffic on your applications. + +## Canary + +To illustrate how to use a canary, assume your application defines the following endpoint: + +```bash +% curl "https://api.example.com/user/books/topbook/" +The Hitchhikers Guide to the Galaxy + +``` + +The below code defines a canary that will hit the `books/topbook` endpoint every 5 minutes: ```ts import * as synthetics from '@aws-cdk/aws-synthetics'; + +const canary = new synthetics.Canary(this, 'MyCanary', { + schedule: synthetics.Schedule.rate(Duration.minutes(5)), + test: Test.custom({ + code: Code.fromAsset(path.join(__dirname, 'canary'))), + handler: 'index.handler', + }), +}); +``` + +The following is an example of an `index.js` file which exports the `handler` function: + +```js +var synthetics = require('Synthetics'); +const log = require('SyntheticsLogger'); + +const pageLoadBlueprint = async function () { + + // INSERT URL here + const URL = "https://api.example.com/user/books/topbook/"; + + let page = await synthetics.getPage(); + const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000}); + //Wait for page to render. + //Increase or decrease wait time based on endpoint being monitored. + await page.waitFor(15000); + // This will take a screenshot that will be included in test output artifacts + await synthetics.takeScreenshot('loaded', 'loaded'); + let pageTitle = await page.title(); + log.info('Page title: ' + pageTitle); + if (response.status() !== 200) { + throw "Failed to load page!"; + } +}; + +exports.handler = async () => { + return await pageLoadBlueprint(); +}; +``` + +> **Note:** The function **must** be called `handler`. + +The canary will automatically produce a CloudWatch Dashboard: + +![UI Screenshot](images/ui-screenshot.png) + +The Canary code will be executed in a lambda function created by Synthetics on your behalf. The Lambda function includes a custom [runtime](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Library.html) provided by Synthetics. The provided runtime includes a variety of handy tools such as [Puppeteer](https://www.npmjs.com/package/puppeteer-core) and Chromium. To learn more about Synthetics capabilities, check out the [docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html). + +### Configuring the Canary Script + +To configure the script the canary executes, use the `test` property. The `test` property accepts a `Test` instance that can be initialized by the `Test` class static methods. Currently, the only implemented method is `Test.custom()`, which allows you to bring your own code. In the future, other methods will be added. `Test.custom()` accepts `code` and `handler` properties -- both are required by Synthetics to create a lambda function on your behalf. + +The `synthetics.Code` class exposes static methods to bundle your code artifacts: + + - `code.fromInline(code)` - specify an inline script. + - `code.fromAsset(path)` - specify a .zip file or a directory in the local filesystem which will be zipped and uploaded to S3 on deployment. See the above Note for directory structure. + - `code.fromBucket(bucket, key[, objectVersion])` - specify an S3 object that contains the .zip file of your runtime code. See the above Note for directory structure. + +Using the `Code` class static initializers: + +```ts +// To supply the code inline: +const canary = new Canary(this, 'MyCanary', { + test: Test.custom({ + code: Code.fromInline('/* Synthetics handler code */'), + handler: 'index.handler', // must be 'index.handler' + }), +}); + +// To supply the code from your local filesystem: +const canary = new Canary(this, 'MyCanary', { + test: Test.custom({ + code: Code.fromAsset(path.join(__dirname, 'canary')), + handler: 'index.handler', // must end with '.handler' + }), +}); + +// To supply the code from a S3 bucket: +const canary = new Canary(this, 'MyCanary', { + test: Test.custom({ + code: Code.fromBucket(bucket, 'canary.zip'), + handler: 'index.handler', // must end with '.handler' + }), +}); +``` + +> **Note:** For `code.fromAsset()` and `code.fromBucket()`, the canary resource requires the following folder structure: +>``` +>canary/ +>├── nodejs/ +> ├── node_modules/ +> ├── .js +>``` +> See Synthetics [docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html). + +### Alarms + +You can configure a CloudWatch Alarm on a canary metric. Metrics are emitted by CloudWatch automatically and can be accessed by the following APIs: +- `canary.metricSuccessPercent()` - percentage of successful canary runs over a given time +- `canary.metricDuration()` - how much time each canary run takes, in seconds. +- `canary.metricFailed()` - number of failed canary runs over a given time + +Create an alarm that tracks the canary metric: + +```ts +new cloudwatch.Alarm(this, 'CanaryAlarm', { + metric: canary.metricSuccessPercent(), + evaluationPeriods: 2, + threshold: 90, + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, +}); ``` + +### Future Work + +- Add blueprints to the Test class [#9613](https://github.com/aws/aws-cdk/issues/9613#issue-677134857). diff --git a/packages/@aws-cdk/aws-synthetics/images/endpoint-example.png b/packages/@aws-cdk/aws-synthetics/images/endpoint-example.png new file mode 100644 index 0000000000000000000000000000000000000000..31ee9fcdbb2e4bd24d8529d6df16b808b28f5c28 GIT binary patch literal 11114 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 Ar~m)} literal 0 HcmV?d00001 diff --git a/packages/@aws-cdk/aws-synthetics/images/ui-screenshot.png b/packages/@aws-cdk/aws-synthetics/images/ui-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..239825fa9bb14c02758bfd4f33d12cd0399aa8f6 GIT binary patch literal 41774 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 I%8mQ~2SU_zQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/@aws-cdk/aws-synthetics/lib/canary.ts b/packages/@aws-cdk/aws-synthetics/lib/canary.ts new file mode 100644 index 0000000000000..e819ff40c56c5 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/lib/canary.ts @@ -0,0 +1,411 @@ +import * as crypto from 'crypto'; +import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { Code } from './code'; +import { Schedule } from './schedule'; +import { CfnCanary } from './synthetics.generated'; + +/** + * Specify a test that the canary should run + */ +export class Test { + /** + * Specify a custom test with your own code + * + * @returns `Test` associated with the specified Code object + * @param options The configuration options + */ + public static custom(options: CustomTestOptions): Test { + Test.validateHandler(options.handler); + return new Test(options.code, options.handler); + } + + /** + * Verifies that the given handler ends in '.handler'. Returns the handler if successful and + * throws an error if not. + * + * @param handler - the handler given by the user + */ + private static validateHandler(handler: string) { + if (!handler.endsWith('.handler')) { + throw new Error(`Canary Handler must end in '.handler' (${handler})`); + } + if (handler.length > 21) { + throw new Error(`Canary Handler must be less than 21 characters (${handler})`); + } + } + + /** + * Construct a Test property + * + * @param code The code that the canary should run + * @param handler The handler of the canary + */ + private constructor(public readonly code: Code, public readonly handler: string){ + } +} + +/** + * Properties for specifying a test + */ +export interface CustomTestOptions { + /** + * The code of the canary script + */ + readonly code: Code, + + /** + * The handler for the code. Must end with `.handler`. + */ + readonly handler: string, +} + +/** + * Runtime options for a canary + */ +export class Runtime { + /** + * `syn-1.0` includes the following: + * + * - Synthetics library 1.0 + * - Synthetics handler code 1.0 + * - Lambda runtime Node.js 10.x + * - Puppeteer-core version 1.14.0 + * - The Chromium version that matches Puppeteer-core 1.14.0 + */ + public static readonly SYNTHETICS_1_0 = new Runtime('syn-1.0'); + + /** + * @param name The name of the runtime version + */ + public constructor(public readonly name: string){ + } +} + +/** + * Options for specifying the s3 location that stores the data of each canary run. The artifacts bucket location **cannot** + * be updated once the canary is created. + */ +export interface ArtifactsBucketLocation { + /** + * The s3 location that stores the data of each run. + */ + readonly bucket: s3.IBucket; + + /** + * The S3 bucket prefix. Specify this if you want a more specific path within the artifacts bucket. + * + * @default - no prefix + */ + readonly prefix?: string; +} + +/** + * Properties for a canary + */ +export interface CanaryProps { + /** + * The s3 location that stores the data of the canary runs. + * + * @default - A new s3 bucket will be created without a prefix. + */ + readonly artifactsBucketLocation?: ArtifactsBucketLocation; + + /** + * Canary execution role. + * + * This is the role that will be assumed by the canary upon execution. + * It controls the permissions that the canary will have. The role must + * be assumable by the AWS Lambda service principal. + * + * If not supplied, a role will be created with all the required permissions. + * If you provide a Role, you must add the required permissions. + * + * @see required permissions: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-executionrolearn + * + * @default - A unique role will be generated for this canary. + * You can add permissions to roles by calling 'addToRolePolicy'. + */ + readonly role?: iam.IRole; + + /** + * How long the canary will be in a 'RUNNING' state. For example, if you set `timeToLive` to be 1 hour and `schedule` to be `rate(10 minutes)`, + * your canary will run at 10 minute intervals for an hour, for a total of 6 times. + * + * @default - no limit + */ + readonly timeToLive?: cdk.Duration; + + /** + * Specify the schedule for how often the canary runs. For example, if you set `schedule` to `rate(10 minutes)`, then the canary will run every 10 minutes. + * You can set the schedule with `Schedule.rate(Duration)` (recommended) or you can specify an expression using `Schedule.expression()`. + * @default 'rate(5 minutes)' + */ + readonly schedule?: Schedule; + + /** + * Whether or not the canary should start after creation. + * + * @default true + */ + readonly startAfterCreation?: boolean; + + /** + * How many days should successful runs be retained. + * + * @default Duration.days(31) + */ + readonly successRetentionPeriod?: cdk.Duration; + + /** + * How many days should failed runs be retained. + * + * @default Duration.days(31) + */ + readonly failureRetentionPeriod?: cdk.Duration; + + /** + * The name of the canary. Be sure to give it a descriptive name that distinguishes it from + * other canaries in your account. + * + * Do not include secrets or proprietary information in your canary name. The canary name + * makes up part of the canary ARN, which is included in outbound calls over the internet. + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/servicelens_canaries_security.html + * + * @default - A unique name will be generated from the construct ID + */ + readonly canaryName?: string; + + /** + * Specify the runtime version to use for the canary. Currently, the only valid value is `Runtime.SYNTHETICS_1.0`. + * + * @default Runtime.SYNTHETICS_1_0 + */ + readonly runtime?: Runtime; + + /** + * The type of test that you want your canary to run. Use `Test.custom()` to specify the test to run. + */ + readonly test: Test; + +} + +/** + * Define a new Canary + */ +export class Canary extends cdk.Resource { + /** + * Execution role associated with this Canary. + */ + public readonly role: iam.IRole; + + /** + * The canary ID + * @attribute + */ + public readonly canaryId: string; + + /** + * The state of the canary. For example, 'RUNNING', 'STOPPED', 'NOT STARTED', or 'ERROR'. + * @attribute + */ + public readonly canaryState: string; + + /** + * The canary Name + * @attribute + */ + public readonly canaryName: string; + + /** + * Bucket where data from each canary run is stored. + */ + public readonly artifactsBucket: s3.IBucket; + + public constructor(scope: cdk.Construct, id: string, props: CanaryProps) { + if (props.canaryName && !cdk.Token.isUnresolved(props.canaryName)) { + validateName(props.canaryName); + } + + super(scope, id, { + physicalName: props.canaryName || cdk.Lazy.stringValue({ + produce: () => this.generateUniqueName(), + }), + }); + + this.artifactsBucket = props.artifactsBucketLocation?.bucket ?? new s3.Bucket(this, 'ArtifactsBucket', { + encryption: s3.BucketEncryption.KMS_MANAGED, + }); + + this.role = props.role ?? this.createDefaultRole(props.artifactsBucketLocation?.prefix); + + const resource: CfnCanary = new CfnCanary(this, 'Resource', { + artifactS3Location: this.artifactsBucket.s3UrlForObject(props.artifactsBucketLocation?.prefix), + executionRoleArn: this.role.roleArn, + startCanaryAfterCreation: props.startAfterCreation ?? true, + runtimeVersion: props.runtime?.name ?? Runtime.SYNTHETICS_1_0.name, + name: this.physicalName, + schedule: this.createSchedule(props), + failureRetentionPeriod: props.failureRetentionPeriod?.toDays(), + successRetentionPeriod: props.successRetentionPeriod?.toDays(), + code: this.createCode(props), + }); + + this.canaryId = resource.attrId; + this.canaryState = resource.attrState; + this.canaryName = this.getResourceNameAttribute(resource.ref); + } + + /** + * Measure the Duration of a single canary run, in seconds. + * + * @param options - configuration options for the metric + * + * @default avg over 5 minutes + */ + public metricDuration(options?: MetricOptions): Metric { + return this.metric('Duration', options); + } + + /** + * Measure the percentage of successful canary runs. + * + * @param options - configuration options for the metric + * + * @default avg over 5 minutes + */ + public metricSuccessPercent(options?: MetricOptions): Metric { + return this.metric('SuccessPercent', options); + } + + /** + * Measure the number of failed canary runs over a given time period. + * + * @param options - configuration options for the metric + * + * @default avg over 5 minutes + */ + public metricFailed(options?: MetricOptions): Metric { + return this.metric('Failed', options); + } + + /** + * @param metricName - the name of the metric + * @param options - configuration options for the metric + * + * @returns a CloudWatch metric associated with the canary. + * @default avg over 5 minutes + */ + private metric(metricName: string, options?: MetricOptions): Metric { + return new Metric({ + metricName, + namespace: 'CloudWatchSynthetics', + dimensions: { CanaryName: this.canaryName }, + statistic: 'avg', + ...options, + }).attachTo(this); + } + + /** + * Returns a default role for the canary + */ + private createDefaultRole(prefix?: string): iam.IRole { + // Created role will need these policies to run the Canary. + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-executionrolearn + const policy = new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + resources: ['*'], + actions: ['s3:ListAllMyBuckets'], + }), + new iam.PolicyStatement({ + resources: [this.artifactsBucket.arnForObjects(`${prefix ? prefix+'/*' : '*'}`)], + actions: ['s3:PutObject', 's3:GetBucketLocation'], + }), + new iam.PolicyStatement({ + resources: ['*'], + actions: ['cloudwatch:PutMetricData'], + conditions: {StringEquals: {'cloudwatch:namespace': 'CloudWatchSynthetics'}}, + }), + new iam.PolicyStatement({ + resources: ['arn:aws:logs:::*'], + actions: ['logs:CreateLogStream', 'logs:CreateLogGroup', 'logs:PutLogEvents'], + }), + ], + }); + + return new iam.Role(this, 'ServiceRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + inlinePolicies: { + canaryPolicy: policy, + }, + }); + } + + /** + * Returns the code object taken in by the canary resource. + */ + private createCode(props: CanaryProps): CfnCanary.CodeProperty { + const codeConfig = { + handler: props.test.handler, + ...props.test.code.bind(this, props.test.handler), + }; + return { + handler: codeConfig.handler, + script: codeConfig.inlineCode, + s3Bucket: codeConfig.s3Location?.bucketName, + s3Key: codeConfig.s3Location?.objectKey, + s3ObjectVersion: codeConfig.s3Location?.objectVersion, + }; + } + + /** + * Returns a canary schedule object + */ + private createSchedule(props: CanaryProps): CfnCanary.ScheduleProperty { + return { + durationInSeconds: String(`${props.timeToLive?.toSeconds() ?? 0}`), + expression: props.schedule?.expressionString ?? 'rate(5 minutes)', + }; + } + + /** + * Creates a unique name for the canary. The generated name is the physical ID of the canary. + */ + private generateUniqueName(): string { + const name = this.node.uniqueId.toLowerCase().replace(' ', '-'); + if (name.length <= 21){ + return name; + } else { + return name.substring(0,15) + nameHash(name); + } + } +} + +/** + * Take a hash of the given name. + * + * @param name the name to be hashed + */ +function nameHash(name: string): string { + const md5 = crypto.createHash('sha256').update(name).digest('hex'); + return md5.slice(0,6); +} + +const nameRegex: RegExp = /^[0-9a-z_\-]+$/; + +/** + * Verifies that the name fits the regex expression: ^[0-9a-z_\-]+$. + * + * @param name - the given name of the canary + */ +function validateName(name: string) { + if (name.length > 21) { + throw new Error(`Canary name is too large, must be between 1 and 21 characters, but is ${name.length} (got "${name}")`); + } + if (!nameRegex.test(name)) { + throw new Error(`Canary name must be lowercase, numbers, hyphens, or underscores (got "${name}")`); + } +} diff --git a/packages/@aws-cdk/aws-synthetics/lib/code.ts b/packages/@aws-cdk/aws-synthetics/lib/code.ts new file mode 100644 index 0000000000000..b5dd4e87aac23 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/lib/code.ts @@ -0,0 +1,183 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import { Construct } from '@aws-cdk/core'; + +/** + * The code the canary should execute + */ +export abstract class Code { + + /** + * Specify code inline. + * + * @param code The actual handler code (limited to 4KiB) + * + * @returns `InlineCode` with inline code. + */ + public static fromInline(code: string): InlineCode { + return new InlineCode(code); + } + + /** + * Specify code from a local path. Path must include the folder structure `nodejs/node_modules/myCanaryFilename.js`. + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html#CloudWatch_Synthetics_Canaries_write_from_scratch + * + * @param assetPath Either a directory or a .zip file + * + * @returns `AssetCode` associated with the specified path. + */ + public static fromAsset(assetPath: string, options?: s3_assets.AssetOptions): AssetCode { + return new AssetCode(assetPath, options); + } + + /** + * Specify code from an s3 bucket. The object in the s3 bucket must be a .zip file that contains + * the structure `nodejs/node_modules/myCanaryFilename.js`. + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html#CloudWatch_Synthetics_Canaries_write_from_scratch + * + * @param bucket The S3 bucket + * @param key The object key + * @param objectVersion Optional S3 object version + * + * @returns `S3Code` associated with the specified S3 object. + */ + public static fromBucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3Code { + return new S3Code(bucket, key, objectVersion); + } + + /** + * Called when the canary is initialized to allow this object to bind + * to the stack, add resources and have fun. + * + * @param scope The binding scope. Don't be smart about trying to down-cast or + * assume it's initialized. You may just use it as a construct scope. + * + * @returns a bound `CodeConfig`. + */ + public abstract bind(scope: Construct, handler: string): CodeConfig; +} + +/** + * Configuration of the code class + */ +export interface CodeConfig { + /** + * The location of the code in S3 (mutually exclusive with `inlineCode`). + * + * @default - none + */ + readonly s3Location?: s3.Location; + + /** + * Inline code (mutually exclusive with `s3Location`). + * + * @default - none + */ + readonly inlineCode?: string; +} + +/** + * Canary code from an Asset + */ +export class AssetCode extends Code { + private asset?: s3_assets.Asset; + + /** + * @param assetPath The path to the asset file or directory. + */ + public constructor(private assetPath: string, private options?: s3_assets.AssetOptions) { + super(); + + if (!fs.existsSync(this.assetPath)) { + throw new Error(`${this.assetPath} is not a valid path`); + } + } + + public bind(scope: Construct, handler: string): CodeConfig { + this.validateCanaryAsset(handler); + + // If the same AssetCode is used multiple times, retain only the first instantiation. + if (!this.asset){ + this.asset = new s3_assets.Asset(scope, 'Code', { + path: this.assetPath, + ...this.options, + }); + } + + return { + s3Location: { + bucketName: this.asset.s3BucketName, + objectKey: this.asset.s3ObjectKey, + }, + }; + } + + /** + * Validates requirements specified by the canary resource. For example, the canary code with handler `index.handler` + * must be found in the file structure `nodejs/node_modules/index.js`. + * + * Requires path to be either zip file or directory. + * Requires asset directory to have the structure 'nodejs/node_modules'. + * Requires canary file to be directly inside node_modules folder. + * Requires canary file name matches the handler name. + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html + * + * @param handler the canary handler + */ + private validateCanaryAsset(handler: string) { + if (path.extname(this.assetPath) !== '.zip') { + if (!fs.lstatSync(this.assetPath).isDirectory()) { + throw new Error(`Asset must be a .zip file or a directory (${this.assetPath})`); + } + const filename = `${handler.split('.')[0]}.js`; + if (!fs.existsSync(path.join(this.assetPath,'nodejs', 'node_modules', filename))) { + throw new Error(`The canary resource requires that the handler is present at "nodejs/node_modules/${filename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html#CloudWatch_Synthetics_Canaries_write_from_scratch)`); + } + } + } +} + +/** + * Canary code from an inline string. + */ +export class InlineCode extends Code { + public constructor(private code: string) { + super(); + + if (code.length === 0) { + throw new Error('Canary inline code cannot be empty'); + } + } + + public bind(_scope: Construct, handler: string): CodeConfig { + + if (handler !== 'index.handler') { + throw new Error(`The handler for inline code must be "index.handler" (got "${handler}")`); + } + + return { + inlineCode: this.code, + }; + } +} + +/** + * S3 bucket path to the code zip file + */ +export class S3Code extends Code { + public constructor(private bucket: s3.IBucket, private key: string, private objectVersion?: string) { + super(); + } + + public bind(_scope: Construct, _handler: string): CodeConfig { + return { + s3Location: { + bucketName: this.bucket.bucketName, + objectKey: this.key, + objectVersion: this.objectVersion, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-synthetics/lib/index.ts b/packages/@aws-cdk/aws-synthetics/lib/index.ts index 4e9fab4ff01dd..f769a0309352e 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/index.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/index.ts @@ -1,2 +1,6 @@ +export * from './canary'; +export * from './code'; +export * from './schedule'; + // AWS::Synthetics CloudFormation Resources: export * from './synthetics.generated'; diff --git a/packages/@aws-cdk/aws-synthetics/lib/schedule.ts b/packages/@aws-cdk/aws-synthetics/lib/schedule.ts new file mode 100644 index 0000000000000..1102ba603367e --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/lib/schedule.ts @@ -0,0 +1,50 @@ +import { Duration } from '@aws-cdk/core'; + +/** + * Schedule for canary runs + */ +export class Schedule { + + /** + * The canary will be executed once. + */ + public static once(): Schedule { + return new Schedule('rate(0 minutes)'); + } + + /** + * Construct a schedule from a literal schedule expression. The expression must be in a `rate(number units)` format. + * For example, `Schedule.expression('rate(10 minutes)')` + * + * @param expression The expression to use. + */ + public static expression(expression: string): Schedule { + return new Schedule(expression); + } + + /** + * Construct a schedule from an interval. Allowed values: 0 (for a single run) or between 1 and 60 minutes. + * To specify a single run, you can use `Schedule.once()`. + * + * @param interval The interval at which to run the canary + */ + public static rate(interval: Duration): Schedule { + const minutes = interval.toMinutes(); + if (minutes > 60) { + throw new Error('Schedule duration must be between 1 and 60 minutes'); + } + if (minutes === 0) { + return Schedule.once(); + } + if (minutes === 1) { + return new Schedule('rate(1 minute)'); + } + return new Schedule(`rate(${minutes} minutes)`); + } + + private constructor( + /** + * The Schedule expression + */ + public readonly expressionString: string){} +} diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index b553ad1e05325..020a3038d3f81 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -67,20 +67,31 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "pkglint": "0.0.0" }, "dependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", + "constructs": "^3.0.2" }, "peerDependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-cloudwatch": "0.0.0", + "constructs": "^3.0.2" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "experimental", - "maturity": "cfn-only", + "maturity": "experimental", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-synthetics/test/canaries/nodejs/node_modules/canary.js b/packages/@aws-cdk/aws-synthetics/test/canaries/nodejs/node_modules/canary.js new file mode 100644 index 0000000000000..0fa437f6288a2 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/canaries/nodejs/node_modules/canary.js @@ -0,0 +1,53 @@ +var synthetics = require('Synthetics'); +const log = require('SyntheticsLogger'); +const https = require('https'); +const http = require('http'); + +const apiCanaryBlueprint = async function () { + const postData = ""; + + const verifyRequest = async function (requestOption) { + return new Promise((resolve, reject) => { + log.info("Making request with options: " + JSON.stringify(requestOption)); + let req + if (requestOption.port === 443) { + req = https.request(requestOption); + } else { + req = http.request(requestOption); + } + req.on('response', (res) => { + log.info(`Status Code: ${res.statusCode}`) + log.info(`Response Headers: ${JSON.stringify(res.headers)}`) + if (res.statusCode !== 200) { + reject("Failed: " + requestOption.path); + } + res.on('data', (d) => { + log.info("Response: " + d); + }); + res.on('end', () => { + resolve(); + }) + }); + + req.on('error', (error) => { + reject(error); + }); + + if (postData) { + req.write(postData); + } + req.end(); + }); + } + + const headers = {} + headers['User-Agent'] = [synthetics.getCanaryUserAgentString(), headers['User-Agent']].join(' '); + const requestOptions = {"hostname":"ajt66lp5wj.execute-api.us-east-1.amazonaws.com","method":"GET","path":"/prod/","port":443} + requestOptions['headers'] = headers; + await verifyRequest(requestOptions); +}; + + +exports.handler = async () => { + return await apiCanaryBlueprint(); +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.test.ts b/packages/@aws-cdk/aws-synthetics/test/canary.test.ts new file mode 100644 index 0000000000000..ecd12308271c5 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/canary.test.ts @@ -0,0 +1,343 @@ +import '@aws-cdk/assert/jest'; +import { objectLike } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, Duration, Lazy, Stack } from '@aws-cdk/core'; +import * as synthetics from '../lib'; + +test('Basic canary properties work', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + canaryName: 'mycanary', + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + successRetentionPeriod: Duration.days(10), + failureRetentionPeriod: Duration.days(10), + startAfterCreation: false, + timeToLive: Duration.minutes(30), + runtime: synthetics.Runtime.SYNTHETICS_1_0, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Name: 'mycanary', + SuccessRetentionPeriod: 10, + FailureRetentionPeriod: 10, + StartCanaryAfterCreation: false, + Schedule: objectLike({ DurationInSeconds: '1800'}), + RuntimeVersion: 'syn-1.0', + }); +}); + +test('Canary can have generated name', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Name: 'canariescanary8dfb794', + }); +}); + +test('Name validation does not fail when using Tokens', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + canaryName: Lazy.stringValue({ produce: () => 'My Canary' }), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN: no exception + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary'); +}); + +test('Throws when name is specified incorrectly', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + expect(() => new synthetics.Canary(stack, 'Canary', { + canaryName: 'My Canary', + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + })) + .toThrowError('Canary name must be lowercase, numbers, hyphens, or underscores (got "My Canary")'); +}); + +test('Throws when name has more than 21 characters', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + expect(() => new synthetics.Canary(stack, 'Canary', { + canaryName: 'a'.repeat(22), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + })) + .toThrowError(`Canary name is too large, must be between 1 and 21 characters, but is 22 (got "${'a'.repeat(22)}")`); +}); + +test('An existing role can be specified instead of auto-created', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + const role = new iam.Role(stack, 'role', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + // role.addToPolicy(/* required permissions per the documentation */); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + role, + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + ExecutionRoleArn: stack.resolve(role.roleArn), + }); +}); + +test('An existing bucket and prefix can be specified instead of auto-created', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const bucket = new s3.Bucket(stack, 'mytestbucket'); + const prefix = 'canary'; + + // WHEN + new synthetics.Canary(stack, 'Canary', { + artifactsBucketLocation: { bucket, prefix }, + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + ArtifactS3Location: stack.resolve(bucket.s3UrlForObject(prefix)), + }); +}); + +test('Runtime can be specified', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + runtime: synthetics.Runtime.SYNTHETICS_1_0, + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + RuntimeVersion: 'syn-1.0', + }); +}); + +test('Runtime can be customized', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + runtime: new synthetics.Runtime('fancy-future-runtime-1337.42'), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + RuntimeVersion: 'fancy-future-runtime-1337.42', + }); +}); + +test('Schedule can be set with Rate', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.rate(Duration.minutes(3)), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Schedule: objectLike({ Expression: 'rate(3 minutes)'}), + }); +}); + +test('Schedule can be set to 1 minute', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.rate(Duration.minutes(1)), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Schedule: objectLike({ Expression: 'rate(1 minute)'}), + }); +}); + +test('Schedule can be set with Expression', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.expression('rate(1 hour)'), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Schedule: objectLike({ Expression: 'rate(1 hour)'}), + }); +}); + +test('Schedule can be set to run once', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.once(), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Schedule: objectLike({ Expression: 'rate(0 minutes)'}), + }); +}); + +test('Throws when rate above 60 minutes', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + expect(() => new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.rate(Duration.minutes(61)), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + })) + .toThrowError('Schedule duration must be between 1 and 60 minutes'); +}); + +test('Throws when rate above is not a whole number of minutes', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + expect(() => new synthetics.Canary(stack, 'Canary', { + schedule: synthetics.Schedule.rate(Duration.seconds(59)), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + })) + .toThrowError('\'59 seconds\' cannot be converted into a whole number of minutes.'); +}); + +test('Can share artifacts bucket between canaries', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + const canary1 = new synthetics.Canary(stack, 'Canary1', { + schedule: synthetics.Schedule.once(), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + }); + + const canary2 = new synthetics.Canary(stack, 'Canary2', { + schedule: synthetics.Schedule.once(), + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + artifactsBucketLocation: { bucket: canary1.artifactsBucket }, + }); + + // THEN + expect(canary1.artifactsBucket).toEqual(canary2.artifactsBucket); +}); + +test('can specify custom test', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline(` + exports.handler = async () => { + console.log(\'hello world\'); + };`), + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Code: { + Handler: 'index.handler', + Script: ` + exports.handler = async () => { + console.log(\'hello world\'); + };`, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.zip b/packages/@aws-cdk/aws-synthetics/test/canary.zip new file mode 100644 index 0000000000000000000000000000000000000000..b8d48b451cf330f303c1ed8cd0e3bc12381e862c GIT binary patch literal 636 zcmWIWW@Zs#00HZ+zX4zdlwfC2V93i)NzE$O4-MgEU~hV^7!SgK3^n3QE4UdLS#p}{ zvRTelGZzNH4RJ*?L{FKt1RH%uQfTm@?P>cuBSWV<$;9#gkGSLC(G+7)b zCMV`47FFtH6@wl74QM0`qZ#eJR^x=emgcq79%sBydg=yk2=&lecg8#H!iA6xTZ30T z3kvW!cV7G4dC#k-Px@aEIPY=x { + test('fromInline works', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + const inline = synthetics.Code.fromInline(` + exports.handler = async () => { + console.log(\'hello world\'); + };`); + + // THEN + expect(inline.bind(stack, 'index.handler').inlineCode).toEqual(` + exports.handler = async () => { + console.log(\'hello world\'); + };`); + }); + + test('fails if empty', () => { + expect(() => synthetics.Code.fromInline('')) + .toThrowError('Canary inline code cannot be empty'); + }); + + test('fails if handler is not "index.handler"', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + expect(() => synthetics.Code.fromInline('code').bind(stack, 'canary.handler')) + .toThrowError('The handler for inline code must be "index.handler" (got "canary.handler")'); + }); +}); + +describe(synthetics.Code.fromAsset, () => { + test('fromAsset works', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // WHEN + const directoryAsset = synthetics.Code.fromAsset(path.join(__dirname, 'canaries')); + new synthetics.Canary(stack, 'Canary', { + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: directoryAsset, + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Synthetics::Canary', { + Code: { + Handler: 'canary.handler', + S3Bucket: stack.resolve(directoryAsset.bind(stack, 'canary.handler').s3Location?.bucketName), + S3Key: stack.resolve(directoryAsset.bind(stack, 'canary.handler').s3Location?.objectKey), + }, + }); + }); + + test('only one Asset object gets created even if multiple canaries use the same AssetCode', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'canaries'); + + // WHEN + const directoryAsset = synthetics.Code.fromAsset(path.join(__dirname, 'canaries')); + new synthetics.Canary(stack, 'Canary1', { + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: directoryAsset, + }), + }); + new synthetics.Canary(stack, 'Canary2', { + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: directoryAsset, + }), + }); + + // THEN + const assembly = app.synth(); + const synthesized = assembly.stacks[0]; + + expect(synthesized.assets.length).toEqual(1); + }); + + test('fails if path does not exist', () => { + const assetPath = path.join(__dirname, 'does-not-exist'); + expect(() => synthetics.Code.fromAsset(assetPath)) + .toThrowError(`${assetPath} is not a valid path`); + }); + + test('fails if non-zip asset is used', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + const assetPath = path.join(__dirname, 'canaries', 'nodejs', 'node_modules', 'canary.js'); + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler')) + .toThrowError(`Asset must be a .zip file or a directory (${assetPath})`); + }); + + test('fails if "nodejs/node_modules" folder structure not used', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + const assetPath = path.join(__dirname, 'canaries', 'nodejs', 'node_modules'); + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler')) + .toThrowError(`The canary resource requires that the handler is present at "nodejs/node_modules/canary.js" but not found at ${assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html#CloudWatch_Synthetics_Canaries_write_from_scratch)`); + }); + + test('fails if handler is specified incorrectly', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + // THEN + const assetPath = path.join(__dirname, 'canaries', 'nodejs', 'node_modules'); + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'incorrect.handler')) + .toThrowError(`The canary resource requires that the handler is present at "nodejs/node_modules/incorrect.js" but not found at ${assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html#CloudWatch_Synthetics_Canaries_write_from_scratch)`); + }); +}); + + +describe(synthetics.Code.fromBucket, () => { + test('fromBucket works', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const bucket = new s3.Bucket(stack, 'CodeBucket'); + + // WHEN + const code = synthetics.Code.fromBucket(bucket, 'code.js'); + const codeConfig = code.bind(stack, 'code.handler'); + + // THEN + expect(codeConfig.s3Location?.bucketName).toEqual(bucket.bucketName); + expect(codeConfig.s3Location?.objectKey).toEqual('code.js'); + }); +}); diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json new file mode 100644 index 0000000000000..62cee1d8660c7 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json @@ -0,0 +1,342 @@ +{ + "Resources": { + "MyCanaryArtifactsBucket89975E6D": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyCanaryServiceRole593F9DD9": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:PutObject", + "s3:GetBucketLocation" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryArtifactsBucket89975E6D", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:::*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ] + } + }, + "MyCanary1A94CAFA": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "MyCanaryArtifactsBucket89975E6D" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Ref": "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3Bucket58589EB6" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3VersionKey8FF13E90" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3VersionKey8FF13E90" + } + ] + } + ] + } + ] + ] + } + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCanaryServiceRole593F9DD9", + "Arn" + ] + }, + "Name": "assetcanary-one", + "RuntimeVersion": "syn-1.0", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true + } + }, + "MyCanaryTwoArtifactsBucket79B179B6": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyCanaryTwoServiceRole041E85D4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:PutObject", + "s3:GetBucketLocation" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyCanaryTwoArtifactsBucket79B179B6", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:::*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ] + } + }, + "MyCanaryTwo6501D55F": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "MyCanaryTwoArtifactsBucket79B179B6" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" + } + ] + } + ] + } + ] + ] + } + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCanaryTwoServiceRole041E85D4", + "Arn" + ] + }, + "Name": "assetcanary-two", + "RuntimeVersion": "syn-1.0", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true + } + } + }, + "Parameters": { + "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3Bucket58589EB6": { + "Type": "String", + "Description": "S3 bucket for asset \"5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529b\"" + }, + "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3VersionKey8FF13E90": { + "Type": "String", + "Description": "S3 key for asset version \"5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529b\"" + }, + "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bArtifactHash74DCED3D": { + "Type": "String", + "Description": "Artifact hash for asset \"5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529b\"" + }, + "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761": { + "Type": "String", + "Description": "S3 bucket for asset \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" + }, + "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B": { + "Type": "String", + "Description": "S3 key for asset version \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" + }, + "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820ArtifactHash536FDCC3": { + "Type": "String", + "Description": "Artifact hash for asset \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.asset.ts b/packages/@aws-cdk/aws-synthetics/test/integ.asset.ts new file mode 100644 index 0000000000000..49595e69b4286 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/integ.asset.ts @@ -0,0 +1,32 @@ +/// !cdk-integ canary-asset + +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import * as synthetics from '../lib'; + +/* + * Stack verification steps: + * + * -- aws synthetics get-canary --name assetcanary-one has a state of 'RUNNING' + * -- aws synthetics get-canary --name assetcanary-two has a state of 'RUNNING' + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'canary-asset'); + +new synthetics.Canary(stack, 'MyCanary', { + canaryName: 'assetcanary-one', + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')), + }), +}); + +new synthetics.Canary(stack, 'MyCanaryTwo', { + canaryName: 'assetcanary-two', + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canary.zip')), + }), +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json new file mode 100644 index 0000000000000..fb21d4808e26e --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.expected.json @@ -0,0 +1,115 @@ +{ + "Resources": { + "mytestbucket8DC16178": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyCanaryServiceRole593F9DD9": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:PutObject", + "s3:GetBucketLocation" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "mytestbucket8DC16178", + "Arn" + ] + }, + "/integ/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:::*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ] + } + }, + "MyCanary1A94CAFA": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "mytestbucket8DC16178" + }, + "/integ" + ] + ] + }, + "Code": { + "Handler": "index.handler", + "Script": "\n exports.handler = async () => {\n console.log('hello world');\n };" + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCanaryServiceRole593F9DD9", + "Arn" + ] + }, + "Name": "canary-integ", + "RuntimeVersion": "syn-1.0", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(1 minute)" + }, + "StartCanaryAfterCreation": true + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts new file mode 100644 index 0000000000000..846361d82d7d7 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/integ.canary.ts @@ -0,0 +1,32 @@ +/// !cdk-integ canary-one + +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as synthetics from '../lib'; + +/* + * Stack verification steps: + * + * -- aws synthetics get-canary --name canary-one has a state of 'RUNNING' + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'canary-one'); + +const bucket = new s3.Bucket(stack, 'mytestbucket'); +const prefix = 'integ'; + +new synthetics.Canary(stack, 'MyCanary', { + canaryName: 'canary-integ', + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline(` + exports.handler = async () => { + console.log(\'hello world\'); + };`), + }), + runtime: synthetics.Runtime.SYNTHETICS_1_0, + schedule: synthetics.Schedule.rate(cdk.Duration.minutes(1)), + artifactsBucketLocation: { bucket, prefix }, +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/metric.test.ts b/packages/@aws-cdk/aws-synthetics/test/metric.test.ts new file mode 100644 index 0000000000000..5a968b8922d84 --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/metric.test.ts @@ -0,0 +1,69 @@ +import '@aws-cdk/assert/jest'; +import { App, Stack } from '@aws-cdk/core'; +import * as synthetics from '../lib'; + +test('.metricXxx() methods can be used to obtain Metrics for the canary', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + const canary = new synthetics.Canary(stack, 'mycanary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('foo'), + }), + }); + + // WHEN + const metricSuccess = canary.metricSuccessPercent(); + const metricFailed = canary.metricFailed(); + const metricDuration = canary.metricDuration(); + + // THEN + expect(metricSuccess).toEqual({ + period: { amount: 5, unit: { inMillis: 60000, label: 'minutes' } }, + dimensions: { CanaryName: canary.canaryName }, + namespace: 'CloudWatchSynthetics', + metricName: 'SuccessPercent', + statistic: 'Average', + }); + + expect(metricFailed).toEqual({ + period: { amount: 5, unit: { inMillis: 60000, label: 'minutes' } }, + dimensions: { CanaryName: canary.canaryName }, + namespace: 'CloudWatchSynthetics', + metricName: 'Failed', + statistic: 'Average', + }); + + expect(metricDuration).toEqual({ + period: { amount: 5, unit: { inMillis: 60000, label: 'minutes' } }, + dimensions: { CanaryName: canary.canaryName }, + namespace: 'CloudWatchSynthetics', + metricName: 'Duration', + statistic: 'Average', + }); +}); + +test('Metric can specify statistic', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + + const canary = new synthetics.Canary(stack, 'mycanary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('foo'), + }), + }); + + // WHEN + const metric = canary.metricFailed({statistic: 'Sum'}); + + // THEN + expect(metric).toEqual({ + period: { amount: 5, unit: { inMillis: 60000, label: 'minutes' } }, + dimensions: { CanaryName: canary.canaryName }, + namespace: 'CloudWatchSynthetics', + metricName: 'Failed', + statistic: 'Sum', + }); +}); diff --git a/packages/@aws-cdk/aws-synthetics/test/synthetics.test.ts b/packages/@aws-cdk/aws-synthetics/test/synthetics.test.ts deleted file mode 100644 index e394ef336bfb4..0000000000000 --- a/packages/@aws-cdk/aws-synthetics/test/synthetics.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@aws-cdk/assert/jest'; -import {} from '../lib'; - -test('No tests are specified for this package', () => { - expect(true).toBe(true); -}); From f754ead874b77d87c5dd9c0ab4163d3abd108496 Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Fri, 14 Aug 2020 22:39:44 +0000 Subject: [PATCH 027/422] chore(release): 1.59.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d445eaaa302df..7055920a11ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.59.0](https://github.com/aws/aws-cdk/compare/v1.58.0...v1.59.0) (2020-08-14) + + +### ⚠ BREAKING CHANGES + +* **eks:** `cluster.addResource` was renamed to `cluster.addManifest` and `KubernetesResource` was renamed to `KubernetesManifest` +* **cloudfront:** (cloudfront) Changed IDs for Distributions (will cause resource replacement). + +### Features + +* **cfn-include:** allow passing Parameters to the included template ([#9543](https://github.com/aws/aws-cdk/issues/9543)) ([cb6de0a](https://github.com/aws/aws-cdk/commit/cb6de0adaec9e9942c7568939b33d7cb29cdeef2)) +* **cfnspec:** cloudformation spec v16.3.0 ([#9452](https://github.com/aws/aws-cdk/issues/9452)) ([fb5068d](https://github.com/aws/aws-cdk/commit/fb5068ded6116b996b108037aa593684a2078351)) +* **cloudfront:** Distribution support for logging, geo restrictions, http version and IPv6 ([#9635](https://github.com/aws/aws-cdk/issues/9635)) ([4c62702](https://github.com/aws/aws-cdk/commit/4c62702fe886a41e00a0be1fdf12bdb75a9ac968)) +* **codebuild:** add support for GPU build images ([#8879](https://github.com/aws/aws-cdk/issues/8879)) ([b1b4cee](https://github.com/aws/aws-cdk/commit/b1b4ceee16a2483604fa97741ed2f7ddf340d10a)), closes [#8408](https://github.com/aws/aws-cdk/issues/8408) +* **codeguruprofiler:** add support for ComputePlatform in ProfilingGroup ([#9391](https://github.com/aws/aws-cdk/issues/9391)) ([5a64bc5](https://github.com/aws/aws-cdk/commit/5a64bc518868301eb4c5ce8d2964d62d4a79a764)) +* **ec2:** CloudFormation-init support ([#9065](https://github.com/aws/aws-cdk/issues/9065)) ([014c13a](https://github.com/aws/aws-cdk/commit/014c13a78261b400404819549f6ff25d27b0c51d)), closes [#8788](https://github.com/aws/aws-cdk/issues/8788) [#9063](https://github.com/aws/aws-cdk/issues/9063) [#9063](https://github.com/aws/aws-cdk/issues/9063) +* **eks:** ability to query runtime information from the cluster ([#9535](https://github.com/aws/aws-cdk/issues/9535)) ([4bc8188](https://github.com/aws/aws-cdk/commit/4bc8188f38544f8e873728d908583ca8afe1714e)), closes [#8394](https://github.com/aws/aws-cdk/issues/8394) +* **synthetics:** Synthetics L2 Support ([#8824](https://github.com/aws/aws-cdk/issues/8824)) ([691b349](https://github.com/aws/aws-cdk/commit/691b349f55e8c8b52518ae40cac0ba3720c71ddf)), closes [#7687](https://github.com/aws/aws-cdk/issues/7687) + + +### Bug Fixes + +* **cloudfront:** ensures origin groups are added with their own ID as a target ([#9593](https://github.com/aws/aws-cdk/issues/9593)) ([246842f](https://github.com/aws/aws-cdk/commit/246842f4f5259ca15b0c04fcda27e3fa37262594)), closes [#9561](https://github.com/aws/aws-cdk/issues/9561) [#9561](https://github.com/aws/aws-cdk/issues/9561) +* **cloudfront:** Escape hatch support for Distribution ([#9648](https://github.com/aws/aws-cdk/issues/9648)) ([cc229c2](https://github.com/aws/aws-cdk/commit/cc229c2c660e7e5be2255f031c001218c26b4752)), closes [#9620](https://github.com/aws/aws-cdk/issues/9620) +* **codepipeline:** S3 source Action with trigger=Events fails for bucketKey a Token ([#9575](https://github.com/aws/aws-cdk/issues/9575)) ([43214b4](https://github.com/aws/aws-cdk/commit/43214b4059aa7af40389d5d762c387d8e6093959)), closes [#9554](https://github.com/aws/aws-cdk/issues/9554) +* **ec2:** can't use imported Subnets in a SubnetSelection ([#9579](https://github.com/aws/aws-cdk/issues/9579)) ([1c4eae8](https://github.com/aws/aws-cdk/commit/1c4eae8e4052e7d56d944006577ee1d78785781a)) + ## [1.58.0](https://github.com/aws/aws-cdk/compare/v1.57.0...v1.58.0) (2020-08-12) diff --git a/lerna.json b/lerna.json index 51753d20a7b8a..334e912348339 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.58.0" + "version": "1.59.0" } From 5d0c70195e781242bec75488e73ba72a4b13f5c4 Mon Sep 17 00:00:00 2001 From: Jerry Kindall <52084730+Jerry-AWS@users.noreply.github.com> Date: Fri, 14 Aug 2020 15:53:11 -0700 Subject: [PATCH 028/422] Merge pull request #1 from aws/master (#9729) From 1893a5eb45e8190f4d1ebbb07b49b6b3492084cc Mon Sep 17 00:00:00 2001 From: Jerry Kindall <52084730+Jerry-AWS@users.noreply.github.com> Date: Fri, 14 Aug 2020 16:35:12 -0700 Subject: [PATCH 029/422] docs: Add info about stability to repo README (#9691) Add information about AWS Construct Library module stability to repo README for highest possible visibility --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a406ed66cfe48..c413d0df84cb2 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,14 @@ how to use AWS. The AWS Construct Library aims to reduce the complexity and glue-logic required when integrating various AWS services to achieve your goals on AWS. +Modules in the AWS Construct Library are designated Experimental while we build +them; experimental modules may have breaking API changes in any release. After +a module is designated Stable, it adheres to [semantic versioning](https://semver.org/), +and only major releases can have breaking changes. Each module's stability designation +is available on its Overview page in the [AWS CDK API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html). +For more information, see [Versioning](https://docs.aws.amazon.com/cdk/latest/guide/reference.html#versioning) +in the CDK Developer Guide. + [CDK framework]: https://docs.aws.amazon.com/cdk/latest/guide/home.html [constructs]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html [stacks]: https://docs.aws.amazon.com/cdk/latest/guide/stacks.html From 5732b8ee727d3c1a59e6dce100ec4179640970cb Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 14 Aug 2020 17:11:46 -0700 Subject: [PATCH 030/422] feat(appsync): import existing graphql api (#9254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **[ISSUE]** Appsync missing `fromXxx` functionality, making multiple stack appsync interaction impossible. **[Approach]** Created a base class for  `GraphQLApi` and a `IGraphQLApi` interface for imports. Added `fromXxxAttributes` functions to code base that can add dataSources. **[Notes]** Only accessible props from `IGraphQLApi` are `apiId` and `arn`. Only accessible functions from `IGraphQLApi` are the `addXxxDataSource` functions. Added `props-physical-name:@aws-cdk/aws-appsync.GraphQLApiProps` as linter exception because the breaking change isn't worth the return of making the physical name (name exists already). Added `from-method:@aws-cdk/aws-appsync.GraphQLApi` as linter exception because a `fromGraphQLApiAttributes` function will turn into `from_graph_q_l_api_attributes` in python. Fixes #6959 BREAKING CHANGE: **appsync.addXxxDataSource** `name` and `description` props are now optional and in an `DataSourceOptions` interface. - **appsync**: the props `name` and `description` in `addXxxDataSource` have been moved into new props `options` of type `DataSourceOptions` - **appsync**: `DataSourceOptions.name` defaults to id - **appsync**: `DataSourceOptions.description` defaults to undefined ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-appsync/README.md | 20 +- .../@aws-cdk/aws-appsync/lib/data-source.ts | 16 +- .../aws-appsync/lib/graphqlapi-base.ts | 177 +++++++++++++++ .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 139 ++++++------ packages/@aws-cdk/aws-appsync/lib/index.ts | 1 + packages/@aws-cdk/aws-appsync/lib/resolver.ts | 6 +- packages/@aws-cdk/aws-appsync/package.json | 4 +- .../aws-appsync/test/appsync-apikey.test.ts | 2 +- .../aws-appsync/test/appsync-dynamodb.test.ts | 143 ++++++++++++- .../aws-appsync/test/appsync-http.test.ts | 106 +++++++++ .../aws-appsync/test/appsync-lambda.test.ts | 121 +++++++++++ .../aws-appsync/test/appsync-none.test.ts | 115 ++++++++++ .../test/integ.api-import.expected.json | 201 ++++++++++++++++++ .../aws-appsync/test/integ.api-import.ts | 68 ++++++ .../test/integ.graphql-iam.expected.json | 31 ++- .../aws-appsync/test/integ.graphql-iam.ts | 2 +- .../test/integ.graphql.expected.json | 119 +++++------ .../aws-appsync/test/integ.graphql.ts | 10 +- 18 files changed, 1099 insertions(+), 182 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.api-import.ts diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index f086184760132..cb3b4d3708c5f 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -64,7 +64,7 @@ const demoTable = new db.Table(stack, 'DemoTable', { }, }); -const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos"', demoTable); +const demoDS = api.addDynamoDbDataSource('demoDataSource', demoTable); // Resolver for the Query "getDemos" that scans the DyanmoDb table and returns the entire list. demoDS.createResolver({ @@ -83,6 +83,24 @@ demoDS.createResolver({ }); ``` +## Imports + +Any GraphQL Api that has been created outside the stack can be imported from +another stack into your CDK app. Utilizing the `fromXxx` function, you have +the ability to add data sources and resolvers through a `IGraphQLApi` interface. + +```ts +const importedApi = appsync.GraphQLApi.fromGraphQLApiAttributes(stack, 'IApi', { + graphqlApiId: api.apiId, + graphqlArn: api.arn, +}); +importedApi.addDynamoDbDataSource('TableDataSource', table); +``` + +If you don't specify `graphqlArn` in `fromXxxAttributes`, CDK will autogenerate +the expected `arn` for the imported api, given the `apiId`. For creating data +sources and resolvers, an `apiId` is sufficient. + ## Permissions When using `AWS_IAM` as the authorization type for GraphQL API, an IAM Role diff --git a/packages/@aws-cdk/aws-appsync/lib/data-source.ts b/packages/@aws-cdk/aws-appsync/lib/data-source.ts index 0daf1f996a452..04c617b12e5d0 100644 --- a/packages/@aws-cdk/aws-appsync/lib/data-source.ts +++ b/packages/@aws-cdk/aws-appsync/lib/data-source.ts @@ -3,7 +3,7 @@ import { IGrantable, IPrincipal, IRole, Role, ServicePrincipal } from '@aws-cdk/ import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, IResolvable } from '@aws-cdk/core'; import { CfnDataSource } from './appsync.generated'; -import { GraphQLApi } from './graphqlapi'; +import { IGraphqlApi } from './graphqlapi-base'; import { BaseResolverProps, Resolver } from './resolver'; /** @@ -13,11 +13,13 @@ export interface BaseDataSourceProps { /** * The API to attach this data source to */ - readonly api: GraphQLApi; + readonly api: IGraphqlApi; /** * The name of the data source + * + * @default - id of data source */ - readonly name: string; + readonly name?: string; /** * the description of the data source * @@ -91,7 +93,7 @@ export abstract class BaseDataSource extends Construct { */ public readonly ds: CfnDataSource; - protected api: GraphQLApi; + protected api: IGraphqlApi; protected serviceRole?: IRole; constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { @@ -100,15 +102,15 @@ export abstract class BaseDataSource extends Construct { if (extended.type !== 'NONE') { this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync') }); } - + const name = props.name ?? id; this.ds = new CfnDataSource(this, 'Resource', { apiId: props.api.apiId, - name: props.name, + name: name, description: props.description, serviceRoleArn: this.serviceRole?.roleArn, ...extended, }); - this.name = props.name; + this.name = name; this.api = props.api; } diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts new file mode 100644 index 0000000000000..2f357c142db91 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts @@ -0,0 +1,177 @@ +import { ITable } from '@aws-cdk/aws-dynamodb'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { CfnResource, IResource, Resource } from '@aws-cdk/core'; +import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; + +/** + * Optional configuration for data sources + */ +export interface DataSourceOptions { + /** + * The name of the data source, overrides the id given by cdk + * + * @default - generated by cdk given the id + */ + readonly name?: string; + + /** + * The description of the data source + * + * @default - No description + */ + readonly description?: string; +} + +/** + * Interface for GraphQL + */ +export interface IGraphqlApi extends IResource { + + /** + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' + * + * @attribute + */ + readonly apiId: string; + + /** + * the ARN of the API + * + * @attribute + */ + readonly arn: string; + + /** + * add a new dummy data source to this API. Useful for pipeline resolvers + * and for backend changes that don't require a data source. + * + * @param id The data source's id + * @param options The optional configuration for this data source + */ + addNoneDataSource(id: string, options?: DataSourceOptions): NoneDataSource; + + /** + * add a new DynamoDB data source to this API + * + * @param id The data source's id + * @param table The DynamoDB table backing this data source + * @param options The optional configuration for this data source + */ + addDynamoDbDataSource(id: string, table: ITable, options?: DataSourceOptions): DynamoDbDataSource; + + /** + * add a new http data source to this API + * + * @param id The data source's id + * @param endpoint The http endpoint + * @param options The optional configuration for this data source + */ + addHttpDataSource(id: string, endpoint: string, options?: DataSourceOptions): HttpDataSource; + + /** + * add a new Lambda data source to this API + * + * @param id The data source's id + * @param lambdaFunction The Lambda function to call to interact with this data source + * @param options The optional configuration for this data source + */ + addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: DataSourceOptions): LambdaDataSource; + + /** + * Add schema dependency if not imported + * + * @param construct the dependee + */ + addSchemaDependency(construct: CfnResource): boolean; +} + +/** + * Base Class for GraphQL API + */ +export abstract class GraphqlApiBase extends Resource implements IGraphqlApi { + + /** + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' + */ + public abstract readonly apiId: string; + + /** + * the ARN of the API + */ + public abstract readonly arn: string; + + /** + * add a new dummy data source to this API. Useful for pipeline resolvers + * and for backend changes that don't require a data source. + * + * @param id The data source's id + * @param options The optional configuration for this data source + */ + public addNoneDataSource(id: string, options?: DataSourceOptions): NoneDataSource { + return new NoneDataSource(this, id, { + api: this, + name: options?.name, + description: options?.description, + }); + } + + /** + * add a new DynamoDB data source to this API + * + * @param id The data source's id + * @param table The DynamoDB table backing this data source + * @param options The optional configuration for this data source + */ + public addDynamoDbDataSource(id: string, table: ITable, options?: DataSourceOptions): DynamoDbDataSource { + return new DynamoDbDataSource(this, id, { + api: this, + table, + name: options?.name, + description: options?.description, + }); + } + + /** + * add a new http data source to this API + * + * @param id The data source's id + * @param endpoint The http endpoint + * @param options The optional configuration for this data source + */ + public addHttpDataSource(id: string, endpoint: string, options?: DataSourceOptions): HttpDataSource { + return new HttpDataSource(this, id, { + api: this, + endpoint, + name: options?.name, + description: options?.description, + }); + } + + /** + * add a new Lambda data source to this API + * + * @param id The data source's id + * @param lambdaFunction The Lambda function to call to interact with this data source + * @param options The optional configuration for this data source + */ + public addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: DataSourceOptions): LambdaDataSource { + return new LambdaDataSource(this, id, { + api: this, + lambdaFunction, + name: options?.name, + description: options?.description, + }); + } + + /** + * Add schema dependency if not imported + * + * @param construct the dependee + */ + public addSchemaDependency(construct: CfnResource): boolean { + construct; + return false; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index c2c1e768e2d9b..af51819d16f42 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,11 +1,9 @@ import { readFileSync } from 'fs'; import { IUserPool } from '@aws-cdk/aws-cognito'; -import { ITable } from '@aws-cdk/aws-dynamodb'; -import { Grant, IGrantable, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { IFunction } from '@aws-cdk/aws-lambda'; -import { Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; -import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; -import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; +import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; +import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; +import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; /** * enum with all possible values for AppSync authorization type @@ -221,7 +219,6 @@ export enum SchemaDefinition { * Properties for an AppSync GraphQL API */ export interface GraphQLApiProps { - /** * the name of the GraphQL API */ @@ -324,31 +321,79 @@ export class IamResource { } } +/** + * Attributes for GraphQL imports + */ +export interface GraphqlApiAttributes { + /** + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' + */ + readonly graphqlApiId: string, + + /** + * the arn for the GraphQL Api + * @default - autogenerated arn + */ + readonly graphqlApiArn?: string, +} + /** * An AppSync GraphQL API + * + * @resource AWS::AppSync::GraphQLApi */ -export class GraphQLApi extends Construct { +export class GraphQLApi extends GraphqlApiBase { + /** + * Import a GraphQL API through this function + * + * @param scope scope + * @param id id + * @param attrs GraphQL API Attributes of an API + */ + public static fromGraphqlApiAttributes(scope: Construct, id: string, attrs: GraphqlApiAttributes): IGraphqlApi { + const arn = attrs.graphqlApiArn ?? Stack.of(scope).formatArn({ + service: 'appsync', + resource: `apis/${attrs.graphqlApiId}`, + }); + class Import extends GraphqlApiBase { + public readonly apiId = attrs.graphqlApiId; + public readonly arn = arn; + constructor (s: Construct, i: string){ + super(s, i); + } + } + return new Import(scope, id); + } /** - * the id of the GraphQL API + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' */ public readonly apiId: string; + /** * the ARN of the API */ public readonly arn: string; + /** * the URL of the endpoint created by AppSync + * + * @attribute */ public readonly graphQlUrl: string; + /** * the name of the API */ public readonly name: string; + /** * underlying CFN schema resource */ public readonly schema: CfnGraphQLSchema; + /** * the configured API key, if present */ @@ -434,72 +479,6 @@ export class GraphQLApi extends Construct { this.schema = this.defineSchema(props.schemaDefinitionFile); } - /** - * add a new dummy data source to this API - * @param name The name of the data source - * @param description The description of the data source - */ - public addNoneDataSource(name: string, description: string): NoneDataSource { - return new NoneDataSource(this, `${name}DS`, { - api: this, - description, - name, - }); - } - - /** - * add a new DynamoDB data source to this API - * @param name The name of the data source - * @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: ITable, - ): DynamoDbDataSource { - return new DynamoDbDataSource(this, `${name}DS`, { - api: this, - description, - name, - table, - }); - } - - /** - * add a new http data source to this API - * @param name The name of the data source - * @param description The description of the data source - * @param endpoint The http endpoint - */ - public addHttpDataSource(name: string, description: string, endpoint: string): HttpDataSource { - return new HttpDataSource(this, `${name}DS`, { - api: this, - description, - endpoint, - name, - }); - } - - /** - * add a new Lambda data source to this API - * @param name The name of the data source - * @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 { - return new LambdaDataSource(this, `${name}DS`, { - api: this, - description, - name, - lambdaFunction, - }); - } - /** * Adds an IAM policy statement associated with this GraphQLApi to an IAM * principal's policy. @@ -613,6 +592,16 @@ export class GraphQLApi extends Construct { } } + /** + * Add schema dependency to a given construct + * + * @param construct the dependee + */ + public addSchemaDependency(construct: CfnResource): boolean { + construct.addDependsOn(this.schema); + return true; + } + private formatOpenIdConnectConfig( config: OpenIdConnectConfig, ): CfnGraphQLApi.OpenIDConnectConfigProperty { diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index 852cbed1c54f6..e0736c1ecc397 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -5,3 +5,4 @@ export * from './data-source'; export * from './mapping-template'; export * from './resolver'; export * from './graphqlapi'; +export * from './graphqlapi-base'; diff --git a/packages/@aws-cdk/aws-appsync/lib/resolver.ts b/packages/@aws-cdk/aws-appsync/lib/resolver.ts index 5a226c448069a..205155cc791c8 100644 --- a/packages/@aws-cdk/aws-appsync/lib/resolver.ts +++ b/packages/@aws-cdk/aws-appsync/lib/resolver.ts @@ -1,7 +1,7 @@ import { Construct } from '@aws-cdk/core'; import { CfnResolver } from './appsync.generated'; import { BaseDataSource } from './data-source'; -import { GraphQLApi } from './graphqlapi'; +import { IGraphqlApi } from './graphqlapi-base'; import { MappingTemplate } from './mapping-template'; /** @@ -44,7 +44,7 @@ export interface ResolverProps extends BaseResolverProps { /** * The API this resolver is attached to */ - readonly api: GraphQLApi; + readonly api: IGraphqlApi; /** * The data source this resolver is using * @@ -79,7 +79,7 @@ export class Resolver extends Construct { requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined, responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined, }); - this.resolver.addDependsOn(props.api.schema); + props.api.addSchemaDependency(this.resolver); if (props.dataSource) { this.resolver.addDependsOn(props.dataSource.ds); } diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 07fa3726c0f9b..84bc2d3af6f7f 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -96,7 +96,9 @@ "exclude": [ "no-unused-type:@aws-cdk/aws-appsync.ApiKeyConfig", "no-unused-type:@aws-cdk/aws-appsync.UserPoolConfig", - "no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction" + "no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction", + "props-physical-name:@aws-cdk/aws-appsync.GraphQLApiProps", + "from-method:@aws-cdk/aws-appsync.GraphQLApi" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts index 39a09c1f1c955..9f47904770fcb 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; -import * as cdk from '@aws-cdk/core'; import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; describe('AppSync Authorization Config', () => { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts index 37e1ecc0ea57b..a43e13afa9528 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -1,15 +1,93 @@ import '@aws-cdk/assert/jest'; -import { MappingTemplate, PrimaryKey, Values } from '../lib'; +import * as path from 'path'; +import * as db from '@aws-cdk/aws-dynamodb'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; function joined(str: string): string { return str.replace(/\s+/g, ''); } +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); +}); + +describe('DynamoDb Data Source configuration', () => { + // GIVEN + let table: db.Table; + beforeEach(() => { + table = new db.Table(stack, 'table', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, + }); + }); + + test('default configuration produces name `DynamoDbCDKDataSource`', () => { + // WHEN + api.addDynamoDbDataSource('ds', table); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addDynamoDbDataSource('ds', table, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addDynamoDbDataSource('ds', table, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple dynamo db data sources with no configuration', () => { + // WHEN + const when = () => { + api.addDynamoDbDataSource('ds', table); + api.addDynamoDbDataSource('ds', table); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); +}); + describe('DynamoDB Mapping Templates', () => { test('PutItem projecting all', () => { - const template = MappingTemplate.dynamoDbPutItem( - PrimaryKey.partition('id').is('id'), - Values.projecting(), + const template = appsync.MappingTemplate.dynamoDbPutItem( + appsync.PrimaryKey.partition('id').is('id'), + appsync.Values.projecting(), ); const rendered = joined(template.renderTemplate()); @@ -28,9 +106,9 @@ describe('DynamoDB Mapping Templates', () => { }); test('PutItem with invididual attributes', () => { - const template = MappingTemplate.dynamoDbPutItem( - PrimaryKey.partition('id').is('id'), - Values.attribute('val').is('ctx.args.val'), + const template = appsync.MappingTemplate.dynamoDbPutItem( + appsync.PrimaryKey.partition('id').is('id'), + appsync.Values.attribute('val').is('ctx.args.val'), ); const rendered = joined(template.renderTemplate()); @@ -50,9 +128,9 @@ describe('DynamoDB Mapping Templates', () => { }); test('PutItem with additional attributes', () => { - const template = MappingTemplate.dynamoDbPutItem( - PrimaryKey.partition('id').is('id'), - Values.projecting().attribute('val').is('ctx.args.val'), + const template = appsync.MappingTemplate.dynamoDbPutItem( + appsync.PrimaryKey.partition('id').is('id'), + appsync.Values.projecting().attribute('val').is('ctx.args.val'), ); const rendered = joined(template.renderTemplate()); @@ -70,4 +148,49 @@ describe('DynamoDB Mapping Templates', () => { }`), ); }); +}); + +describe('adding DynamoDb data source from imported api', () => { + // GIVEN + let table: db.Table; + beforeEach(() => { + table = new db.Table(stack, 'table', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, + }); + }); + + test('imported api can add DynamoDbDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addDynamoDbDataSource('ds', table); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add DynamoDbDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addDynamoDbDataSource('ds', table); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts new file mode 100644 index 0000000000000..25961b87cb8d2 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts @@ -0,0 +1,106 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +let endpoint: string; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); + endpoint = 'aws.amazon.com'; +}); + +describe('Http Data Source configuration', () => { + + test('default configuration produces name `HttpCDKDataSource`', () => { + // WHEN + api.addHttpDataSource('ds', endpoint); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addHttpDataSource('ds', endpoint, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addHttpDataSource('ds', endpoint, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple http data sources with no configuration', () => { + // WHEN + const when = () => { + api.addHttpDataSource('ds', endpoint); + api.addHttpDataSource('ds', endpoint); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); +}); + +describe('adding http data source from imported api', () => { + test('imported api can add HttpDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addHttpDataSource('ds', endpoint); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add HttpDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addHttpDataSource('ds', endpoint); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); +}); + + diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts new file mode 100644 index 0000000000000..b178fee2282cf --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts @@ -0,0 +1,121 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); +}); + +describe('Lambda Data Source configuration', () => { + // GIVEN + let func: lambda.Function; + beforeEach(() => { + func = new lambda.Function(stack, 'func', { + code: lambda.Code.fromAsset('test/verify'), + handler: 'iam-query.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + }); + + test('default configuration produces name `TableCDKDataSource`', () => { + // WHEN + api.addLambdaDataSource('ds', func); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addLambdaDataSource('ds', func, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addLambdaDataSource('ds', func, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple lambda data sources with no configuration', () => { + // WHEN + const when = () => { + api.addLambdaDataSource('ds', func); + api.addLambdaDataSource('ds', func); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); +}); + +describe('adding lambda data source from imported api',() => { + let func: lambda.Function; + beforeEach(() => { + func = new lambda.Function(stack, 'func', { + code: lambda.Code.fromAsset('test/verify'), + handler: 'iam-query.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + }); + + test('imported api can add LambdaDbDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addLambdaDataSource('ds', func); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add LambdaDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addLambdaDataSource('ds', func); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts new file mode 100644 index 0000000000000..13ab095ed0fee --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts @@ -0,0 +1,115 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); +}); + +describe('None Data Source configuration', () => { + + test('default configuration produces name `NoneCDKDataSource`', () => { + // WHEN + api.addNoneDataSource('ds'); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addNoneDataSource('ds', { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addNoneDataSource('ds', { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple none data sources with no configuration', () => { + // WHEN + const when = () => { + api.addNoneDataSource('ds'); + api.addNoneDataSource('ds'); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); + + test('appsync errors when creating multiple none data sources with same name configuration', () => { + // WHEN + const when = () => { + api.addNoneDataSource('ds1', { name: 'custom' }); + api.addNoneDataSource('ds2', { name: 'custom' }); + }; + + // THEN + expect(when).not.toThrowError(); + }); +}); + +describe('adding none data source from imported api', () => { + test('imported api can add NoneDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addNoneDataSource('none'); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add NoneDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addNoneDataSource('none'); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); +}); + + diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json new file mode 100644 index 0000000000000..3a0b9a76b760c --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json @@ -0,0 +1,201 @@ +[ + { + "Resources": { + "baseApiCDA4D43A": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "baseApi" + } + }, + "baseApiDefaultAPIKeyApiKey4804ACE5": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "baseApiCDA4D43A", + "ApiId" + ] + }, + "Description": "Default API Key created by CDK" + } + }, + "baseApiSchemaB12C7BB0": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "baseApiCDA4D43A", + "ApiId" + ] + }, + "Definition": "type test {\n version: String!\n}\n\ntype Query {\n getTests: [ test! ]!\n}\n\ntype Mutation {\n addTest(version: String!): test\n}\n" + } + } + }, + "Outputs": { + "ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68": { + "Value": { + "Fn::GetAtt": [ + "baseApiCDA4D43A", + "ApiId" + ] + }, + "Export": { + "Name": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + } + } + } + }, + { + "Resources": { + "ApidsServiceRoleADC7D124": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ApidsServiceRoleDefaultPolicyE5E18D6D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TestTable5769773A", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ApidsServiceRoleDefaultPolicyE5E18D6D", + "Roles": [ + { + "Ref": "ApidsServiceRoleADC7D124" + } + ] + } + }, + "Apids0DB53FEA": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "Name": "ds", + "Type": "AMAZON_DYNAMODB", + "DynamoDBConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "TableName": { + "Ref": "TestTable5769773A" + } + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "ApidsServiceRoleADC7D124", + "Arn" + ] + } + } + }, + "ApidsQuerygetTestsResolver952F49EE": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "FieldName": "getTests", + "TypeName": "Query", + "DataSourceName": "ds", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "Apids0DB53FEA" + ] + }, + "ApidsMutationaddTestResolverBCF0400B": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "FieldName": "addTest", + "TypeName": "Mutation", + "DataSourceName": "ds", + "Kind": "UNIT", + "RequestMappingTemplate": "\n #set($input = $ctx.args.test)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "ResponseMappingTemplate": "$util.toJson($ctx.result)" + }, + "DependsOn": [ + "Apids0DB53FEA" + ] + }, + "TestTable5769773A": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "api2noneC88DB89F": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "Name": "none", + "Type": "NONE" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts new file mode 100644 index 0000000000000..74a8942246e51 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts @@ -0,0 +1,68 @@ +/// !cdk-integ * + +import * as path from 'path'; +import * as db from '@aws-cdk/aws-dynamodb'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +/* + * Creates an Appsync GraphQL API in a separate stack. + * Add dependencies to imported api. + * + * Stack verification steps: + * Install dependencies and deploy integration test. Check if data sources are + * connected to the graphQL Api + * + * -- cdk deploy --app 'node integ.api-import.js' stack -- start -- + * -- aws appsync list-graphql-apis -- obtain api id -- + * -- aws appsync list-data-sources --api-id [api_id] -- testDS/None -- + * -- cdk destroy --app 'node integ.api-import.js' stack baseStack -- clean -- + */ + +const app = new cdk.App(); +const baseStack = new cdk.Stack(app, 'baseStack'); + +const baseApi = new appsync.GraphQLApi(baseStack, 'baseApi', { + name: 'baseApi', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), +}); + +const stack = new cdk.Stack(app, 'stack'); +const api = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'Api', { + graphqlApiId: `${baseApi.apiId}`, +}); + +const testTable = new db.Table(stack, 'TestTable', { + billingMode: db.BillingMode.PAY_PER_REQUEST, + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +const testDS = api.addDynamoDbDataSource('ds', testTable); + +testDS.createResolver({ + typeName: 'Query', + fieldName: 'getTests', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(), +}); + +testDS.createResolver({ + typeName: 'Mutation', + fieldName: 'addTest', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem(appsync.PrimaryKey.partition('id').auto(), appsync.Values.projecting('test')), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), +}); + +const api2 = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'api2', { + graphqlApiId: baseApi.apiId, + graphqlApiArn: baseApi.arn, +}); + +api2.addNoneDataSource('none'); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json index 1d90ae0c15d4b..eda4a662ffc58 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json @@ -63,7 +63,7 @@ "Definition": "type test @aws_iam {\n id: String!\n version: String!\n}\n\ntype Query {\n getTest(id: String!): test\n getTests: [ test! ]\n @aws_iam \n}\n\ninput TestInput {\n version: String!\n}\n\ntype Mutation {\n addTest(input: TestInput!): test\n @aws_iam\n}\n" } }, - "ApitestDataSourceDSServiceRoleE543E310": { + "ApidsServiceRoleADC7D124": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -80,7 +80,7 @@ } } }, - "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C": { + "ApidsServiceRoleDefaultPolicyE5E18D6D": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -114,15 +114,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C", + "PolicyName": "ApidsServiceRoleDefaultPolicyE5E18D6D", "Roles": [ { - "Ref": "ApitestDataSourceDSServiceRoleE543E310" + "Ref": "ApidsServiceRoleADC7D124" } ] } }, - "ApitestDataSourceDS776EA507": { + "Apids0DB53FEA": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -133,7 +133,6 @@ }, "Name": "testDataSource", "Type": "AMAZON_DYNAMODB", - "Description": "Table for Tests\"", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -144,13 +143,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApitestDataSourceDSServiceRoleE543E310", + "ApidsServiceRoleADC7D124", "Arn" ] } } }, - "ApitestDataSourceDSQuerygetTestResolver1A3C9201": { + "ApidsQuerygetTestResolverCCED7EC2": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -167,11 +166,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" + "Apids0DB53FEA", + "ApiSchema510EECD7" ] }, - "ApitestDataSourceDSQuerygetTestsResolver61ED88B6": { + "ApidsQuerygetTestsResolver952F49EE": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -188,11 +187,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" + "Apids0DB53FEA", + "ApiSchema510EECD7" ] }, - "ApitestDataSourceDSMutationaddTestResolver0D3A4591": { + "ApidsMutationaddTestResolverBCF0400B": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -209,8 +208,8 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" + "Apids0DB53FEA", + "ApiSchema510EECD7" ] }, "TestTable5769773A": { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts index d55a63adcfb62..9613070f49dbc 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -65,7 +65,7 @@ const testTable = new Table(stack, 'TestTable', { removalPolicy: RemovalPolicy.DESTROY, }); -const testDS = api.addDynamoDbDataSource('testDataSource', 'Table for Tests"', testTable); +const testDS = api.addDynamoDbDataSource('ds', testTable, {name: 'testDataSource'}); testDS.createResolver({ typeName: 'Query', 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 80d03e19f7767..0406904978e66 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -75,7 +75,7 @@ "Definition": "type ServiceVersion @aws_api_key {\n version: String!\n}\n\ntype Customer @aws_api_key {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order @aws_api_key {\n customer: String!\n order: String!\n}\n\ntype Payment @aws_api_key {\n id: String!\n amount: String!\n}\n\ninput PaymentInput {\n amount: String!\n}\n\ntype Query @aws_api_key {\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 getPayment(id: String): Payment\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation @aws_api_key {\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 savePayment(payment: PaymentInput!): Payment\n doPostOnAws: String!\n}\n" } }, - "ApiNoneDSB4E6495F": { + "Apinone1F55F3F3": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -85,11 +85,10 @@ ] }, "Name": "None", - "Type": "NONE", - "Description": "Dummy data source" + "Type": "NONE" } }, - "ApiNoneDSQuerygetServiceVersionResolver4AF8FD42": { + "ApinoneQuerygetServiceVersionResolver336A3C2C": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -106,11 +105,11 @@ "ResponseMappingTemplate": "{\"version\":\"v1\"}" }, "DependsOn": [ - "ApiNoneDSB4E6495F", + "Apinone1F55F3F3", "ApiSchema510EECD7" ] }, - "ApiCustomerDSServiceRoleA929BCF7": { + "ApicustomerDsServiceRole76CAD539": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -127,7 +126,7 @@ } } }, - "ApiCustomerDSServiceRoleDefaultPolicy8C927D33": { + "ApicustomerDsServiceRoleDefaultPolicyF8E72AE7": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -161,15 +160,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApiCustomerDSServiceRoleDefaultPolicy8C927D33", + "PolicyName": "ApicustomerDsServiceRoleDefaultPolicyF8E72AE7", "Roles": [ { - "Ref": "ApiCustomerDSServiceRoleA929BCF7" + "Ref": "ApicustomerDsServiceRole76CAD539" } ] } }, - "ApiCustomerDS8C23CB2D": { + "ApicustomerDsFE73DAC5": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -180,7 +179,6 @@ }, "Name": "Customer", "Type": "AMAZON_DYNAMODB", - "Description": "The customer data source", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -191,13 +189,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApiCustomerDSServiceRoleA929BCF7", + "ApicustomerDsServiceRole76CAD539", "Arn" ] } } }, - "ApiCustomerDSQuerygetCustomersResolver0F8B3416": { + "ApicustomerDsQuerygetCustomersResolverA74C8A2E": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -214,11 +212,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSQuerygetCustomerResolver0DC795BE": { + "ApicustomerDsQuerygetCustomerResolver3649A130": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -235,11 +233,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationaddCustomerResolverC9041B1C": { + "ApicustomerDsMutationaddCustomerResolver4DE5B517": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -256,11 +254,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationsaveCustomerResolverB707057E": { + "ApicustomerDsMutationsaveCustomerResolver241DD231": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -277,11 +275,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationsaveCustomerWithFirstOrderResolver8B1277A8": { + "ApicustomerDsMutationsaveCustomerWithFirstOrderResolver7DE2CBC8": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -298,11 +296,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationremoveCustomerResolverA0046C60": { + "ApicustomerDsMutationremoveCustomerResolverAD3AE7F5": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -319,11 +317,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiOrderDSServiceRole81A3E9E7": { + "ApiorderDsServiceRoleCC2040C0": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -340,7 +338,7 @@ } } }, - "ApiOrderDSServiceRoleDefaultPolicyDAB14B69": { + "ApiorderDsServiceRoleDefaultPolicy3315FCF4": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -374,15 +372,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApiOrderDSServiceRoleDefaultPolicyDAB14B69", + "PolicyName": "ApiorderDsServiceRoleDefaultPolicy3315FCF4", "Roles": [ { - "Ref": "ApiOrderDSServiceRole81A3E9E7" + "Ref": "ApiorderDsServiceRoleCC2040C0" } ] } }, - "ApiOrderDS4B3EEEBA": { + "ApiorderDsB50C8AAD": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -393,7 +391,6 @@ }, "Name": "Order", "Type": "AMAZON_DYNAMODB", - "Description": "The order data source", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -404,13 +401,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApiOrderDSServiceRole81A3E9E7", + "ApiorderDsServiceRoleCC2040C0", "Arn" ] } } }, - "ApiOrderDSQuerygetCustomerOrdersEqResolverFCC2003B": { + "ApiorderDsQuerygetCustomerOrdersEqResolverEF9D5350": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -427,11 +424,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersLtResolverE2C5E19E": { + "ApiorderDsQuerygetCustomerOrdersLtResolver909F3D8F": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -448,11 +445,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersLeResolver95C3D740": { + "ApiorderDsQuerygetCustomerOrdersLeResolverF230A8BE": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -469,11 +466,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersGtResolver1B99CF3D": { + "ApiorderDsQuerygetCustomerOrdersGtResolverF01F806B": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -490,11 +487,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersGeResolver5138C680": { + "ApiorderDsQuerygetCustomerOrdersGeResolver63CAD303": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -511,11 +508,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersFilterResolver4433E2B4": { + "ApiorderDsQuerygetCustomerOrdersFilterResolverCD2B8747": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -532,11 +529,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersBetweenResolver14F90CFD": { + "ApiorderDsQuerygetCustomerOrdersBetweenResolver7DEE368E": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -553,11 +550,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiPaymentDSServiceRole7A857DD9": { + "ApipaymentDsServiceRole0DAC58D6": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -574,7 +571,7 @@ } } }, - "ApiPaymentDSServiceRoleDefaultPolicy1BE875C5": { + "ApipaymentDsServiceRoleDefaultPolicy528E42B0": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -622,15 +619,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApiPaymentDSServiceRoleDefaultPolicy1BE875C5", + "PolicyName": "ApipaymentDsServiceRoleDefaultPolicy528E42B0", "Roles": [ { - "Ref": "ApiPaymentDSServiceRole7A857DD9" + "Ref": "ApipaymentDsServiceRole0DAC58D6" } ] } }, - "ApiPaymentDS69022256": { + "ApipaymentDs95C7AC36": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -641,7 +638,6 @@ }, "Name": "Payment", "Type": "AMAZON_DYNAMODB", - "Description": "The payment data source", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -650,13 +646,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApiPaymentDSServiceRole7A857DD9", + "ApipaymentDsServiceRole0DAC58D6", "Arn" ] } } }, - "ApiPaymentDSQuerygetPaymentResolver25686F48": { + "ApipaymentDsQuerygetPaymentResolverD172BFC9": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -673,11 +669,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiPaymentDS69022256", + "ApipaymentDs95C7AC36", "ApiSchema510EECD7" ] }, - "ApiPaymentDSMutationsavePaymentResolver08FBC62D": { + "ApipaymentDsMutationsavePaymentResolverE09FE5BB": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -694,11 +690,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiPaymentDS69022256", + "ApipaymentDs95C7AC36", "ApiSchema510EECD7" ] }, - "ApihttpDSServiceRole8B5C9457": { + "ApidsServiceRoleADC7D124": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -715,7 +711,7 @@ } } }, - "ApihttpDS91F12990": { + "Apids0DB53FEA": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -726,19 +722,18 @@ }, "Name": "http", "Type": "HTTP", - "Description": "The http data source", "HttpConfig": { "Endpoint": "https://aws.amazon.com/" }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApihttpDSServiceRole8B5C9457", + "ApidsServiceRoleADC7D124", "Arn" ] } } }, - "ApihttpDSMutationdoPostOnAwsResolverA9027953": { + "ApidsMutationdoPostOnAwsResolver9583D8A3": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -755,7 +750,7 @@ "ResponseMappingTemplate": "\n ## Raise a GraphQL field error in case of a datasource invocation error\n #if($ctx.error)\n $util.error($ctx.error.message, $ctx.error.type)\n #end\n ## if the response status code is not 200, then return an error. Else return the body **\n #if($ctx.result.statusCode == 200)\n ## If response is 200, return the body.\n $ctx.result.body\n #else\n ## If response is not 200, append the response to error block.\n $utils.appendError($ctx.result.body, \"$ctx.result.statusCode\")\n #end\n " }, "DependsOn": [ - "ApihttpDS91F12990", + "Apids0DB53FEA", "ApiSchema510EECD7" ] }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 3b8564dba6865..add2e19cb8b68 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -54,7 +54,7 @@ const api = new GraphQLApi(stack, 'Api', { }, }); -const noneDS = api.addNoneDataSource('None', 'Dummy data source'); +const noneDS = api.addNoneDataSource('none', {name: 'None'}); noneDS.createResolver({ typeName: 'Query', @@ -99,9 +99,9 @@ new Table(stack, 'PaymentTable', { const paymentTable = Table.fromTableName(stack, 'ImportedPaymentTable', 'PaymentTable'); -const customerDS = api.addDynamoDbDataSource('Customer', 'The customer data source', customerTable); -const orderDS = api.addDynamoDbDataSource('Order', 'The order data source', orderTable); -const paymentDS = api.addDynamoDbDataSource('Payment', 'The payment data source', paymentTable); +const customerDS = api.addDynamoDbDataSource('customerDs', customerTable, {name: 'Customer'}); +const orderDS = api.addDynamoDbDataSource('orderDs', orderTable, {name: 'Order'}); +const paymentDS = api.addDynamoDbDataSource('paymentDs', paymentTable, {name: 'Payment'}); customerDS.createResolver({ typeName: 'Query', @@ -189,7 +189,7 @@ paymentDS.createResolver({ responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); -const httpDS = api.addHttpDataSource('http', 'The http data source', 'https://aws.amazon.com/'); +const httpDS = api.addHttpDataSource('ds', 'https://aws.amazon.com/', {name: 'http'}); httpDS.createResolver({ typeName: 'Mutation', From 6f1782fa679b5bc1a3bb3ec9e4afb71f8c64a951 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 14 Aug 2020 20:46:02 -0700 Subject: [PATCH 031/422] feat(appsync): code-first schema allows for object type definition (#9417) AppSync now able to generate a code-first approach to generating object and interface types through code. You can also append to schema through the `GraphQLApi.appendToSchema` function. Feature List: - GraphqlTypes - InterfaceTypes - ObjectTypes - Directives All details can be found in the README.md Fixes #9307 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-appsync/README.md | 171 ++++- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 60 +- packages/@aws-cdk/aws-appsync/lib/index.ts | 1 + .../@aws-cdk/aws-appsync/lib/schema-types.ts | 604 ++++++++++++++++++ .../test/appsync-code-first.test.ts | 400 ++++++++++++ .../aws-appsync/test/appsync-schema.test.ts | 60 +- .../test/integ.graphql-schema.expected.json | 165 +++++ .../aws-appsync/test/integ.graphql-schema.ts | 66 ++ .../test/object-type-definitions.ts | 19 + .../test/scalar-type-defintions.ts | 64 ++ .../test/verify.integ.graphql-schema.sh | 32 + 11 files changed, 1606 insertions(+), 36 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-types.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/scalar-type-defintions.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index cb3b4d3708c5f..96d0ce93d1879 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -60,7 +60,7 @@ const api = new appsync.GraphQLApi(stack, 'Api', { const demoTable = new db.Table(stack, 'DemoTable', { partitionKey: { name: 'id', - type: AttributeType.STRING, + type: db.AttributeType.STRING, }, }); @@ -179,3 +179,172 @@ api.grantMutation(role, 'updateExample'); // For custom types and granular design api.grant(role, appsync.IamResource.ofType('Mutation', 'updateExample'), 'appsync:GraphQL'); ``` + +### Code-First Schema + +CDK offers the ability to generate your schema in a code-first approach. +A code-first approach offers a developer workflow with: +- **modularity**: organizing schema type definitions into different files +- **reusability**: simplifying down boilerplate/repetitive code +- **consistency**: resolvers and schema definition will always be synced + +The code-first approach allows for dynamic schema generation. You can generate your schema based on variables and templates to reduce code duplication. + +#### Code-First Example + +We are going to reference the [example](#Example) through a code-first approach. + +```ts +import * as appsync from '@aws-cdk/aws-appsync'; +import * as db from '@aws-cdk/aws-dynamodb'; + +const api = new appsync.GraphQLApi(stack, 'Api', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.CODE, + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.IAM + }, + }, +}); + +const demoTable = new db.Table(stack, 'DemoTable', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, +}); + +const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', demoTable); + +// Schema Definition starts here + +const demo = api.addType('demo', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, +}); + +``` + +#### GraphQL Types + +One of the benefits of GraphQL is its strongly typed nature. We define the +types within an object, query, mutation, interface, etc. as **GraphQL Types**. + +GraphQL Types are the building blocks of types, whether they are scalar, objects, +interfaces, etc. GraphQL Types can be: +- [**Scalar Types**](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html): Id, Int, String, AWSDate, etc. +- **Object Types**: types that you generate (i.e. `demo` from the example above) +- **Interface Types**: abstract types that define the base implementation of other +Intermediate Types + +More concretely, GraphQL Types are simply the types appended to variables. +Referencing the object type `Demo` in the previous example, the GraphQL Types +is `String!` and is applied to both the names `id` and `version`. + +#### Intermediate Types + +Intermediate Types are abstractions above Scalar Types. They have a set of defined +fields, where each field corresponds to another type in the system. Intermediate +Types will be the meat of your GraphQL Schema as they are the types defined by you. + +Intermediate Types include: +- [**Interface Types**](#Interface-Types) +- [**Object Types**](#Object-Types) + +#### Interface Types + +**Interface Types** are abstract types that define the implementation of other +intermediate types. They are useful for eliminating duplication and can be used +to generate Object Types with less work. + +You can create Interface Types ***externally***. +```ts +const node = new appsync.InterfaceType('Node', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + }, +}); +``` + +#### Object Types + +**Object Types** are types that you declare. For example, in the [code-first example](#code-first-example) +the `demo` variable is an **Object Type**. **Object Types** are defined by +GraphQL Types and are only usable when linked to a GraphQL Api. + +You can create Object Types in three ways: + +1. Object Types can be created ***externally***. + ```ts + const api = new appsync.GraphQLApi(stack, 'Api', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); + const demo = new appsync.ObjectType('Demo', { + defintion: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + + api.appendToSchema(object.toString()); + ``` + > This method allows for reusability and modularity, ideal for larger projects. + For example, imagine moving all Object Type definition outside the stack. + + `scalar-types.ts` - a file for scalar type definitions + ```ts + export const required_string = new appsync.GraphqlType.string({ isRequired: true }); + ``` + + `object-types.ts` - a file for object type definitions + ```ts + import { required_string } from './scalar-types'; + export const demo = new appsync.ObjectType('Demo', { + defintion: { + id: required_string, + version: required_string, + }, + }); + ``` + + `cdk-stack.ts` - a file containing our cdk stack + ```ts + import { demo } from './object-types'; + api.appendToSchema(demo.toString()); + ``` + +2. Object Types can be created ***externally*** from an Interface Type. + ```ts + const node = new appsync.InterfaceType('Node', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + const demo = new appsync.ObjectType.implementInterface('Demo', { + interfaceTypes: [ node ], + defintion: { + version: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + ``` + > This method allows for reusability and modularity, ideal for reducing code duplication. + +3. Object Types can be created ***internally*** within the GraphQL API. + ```ts + const api = new appsync.GraphQLApi(stack, 'Api', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); + api.addType('Demo', { + defintion: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + ``` + > This method provides easy use and is ideal for smaller projects. + \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index af51819d16f42..ac11c4a98decf 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -4,6 +4,7 @@ import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-c import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; +import { ObjectType, ObjectTypeProps } from './schema-types'; /** * enum with all possible values for AppSync authorization type @@ -672,19 +673,6 @@ export class GraphQLApi extends GraphqlApiBase { return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined; } - /** - * Sets schema defintiion to input if schema mode is configured with SchemaDefinition.CODE - * - * @param definition string that is the graphql representation of schema - * @experimental temporary - */ - public updateDefinition (definition: string): void{ - if ( this.schemaMode != SchemaDefinition.CODE ) { - throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); - } - this.schema.definition = definition; - } - /** * Define schema based on props configuration * @param file the file name/s3 location of Schema @@ -692,13 +680,13 @@ export class GraphQLApi extends GraphqlApiBase { private defineSchema(file?: string): CfnGraphQLSchema { let definition; - if ( this.schemaMode == SchemaDefinition.FILE && !file) { + if ( this.schemaMode === SchemaDefinition.FILE && !file) { throw new Error('schemaDefinitionFile must be configured if using FILE definition mode.'); - } else if ( this.schemaMode == SchemaDefinition.FILE && file ) { + } else if ( this.schemaMode === SchemaDefinition.FILE && file ) { definition = readFileSync(file).toString('UTF-8'); - } else if ( this.schemaMode == SchemaDefinition.CODE && !file ) { + } else if ( this.schemaMode === SchemaDefinition.CODE && !file ) { definition = ''; - } else if ( this.schemaMode == SchemaDefinition.CODE && file) { + } else if ( this.schemaMode === SchemaDefinition.CODE && file) { throw new Error('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile'); } @@ -707,4 +695,42 @@ export class GraphQLApi extends GraphqlApiBase { definition, }); } + + /** + * Escape hatch to append to Schema as desired. Will always result + * in a newline. + * + * @param addition the addition to add to schema + * @param delimiter the delimiter between schema and addition + * @default - '' + * + * @experimental + */ + public appendToSchema(addition: string, delimiter?: string): void { + if ( this.schemaMode != SchemaDefinition.CODE ) { + throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); + } + const sep = delimiter ?? ''; + this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; + } + + /** + * Add an object type to the schema + * + * @param name the name of the object type + * @param props the definition + * + * @experimental + */ + public addType(name: string, props: ObjectTypeProps): ObjectType { + if ( this.schemaMode != SchemaDefinition.CODE ) { + throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); + }; + const type = new ObjectType(name, { + definition: props.definition, + directives: props.directives, + }); + this.appendToSchema(type.toString()); + return type; + } } diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index e0736c1ecc397..d418c63f0232c 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -4,5 +4,6 @@ export * from './key'; export * from './data-source'; export * from './mapping-template'; export * from './resolver'; +export * from './schema-types'; export * from './graphqlapi'; export * from './graphqlapi-base'; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts new file mode 100644 index 0000000000000..2649fb41bc64b --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -0,0 +1,604 @@ +/** + * Directives for types + * + * i.e. @aws_iam or @aws_subscribe + * + * @experimental + */ +export class Directive { + /** + * Add the @aws_iam directive + */ + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + /** + * Add a custom directive + * + * @param statement - the directive statement to append + * Note: doesn't guarantee functionality + */ + public static custom(statement: string): Directive { + return new Directive(statement); + } + + /** + * the directive statement + */ + public readonly statement: string; + + private constructor(statement: string) { this.statement = statement; } +} + +/** + * Properties for configuring an Intermediate Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * + * @experimental + */ +export interface IntermediateTypeProps { + /** + * the attributes of this type + */ + readonly definition: { [key: string]: GraphqlType }; +} + +/** + * Interface Types are abstract types that includes a certain set of fields + * that other types must include if they implement the interface. + * + * @experimental + */ +export class InterfaceType { + /** + * the name of this type + */ + public readonly name: string; + /** + * the attributes of this type + */ + public readonly definition: { [key: string]: GraphqlType }; + + public constructor(name: string, props: IntermediateTypeProps) { + this.name = name; + this.definition = props.definition; + } + + /** + * Create an GraphQL Type representing this Intermediate Type + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + return GraphqlType.intermediate({ + isList: options?.isList, + isRequired: options?.isRequired, + isRequiredList: options?.isRequiredList, + intermediateType: this, + }); + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let schemaAddition = `interface ${this.name} {\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } +} + +/** + * Properties for configuring an Object Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * @param interfaceTypes - the interfaces that this object type implements + * @param directives - the directives for this object type + * + * @experimental + */ +export interface ObjectTypeProps extends IntermediateTypeProps { + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + readonly interfaceTypes?: InterfaceType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + readonly directives?: Directive[]; +} + +/** + * Object Types are types declared by you. + * + * @experimental + */ +export class ObjectType extends InterfaceType { + /** + * A method to define Object Types from an interface + */ + public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { + if (!props.interfaceTypes || !props.interfaceTypes.length) { + throw new Error('Static function `implementInterface` requires an interfaceType to implement'); + } + return new ObjectType(name, { + interfaceTypes: props.interfaceTypes, + definition: props.interfaceTypes.reduce((def, interfaceType) => { + return Object.assign({}, def, interfaceType.definition); + }, props.definition), + directives: props.directives, + }); + } + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + public readonly interfaceTypes?: InterfaceType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + public readonly directives?: Directive[]; + + public constructor(name: string, props: ObjectTypeProps) { + super(name, props); + this.interfaceTypes = props.interfaceTypes; + this.directives = props.directives; + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let title = this.name; + if(this.interfaceTypes && this.interfaceTypes.length){ + title = `${title} implements`; + this.interfaceTypes.map((interfaceType) => { + title = `${title} ${interfaceType.name},`; + }); + title = title.slice(0, -1); + } + const directives = this.generateDirectives(this.directives); + let schemaAddition = `type ${title} ${directives}{\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } + + /** + * Utility function to generate directives + * + * @param directives the directives of a given type + * @param delimiter the separator betweeen directives + * @default - ' ' + */ + private generateDirectives(directives?: Directive[], delimiter?: string): string{ + let schemaAddition = ''; + if (!directives){ return schemaAddition; } + directives.map((directive) => { + schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; + }); + return schemaAddition; + } +} + +/** + * Base options for GraphQL Types + * + * @option isList - is this attribute a list + * @option isRequired - is this attribute non-nullable + * @option isRequiredList - is this attribute a non-nullable list + * + * @experimental + */ +export interface BaseGraphqlTypeOptions { + /** + * property determining if this attribute is a list + * i.e. if true, attribute would be [Type] + * + * @default - false + */ + readonly isList?: boolean; + + /** + * property determining if this attribute is non-nullable + * i.e. if true, attribute would be Type! + * + * @default - false + */ + readonly isRequired?: boolean; + + /** + * property determining if this attribute is a non-nullable list + * i.e. if true, attribute would be [ Type ]! + * or if isRequired true, attribe would be [ Type! ]! + * + * @default - false + */ + readonly isRequiredList?: boolean; +} + +/** + * Options for GraphQL Types + * + * @option isList - is this attribute a list + * @option isRequired - is this attribute non-nullable + * @option isRequiredList - is this attribute a non-nullable list + * @option objectType - the object type linked to this attribute + * + * @experimental + */ +export interface GraphqlTypeOptions extends BaseGraphqlTypeOptions { + /** + * the intermediate type linked to this attribute + * @default - no intermediate type + */ + readonly intermediateType?: InterfaceType; +} + +/** + * The GraphQL Types in AppSync's GraphQL. GraphQL Types are the + * building blocks for object types, queries, mutations, etc. They are + * types like String, Int, Id or even Object Types you create. + * + * i.e. `String`, `String!`, `[String]`, `[String!]`, `[String]!` + * + * GraphQL Types are used to define the entirety of schema. + */ +export class GraphqlType { + /** + * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. + * + * Often used as a key for a cache and not intended to be human-readable. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static id(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.ID, options); + } + /** + * `String` scalar type is a free-form human-readable text. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static string(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.STRING, options); + } + /** + * `Int` scalar type is a signed non-fractional numerical value. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static int(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.INT, options); + } + /** + * `Float` scalar type is a signed double-precision fractional value. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static float(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.FLOAT, options); + } + /** + * `Boolean` scalar type is a boolean value: true or false. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static boolean(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.BOOLEAN, options); + } + + /** + * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsDate(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_DATE, options); + } + /** + * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. + * + * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsTime(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_TIME, options); + } + /** + * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsDateTime(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_DATE_TIME, options); + } + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsTimestamp(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_TIMESTAMP, options); + } + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsEmail(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_EMAIL, options); + } + /** + * `AWSJson` scalar type represents a JSON string. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsJson(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_JSON, options); + } + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsUrl(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_URL, options); + } + /** + * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. + * + * The number can specify a country code at the beginning, but is not required for US phone numbers. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsPhone(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_PHONE, options); + } + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public static awsIpAddress(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_IP_ADDRESS, options); + } + + /** + * an intermediate type to be added as an attribute + * (i.e. an interface or an object type) + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + * - intermediateType + */ + public static intermediate(options?: GraphqlTypeOptions): GraphqlType { + if (!options?.intermediateType) { + throw new Error('GraphQL Type of interface must be configured with corresponding Intermediate Type'); + } + return new GraphqlType(Type.INTERMEDIATE, options); + } + + /** + * the type of attribute + */ + public readonly type: Type; + + /** + * property determining if this attribute is a list + * i.e. if true, attribute would be `[Type]` + * + * @default - false + */ + public readonly isList: boolean; + + /** + * property determining if this attribute is non-nullable + * i.e. if true, attribute would be `Type!` and this attribute + * must always have a value + * + * @default - false + */ + public readonly isRequired: boolean; + + /** + * property determining if this attribute is a non-nullable list + * i.e. if true, attribute would be `[ Type ]!` and this attribute's + * list must always have a value + * + * @default - false + */ + public readonly isRequiredList: boolean; + + /** + * the intermediate type linked to this attribute + * (i.e. an interface or an object) + * + * @default - no intermediate type + */ + public readonly intermediateType?: InterfaceType; + + private constructor(type: Type, options?: GraphqlTypeOptions) { + this.type = type; + this.isList = options?.isList ?? false; + this.isRequired = options?.isRequired ?? false; + this.isRequiredList = options?.isRequiredList ?? false; + this.intermediateType = options?.intermediateType; + } + + /** + * Generate the string for this attribute + */ + public toString(): string{ + // If an Object Type, we use the name of the Object Type + let type = this.intermediateType ? this.intermediateType?.name : this.type; + // If configured as required, the GraphQL Type becomes required + type = this.isRequired ? `${type}!` : type; + // If configured with isXxxList, the GraphQL Type becomes a list + type = this.isList || this.isRequiredList ? `[${type}]` : type; + // If configured with isRequiredList, the list becomes required + type = this.isRequiredList ? `${type}!` : type; + return type; + } +} + +/** + * Enum containing the Types that can be used to define ObjectTypes + */ +export enum Type { + /** + * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. + * + * Often used as a key for a cache and not intended to be human-readable. + */ + ID = 'ID', + /** + * `String` scalar type is a free-form human-readable text. + */ + STRING = 'String', + /** + * `Int` scalar type is a signed non-fractional numerical value. + */ + INT = 'Int', + /** + * `Float` scalar type is a signed double-precision fractional value. + */ + FLOAT = 'Float', + /** + * `Boolean` scalar type is a boolean value: true or false. + */ + BOOLEAN = 'Boolean', + + /** + * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + */ + AWS_DATE = 'AWSDate', + /** + * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. + * + * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Times + */ + AWS_TIME = 'AWSTime', + /** + * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + */ + AWS_DATE_TIME = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWS_TIMESTAMP = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWS_EMAIL = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWS_JSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWS_URL = 'AWSURL', + /** + * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. + * + * The number can specify a country code at the beginning, but is not required for US phone numbers. + */ + AWS_PHONE = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWS_IP_ADDRESS = 'AWSIPAddress', + + /** + * Type used for Intermediate Types + * (i.e. an interface or an object type) + */ + INTERMEDIATE = 'INTERMEDIATE', +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts new file mode 100644 index 0000000000000..34e0eeaade11c --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -0,0 +1,400 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; +import * as t from './scalar-type-defintions'; + +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); +}); + +describe('testing addType for schema definition mode `code`', () => { + test('check scalar type id with all options', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + dupid: t.dup_id, + }, + }); + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + +}); + +describe('testing all GraphQL Types', () => { + test('scalar type id', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.id, + }, + }); + const out = 'type Test {\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type string', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.string, + }, + }); + const out = 'type Test {\n id: String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type int', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.int, + }, + }); + const out = 'type Test {\n id: Int\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type float', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.float, + }, + }); + const out = 'type Test {\n id: Float\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type boolean', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.boolean, + }, + }); + const out = 'type Test {\n id: Boolean\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSDate', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsDate, + }, + }); + const out = 'type Test {\n id: AWSDate\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSTime', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsTime, + }, + }); + const out = 'type Test {\n id: AWSTime\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSDateTime', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsDateTime, + }, + }); + const out = 'type Test {\n id: AWSDateTime\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSTimestamp', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsTimestamp, + }, + }); + const out = 'type Test {\n id: AWSTimestamp\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSEmail', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsEmail, + }, + }); + const out = 'type Test {\n id: AWSEmail\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSJSON', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsJson, + }, + }); + const out = 'type Test {\n id: AWSJSON\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + + test('scalar type AWSUrl', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsUrl, + }, + }); + const out = 'type Test {\n id: AWSURL\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSPhone', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsPhone, + }, + }); + const out = 'type Test {\n id: AWSPhone\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSIPAddress', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsIpAddress, + }, + }); + const out = 'type Test {\n id: AWSIPAddress\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); + +describe('testing InterfaceType properties', () => { + let baseTest: appsync.InterfaceType; + beforeEach(()=>{ + baseTest = new appsync.InterfaceType('baseTest', { + definition: { + id: t.id, + }, + }); + }); + test('basic InterfaceType produces correct schema', () => { + // WHEN + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Interface Type can be a Graphql Type', () => { + // WHEN + const graphqlType = baseTest.attribute(); + + const test = new appsync.ObjectType('Test', { + definition: { + test: graphqlType, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: baseTest\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); + +describe('testing Object Type properties', () => { + + test('errors when no InterfaceTypes are specified', () => { + // WHEN + const when = () => { + appsync.ObjectType.implementInterface('objectTest', { + definition: { + id2: t.id, + }, + }); + }; + + // THEN + expect(when).toThrowError('Static function `implementInterface` requires an interfaceType to implement'); + }); + + test('errors when implementing empty InterfaceTypes properties', () => { + // WHEN + const when = () => { + appsync.ObjectType.implementInterface('objectTest', { + interfaceTypes: [], + definition: { + id2: t.id, + }, + }); + }; + + // THEN + expect(when).toThrowError('Static function `implementInterface` requires an interfaceType to implement'); + }); + + test('ObjectType can implement from interface types', () => { + // WHEN + const baseTest = new appsync.InterfaceType('baseTest', { + definition: { + id: t.id, + }, + }); + const objectTest = appsync.ObjectType.implementInterface('objectTest', { + interfaceTypes: [baseTest], + definition: { + id2: t.id, + }, + directives: [appsync.Directive.custom('@test')], + }); + + api.appendToSchema(baseTest.toString()); + api.appendToSchema(objectTest.toString()); + const gql_interface = 'interface baseTest {\n id: ID\n}\n'; + const gql_object = 'type objectTest implements baseTest @test {\n id2: ID\n id: ID\n}\n'; + const out = `${gql_interface}${gql_object}`; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('ObjectType can implement from multiple interface types', () => { + // WHEN + const baseTest = new appsync.InterfaceType('baseTest', { + definition: { id: t.id }, + }); + const anotherTest = new appsync.InterfaceType('anotherTest', { + definition: { id2: t.id }, + }); + const objectTest = appsync.ObjectType.implementInterface('objectTest', { + interfaceTypes: [anotherTest, baseTest], + definition: { + id3: t.id, + }, + }); + + api.appendToSchema(baseTest.toString()); + api.appendToSchema(anotherTest.toString()); + api.appendToSchema(objectTest.toString()); + + const gql_interface = 'interface baseTest {\n id: ID\n}\ninterface anotherTest {\n id2: ID\n}\n'; + const gql_object = 'type objectTest implements anotherTest, baseTest {\n id3: ID\n id2: ID\n id: ID\n}\n'; + const out = `${gql_interface}${gql_object}`; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can be a Graphql Type', () => { + // WHEN + const baseTest = new appsync.ObjectType('baseTest', { + definition: { + id: t.id, + }, + }); + const graphqlType = baseTest.attribute(); + const test = new appsync.ObjectType('Test', { + definition: { + test: graphqlType, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: baseTest\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts index d031dbf77d542..25542722f92e9 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -2,6 +2,7 @@ import { join } from 'path'; import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; +import * as t from './scalar-type-defintions'; // Schema Definitions const type = 'type test {\n version: String!\n}\n\n'; @@ -14,7 +15,7 @@ beforeEach(() => { stack = new cdk.Stack(); }); -describe('testing schema definition mode `code`', () => { +describe('basic testing schema definition mode `code`', () => { test('definition mode `code` produces empty schema definition', () => { // WHEN @@ -23,23 +24,25 @@ describe('testing schema definition mode `code`', () => { schemaDefinition: appsync.SchemaDefinition.CODE, }); - //THEN + // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: '', }); }); - test('definition mode `code` generates correct schema with updateDefinition', () => { + test('definition mode `code` generates correct schema with appendToSchema', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schemaDefinition: appsync.SchemaDefinition.CODE, }); - api.updateDefinition(`${type}${query}${mutation}`); + api.appendToSchema(type); + api.appendToSchema(query); + api.appendToSchema(mutation); - //THEN + // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${type}${query}${mutation}`, + Definition: `${type}\n${query}\n${mutation}\n`, }); }); @@ -53,7 +56,7 @@ describe('testing schema definition mode `code`', () => { }); }; - //THEN + // THEN expect(when).toThrowError('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile'); }); @@ -69,36 +72,57 @@ describe('testing schema definition mode `file`', () => { schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), }); - //THEN + // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: `${type}${query}${mutation}`, }); }); - test('definition mode `file` errors when calling updateDefiniton function', () => { + test('definition mode `file` errors when schemaDefinitionFile is not configured', () => { + // WHEN + const when = () => { + new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.FILE, + }); + }; + + // THEN + expect(when).toThrowError('schemaDefinitionFile must be configured if using FILE definition mode.'); + }); + + test('definition mode `file` errors when addType is called', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schemaDefinition: appsync.SchemaDefinition.FILE, schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), }); - const when = () => { api.updateDefinition('error'); }; - //THEN + const when = () => { + api.addType('blah', { + definition: { fail: t.id }, + }); + }; + + // THEN expect(when).toThrowError('API cannot add type because schema definition mode is not configured as CODE.'); }); - test('definition mode `file` errors when schemaDefinitionFile is not configured', () => { + test('definition mode `file` errors when appendToSchema is called', () => { // WHEN + const api = new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), + }); + const when = () => { - new appsync.GraphQLApi(stack, 'API', { - name: 'demo', - schemaDefinition: appsync.SchemaDefinition.FILE, - }); + api.appendToSchema('blah'); }; - //THEN - expect(when).toThrowError('schemaDefinitionFile must be configured if using FILE definition mode.'); + // THEN + expect(when).toThrowError('API cannot append to schema because schema definition mode is not configured as CODE.'); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json new file mode 100644 index 0000000000000..911fb65da3e0d --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -0,0 +1,165 @@ +{ + "Resources": { + "codefirstapi1A3CC7D2": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "api" + } + }, + "codefirstapiDefaultAPIKeyApiKeyB73DF94B": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "Description": "Default API Key created by CDK" + } + }, + "codefirstapiSchema148B6CDE": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\ntype Species {\n name: String\n classification: String\n designation: String\n averageHeight: Float\n averageLifespan: Int\n eyeColors: [String]\n hairColors: [String]\n skinColors: [String]\n language: String\n homeworld: Planet\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" + } + }, + "codefirstapiplanetsServiceRole2F4AA8E7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "codefirstapiplanetsServiceRoleDefaultPolicy76FE4F2C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "table8235A42E", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "codefirstapiplanetsServiceRoleDefaultPolicy76FE4F2C", + "Roles": [ + { + "Ref": "codefirstapiplanetsServiceRole2F4AA8E7" + } + ] + } + }, + "codefirstapiplanets0F6BB479": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "Name": "planets", + "Type": "AMAZON_DYNAMODB", + "DynamoDBConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "TableName": { + "Ref": "table8235A42E" + } + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "codefirstapiplanetsServiceRole2F4AA8E7", + "Arn" + ] + } + } + }, + "codefirstapiplanetsQuerygetPlanetsResolver633EA597": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "FieldName": "getPlanets", + "TypeName": "Query", + "DataSourceName": "planets", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "codefirstapiplanets0F6BB479", + "codefirstapiSchema148B6CDE" + ] + }, + "table8235A42E": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts new file mode 100644 index 0000000000000..4dc10dbf5479f --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -0,0 +1,66 @@ +import * as cdk from '@aws-cdk/core'; +import * as db from '@aws-cdk/aws-dynamodb'; +import * as appsync from '../lib'; +import * as ObjectType from './object-type-definitions'; +import * as ScalarType from './scalar-type-defintions'; + +/* + * Creates an Appsync GraphQL API and schema in a code-first approach. + * + * Stack verification steps: + * Deploy stack, get api key and endpoinScalarType. Check if schema connects to data source. + * + * -- bash verify.integ.graphql-schema.sh --start -- start -- + * -- aws appsync list-graphql-apis -- obtain apiId & endpoint -- + * -- aws appsync list-api-keys --api-id [apiId] -- obtain api key -- + * -- bash verify.integ.graphql-schema.sh --check [apiKey] [url] -- check if success -- + * -- bash verify.integ.graphql-schema.sh --clean -- clean -- + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'code-first-schema'); + +const api = new appsync.GraphQLApi(stack, 'code-first-api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, +}); + +const planet = ObjectType.planet; +api.appendToSchema(planet.toString()); + +api.addType('Species', { + definition: { + name: ScalarType.string, + classification: ScalarType.string, + designation: ScalarType.string, + averageHeight: ScalarType.float, + averageLifespan: ScalarType.int, + eyeColors: ScalarType.list_string, + hairColors: ScalarType.list_string, + skinColors: ScalarType.list_string, + language: ScalarType.string, + homeworld: planet.attribute(), + created: ScalarType.string, + edited: ScalarType.string, + id: ScalarType.required_id, + }, +}); + +api.appendToSchema('type Query {\n getPlanets: [Planet]\n}', '\n'); + +const table = new db.Table(stack, 'table', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, +}); + +const tableDS = api.addDynamoDbDataSource('planets', table); + +tableDS.createResolver({ + typeName: 'Query', + fieldName: 'getPlanets', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(), +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts b/packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts new file mode 100644 index 0000000000000..138f2d7e1faa1 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts @@ -0,0 +1,19 @@ +import { ObjectType } from '../lib'; +import * as ScalarType from './scalar-type-defintions'; + +export const planet = new ObjectType('Planet', { + definition: { + name: ScalarType.string, + diameter: ScalarType.int, + rotationPeriod: ScalarType.int, + orbitalPeriod: ScalarType.int, + gravity: ScalarType.string, + population: ScalarType.list_string, + climates: ScalarType.list_string, + terrains: ScalarType.list_string, + surfaceWater: ScalarType.float, + created: ScalarType.string, + edited: ScalarType.string, + id: ScalarType.required_id, + }, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/scalar-type-defintions.ts b/packages/@aws-cdk/aws-appsync/test/scalar-type-defintions.ts new file mode 100644 index 0000000000000..e13df50ca0fc4 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/scalar-type-defintions.ts @@ -0,0 +1,64 @@ +import { GraphqlType } from '../lib'; + +// ID +export const id = GraphqlType.id(); +export const list_id = GraphqlType.id({ + isList: true, +}); +export const required_id = GraphqlType.id({ + isRequired: true, +}); +export const required_list_id = GraphqlType.id({ + isRequiredList: true, +}); +export const required_list_required_id = GraphqlType.id({ + isRequired: true, + isRequiredList: true, +}); +export const dup_id = GraphqlType.id({ + isList: true, + isRequired: true, + isRequiredList: true, +}); + +// STRING +export const string = GraphqlType.string(); +export const list_string = GraphqlType.string({ + isList: true, +}); + +// INT +export const int = GraphqlType.int(); + +// FLOAT +export const float = GraphqlType.float(); + +// BOOLEAN +export const boolean = GraphqlType.boolean(); + +// AWSDate +export const awsDate = GraphqlType.awsDate(); + +// AWSTime +export const awsTime = GraphqlType.awsTime(); + +// AWSDateTime +export const awsDateTime = GraphqlType.awsDateTime(); + +// AWSTimestamp +export const awsTimestamp = GraphqlType.awsTimestamp(); + +// AWSEmail +export const awsEmail = GraphqlType.awsEmail(); + +// AWSJSON +export const awsJson = GraphqlType.awsJson(); + +// AWSUrl +export const awsUrl = GraphqlType.awsUrl(); + +// AWSPhone +export const awsPhone = GraphqlType.awsPhone(); + +// AWSIPAddress +export const awsIpAddress = GraphqlType.awsIpAddress(); diff --git a/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh new file mode 100644 index 0000000000000..95662531fece5 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +function error { + printf "\e[91;5;81m$@\e[0m\n" +} + +function usage { + echo "###############################################################################" + echo "# run 'verify.integ.graphql-schema.sh --start' to deploy #" + echo "# run 'verify.integ.graphql-schema.sh --check [APIKEY] [ENDPOINT]' to run check #" + echo "# run 'verify.integ.graphql-schema.sh --clean' to clean up stack #" + echo "###############################################################################" +} + +if [[ "$1" == "--start" ]]; then + cdk deploy --app "node integ.graphql-schema.js" +elif [[ "$1" == "--check" ]]; then + if [[ -z $2 || -z $3 ]]; then + error "Error: --check flag requires [APIKEY] [ENDPOINT]" + usage + exit 1 + fi + echo THIS TEST SHOULD SUCCEED + curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:$2" -d '{ "query": "query { getPlanets { id name } }" }" }' $3 + echo "" +elif [[ "$1" == "--clean" ]];then + cdk destroy --app "node integ.graphql-schema.js" +else + error "Error: use flags --start, --check, --clean" + usage + exit 1 +fi \ No newline at end of file From 9ca1d0228824014f2397a4392bc264ab69f7ad1b Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 16 Aug 2020 08:39:49 -0700 Subject: [PATCH 032/422] fix(build): Prereq check - support paths with spaces. Prereq check before `yarn build` fails when there are spaces in the path. This is common for WSL2 on windows. For example, mvn is in the path at `/mnt/c/Program Files/`. Testing - Ran check on linux paths and windows paths with and without spaces. Tested with correct version, wrong version, and missing dep. fixes #9749 --- scripts/check-prerequisites.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-prerequisites.sh b/scripts/check-prerequisites.sh index fb2011360eb0e..12e85ae3789f1 100755 --- a/scripts/check-prerequisites.sh +++ b/scripts/check-prerequisites.sh @@ -22,7 +22,7 @@ check_which() { w=$(which ${app}) || w="" - if [ -z $w ] || [ $w == "$app not found" ] + if [ -z "$w" ] || [ "$w" == "$app not found" ] then die "Missing dependency: $app. Install $app >= $min" else From 4ee37a4b92218803c5d0123161f5502cc9e9a62c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Aug 2020 05:19:54 +0200 Subject: [PATCH 033/422] feat(amplify): automatic branch deletion (#9663) Add the `autoBranchDeletion` prop to control automatic branch deletion. Closes #9650 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-amplify/README.md | 9 ++++++--- packages/@aws-cdk/aws-amplify/lib/app.ts | 11 ++++++++++- packages/@aws-cdk/aws-amplify/test/app.test.ts | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-amplify/README.md b/packages/@aws-cdk/aws-amplify/README.md index 9a6ec9b6e48f0..49f8f5028c5ab 100644 --- a/packages/@aws-cdk/aws-amplify/README.md +++ b/packages/@aws-cdk/aws-amplify/README.md @@ -140,14 +140,17 @@ app.addBranch('feature/next', { }); ``` -### Automatically creating branches -Use the `autoBranchCreation` prop to automatically create new branches: +### Automatically creating and deleting branches +Use the `autoBranchCreation` and `autoBranchDeletion` props to control creation/deletion +of branches: + ```ts const amplifyApp = new amplify.App(this, 'MyApp', { repository: 'https://github.com//', oauthToken: cdk.SecretValue.secretsManager('my-github-token'), - autoBranchCreation: { + autoBranchCreation: { // Automatically connect branches that match a pattern set patterns: ['feature/*', 'test/*'] } + autoBranchDeletion: true, // Automatically disconnect a branch when you delete a branch from your repository }); ``` diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index 1d41c0e07ede3..034edafaaf3c9 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -91,6 +91,14 @@ export interface AppProps { */ readonly autoBranchCreation?: AutoBranchCreation; + /** + * Automatically disconnect a branch in the Amplify Console when you delete a + * branch from your Git repository. + * + * @default false + */ + readonly autoBranchDeletion?: boolean; + /** * The Basic Auth configuration. Use this to set password protection at an * app level to all your branches. @@ -210,11 +218,12 @@ export class App extends Resource implements IApp, iam.IGrantable { buildSpec: props.autoBranchCreation.buildSpec && props.autoBranchCreation.buildSpec.toBuildSpec(), enableAutoBranchCreation: true, enableAutoBuild: props.autoBranchCreation.autoBuild === undefined ? true : props.autoBranchCreation.autoBuild, - environmentVariables: Lazy.anyValue({ produce: () => renderEnvironmentVariables(this.autoBranchEnvironmentVariables )}, { omitEmptyArray: true }), // eslint-disable-line max-len + environmentVariables: Lazy.anyValue({ produce: () => renderEnvironmentVariables(this.autoBranchEnvironmentVariables ) }, { omitEmptyArray: true }), // eslint-disable-line max-len enablePullRequestPreview: props.autoBranchCreation.pullRequestPreview === undefined ? true : props.autoBranchCreation.pullRequestPreview, pullRequestEnvironmentName: props.autoBranchCreation.pullRequestEnvironmentName, stage: props.autoBranchCreation.stage, }, + enableBranchAutoDeletion: props.autoBranchDeletion, basicAuthConfig: props.basicAuth && props.basicAuth.bind(this, 'AppBasicAuth'), buildSpec: props.buildSpec && props.buildSpec.toBuildSpec(), customRules: Lazy.anyValue({ produce: () => this.customRules }, { omitEmptyArray: true }), diff --git a/packages/@aws-cdk/aws-amplify/test/app.test.ts b/packages/@aws-cdk/aws-amplify/test/app.test.ts index 5af765cae6d75..8cff8e3d5b66d 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/app.test.ts @@ -371,3 +371,20 @@ test('with auto branch creation', () => { }, }); }); + +test('with auto branch deletion', () => { + // WHEN + new amplify.App(stack, 'App', { + sourceCodeProvider: new amplify.GitHubSourceCodeProvider({ + owner: 'aws', + repository: 'aws-cdk', + oauthToken: SecretValue.plainText('secret'), + }), + autoBranchDeletion: true, + }); + + // THEN + expect(stack).toHaveResource('AWS::Amplify::App', { + EnableBranchAutoDeletion: true, + }); +}); From 44f7753cb33801f2c4c19ad5b72896331fbc1e71 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Mon, 17 Aug 2020 10:10:38 +0300 Subject: [PATCH 034/422] fix(eks): AMI changes in managed SSM store param causes rolling update of ASG (#9746) This might be the PR with the highest explanation/code ratio i've ever made :) When a value changes for an AMI in a managed SSM store parameter, it should not cause a replacement of the ASG nodes. The reasoning is that managed params can change over time with no control on the user's part. Because of this, the change will not be reflected in `cdk diff` and creates a situation where every deployment can potentially cause node replacement without notice. There are two scenarios in which the cluster interacts with an `AutoScalingGroup` ### `addCapacity` When one uses `cluster.addCapacity`, we implicitly create an `AutoScalingGroup` that uses either the `BottleRocketImage` or the `EksOptimizedImage` as the machine image, with no option to customize it. Both these images fetch their AMI's from a managed SSM parameter (`/aws/service/eks/optimized-ami` or `/aws/service/bottlerocket`). This means that we create the situation described above by **default**. https://github.com/aws/aws-cdk/blob/5af718bab8522f1a4e7f70e7221f4878a15aa4a4/packages/%40aws-cdk/aws-eks/lib/cluster.ts#L779-L785 Seems like a more reasonable default in this case would be to use `UpdateType.NONE` instead of `UpdateType.RollingUpdate`. Note that in such a case, even if the user explicitly changes the machine image configuration (by specifying a different `machineImageType`), node replacement will not occur, even though `cdk diff` will clearly show a configuration change. In any case, the `updateType` can always be explicitly passed to mitigate any issue caused by the default behavior. ### `addAutoScalingGroup` When one uses `cluster.addAutoScalingGroup`, the `AutoScalingGroup` is created by the user. The default value for `updateType` in the `AutoScalingGroup` construct is `UpdateType.NONE`, so unless the user explicitly configured `UpdateType.RollingUpdate` - node replacement should not occur. Having said that, when a user specifies `UpdateType.RollingUpdate`, its not super intuitive that this update might happen without any explicit configuration change, and in fact this is actually documented in the images that use SSM to fetch the API: https://github.com/aws/aws-cdk/blob/5af718bab8522f1a4e7f70e7221f4878a15aa4a4/packages/%40aws-cdk/aws-ec2/lib/machine-image.ts#L216-L226 ------------------------------------------- There is no way for us to selectively apply the update policy, we either dont use it at all, meaning intentional user changes won't replace nodes as well, or we use it for all, meaning implicit changes will cause it. Ideally, we should consider moving away from using these managed SSM params in launch configurations, but that requires some additional investigation. The PR simply suggests to remove the `UpdateType.RollingUpdate` default from the `addCapacity` method, as a form of balance between all the considerations mentioned above. Fixes https://github.com/aws/aws-cdk/issues/7273 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/lib/cluster.ts | 9 ++-- .../@aws-cdk/aws-eks/lib/legacy-cluster.ts | 4 -- .../test/integ.eks-cluster.expected.json | 44 ------------------- .../@aws-cdk/aws-eks/test/test.cluster.ts | 19 ++++++++ 4 files changed, 25 insertions(+), 51 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 9bbc71ca73f5a..c7cd8d9719c67 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -763,9 +763,12 @@ export class Cluster extends Resource implements ICluster { * The nodes will automatically be configured with the right VPC and AMI * for the instance type and Kubernetes version. * + * Note that if you specify `updateType: RollingUpdate` or `updateType: ReplacingUpdate`, your nodes might be replaced at deploy + * time without notice in case the recommended AMI for your machine image type has been updated by AWS. + * The default behavior for `updateType` is `None`, which means only new instances will be launched using the new AMI. + * * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. - * If kubectl is enabled, the - * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) + * In addition, the [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) * daemon will be installed on all spot instances to handle * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). */ @@ -782,7 +785,7 @@ export class Cluster extends Resource implements ICluster { nodeType: nodeTypeForInstanceType(options.instanceType), kubernetesVersion: this.version.version, }), - updateType: options.updateType || autoscaling.UpdateType.ROLLING_UPDATE, + updateType: options.updateType, instanceType: options.instanceType, }); diff --git a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts index 3e2ba0feb9abd..f8766e8ce3929 100644 --- a/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts @@ -230,10 +230,6 @@ export class LegacyCluster extends Resource implements ICluster { * for the instance type and Kubernetes version. * * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. - * If kubectl is enabled, the - * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) - * daemon will be installed on all spot instances to handle - * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). */ public addCapacity(id: string, options: CapacityOptions): autoscaling.AutoScalingGroup { if (options.machineImageType === MachineImageType.BOTTLEROCKET && options.bootstrapOptions !== undefined ) { 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 471201d249202..62e1e78b850c1 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 @@ -1551,17 +1551,6 @@ ] }, "UpdatePolicy": { - "AutoScalingRollingUpdate": { - "WaitOnResourceSignals": false, - "PauseTime": "PT0S", - "SuspendProcesses": [ - "HealthCheck", - "ReplaceUnhealthy", - "AZRebalance", - "AlarmNotification", - "ScheduledActions" - ] - }, "AutoScalingScheduledAction": { "IgnoreUnmodifiedGroupSizeProperties": true } @@ -1853,17 +1842,6 @@ ] }, "UpdatePolicy": { - "AutoScalingRollingUpdate": { - "WaitOnResourceSignals": false, - "PauseTime": "PT0S", - "SuspendProcesses": [ - "HealthCheck", - "ReplaceUnhealthy", - "AZRebalance", - "AlarmNotification", - "ScheduledActions" - ] - }, "AutoScalingScheduledAction": { "IgnoreUnmodifiedGroupSizeProperties": true } @@ -2142,17 +2120,6 @@ ] }, "UpdatePolicy": { - "AutoScalingRollingUpdate": { - "WaitOnResourceSignals": false, - "PauseTime": "PT0S", - "SuspendProcesses": [ - "HealthCheck", - "ReplaceUnhealthy", - "AZRebalance", - "AlarmNotification", - "ScheduledActions" - ] - }, "AutoScalingScheduledAction": { "IgnoreUnmodifiedGroupSizeProperties": true } @@ -2462,17 +2429,6 @@ ] }, "UpdatePolicy": { - "AutoScalingRollingUpdate": { - "WaitOnResourceSignals": false, - "PauseTime": "PT0S", - "SuspendProcesses": [ - "HealthCheck", - "ReplaceUnhealthy", - "AZRebalance", - "AlarmNotification", - "ScheduledActions" - ] - }, "AutoScalingScheduledAction": { "IgnoreUnmodifiedGroupSizeProperties": true } diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 6c1d01a1f02ba..0ae01003d7e14 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -15,6 +15,7 @@ import { testFixture, testFixtureNoVpc } from './util'; const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; export = { + 'a default cluster spans all subnets'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); @@ -201,6 +202,24 @@ export = { test.done(); }, + 'adding capacity creates an ASG without a rolling update policy'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); + + // WHEN + cluster.addCapacity('Default', { + instanceType: new ec2.InstanceType('t2.medium'), + }); + + test.deepEqual(expect(stack).value.Resources.ClusterASG0E4BA723.UpdatePolicy, { AutoScalingScheduledAction: { IgnoreUnmodifiedGroupSizeProperties: true } }); + test.done(); + }, + 'adding capacity creates an ASG with tags'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); From 90de605a0f9c5487c302e34db5f89dcd58e3b445 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 17 Aug 2020 09:35:09 +0200 Subject: [PATCH 035/422] chore: more style rules for eslint (#9517) Configure `comma-spacing`, `no-multi-spaces`, `array-bracket-spacing`, `array-bracket-newline`, `object-curly-spacing`, `object-curly-newline` and `object-property-newline` to uniformize arrays and objects. * Valid arrays (no bracket spacing, no space before comma, a single space after comma): ```ts [1, 2, 3] [ 1, 2, 3, ] [ 1, 2, 3, 4, ] ``` * Valid objects (curly spacing): ```ts { key: 'value' } { key1: 'value1', key2: 'value2 } { key1: 'value1', key2: 'value2', } ``` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/test.pipeline-deploy-stack-action.ts | 2 +- .../@aws-cdk/assert/test/assertions.test.ts | 6 +- .../@aws-cdk/assert/test/have-output.test.ts | 2 +- packages/@aws-cdk/assets/test/test.staging.ts | 4 +- .../@aws-cdk/aws-apigateway/lib/access-log.ts | 6 +- .../aws-apigateway/lib/apigatewayv2.ts | 44 +-- .../aws-apigateway/lib/authorizers/lambda.ts | 4 +- packages/@aws-cdk/aws-apigateway/lib/cors.ts | 4 +- .../@aws-cdk/aws-apigateway/lib/deployment.ts | 6 +- .../aws-apigateway/lib/integrations/aws.ts | 22 +- .../@aws-cdk/aws-apigateway/lib/resource.ts | 2 +- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 2 +- packages/@aws-cdk/aws-apigateway/lib/stage.ts | 5 +- packages/@aws-cdk/aws-apigateway/lib/util.ts | 4 +- .../@aws-cdk/aws-apigateway/lib/vpc-link.ts | 2 +- .../authorizers/integ.request-authorizer.ts | 6 +- .../integ.token-authorizer-iam-role.ts | 2 +- .../authorizers/integ.token-authorizer.ts | 2 +- .../test/authorizers/test.lambda.ts | 2 +- .../test/integ.api-definition.asset.ts | 4 +- .../aws-apigateway/test/integ.cors.ts | 8 +- .../test/integ.restapi-import.lit.ts | 6 +- .../test/integ.restapi.vpc-endpoint.ts | 4 +- .../aws-apigateway/test/test.access-log.ts | 2 +- .../test/test.api-definition.ts | 2 +- .../@aws-cdk/aws-apigateway/test/test.cors.ts | 10 +- .../aws-apigateway/test/test.deployment.ts | 2 +- .../aws-apigateway/test/test.domains.ts | 22 +- .../aws-apigateway/test/test.lambda.ts | 2 +- .../aws-apigateway/test/test.method.ts | 20 +- .../aws-apigateway/test/test.restapi.ts | 90 +++--- .../aws-apigateway/test/test.stage.ts | 8 +- .../@aws-cdk/aws-apigateway/test/test.util.ts | 10 +- .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 2 +- .../aws-apigatewayv2/test/http/api.test.ts | 2 +- .../test/http/domain-name.test.ts | 2 +- .../aws-apigatewayv2/test/http/route.test.ts | 8 +- .../lib/scalable-target.ts | 2 +- .../test/test.step-scaling-policy.ts | 2 +- .../aws-appmesh/lib/virtual-router.ts | 2 +- .../aws-appmesh/lib/virtual-service.ts | 2 +- .../aws-appmesh/test/test.health-check.ts | 12 +- .../@aws-cdk/aws-appmesh/test/test.mesh.ts | 2 +- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 4 +- packages/@aws-cdk/aws-appsync/lib/key.ts | 2 +- .../aws-appsync/test/appsync-dynamodb.test.ts | 6 +- .../aws-appsync/test/appsync-grant.test.ts | 117 ++++---- .../aws-appsync/test/appsync-http.test.ts | 6 +- .../aws-appsync/test/appsync-lambda.test.ts | 8 +- .../aws-appsync/test/appsync-none.test.ts | 6 +- .../@aws-cdk/aws-appsync/test/appsync.test.ts | 2 +- .../aws-appsync/test/integ.graphql-iam.ts | 8 +- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- .../aws-appsync/test/integ.graphql.ts | 24 +- .../@aws-cdk/aws-athena/test/athena.test.ts | 3 +- .../aws-athena/test/integ.workgroup.ts | 3 +- .../lib/interval-utils.ts | 2 +- .../aws-autoscaling-common/lib/test-utils.ts | 2 +- .../test/test.intervals.ts | 2 +- .../test/hooks.test.ts | 5 +- .../aws-autoscaling/lib/auto-scaling-group.ts | 6 +- .../test/auto-scaling-group.test.ts | 22 +- .../aws-autoscaling/test/scaling.test.ts | 27 +- .../test/scheduled-action.test.ts | 2 +- packages/@aws-cdk/aws-backup/lib/selection.ts | 2 +- .../aws-batch/lib/compute-environment.ts | 4 +- .../@aws-cdk/aws-batch/lib/job-definition.ts | 6 +- .../test/compute-environment.test.ts | 8 +- .../aws-batch/test/job-definition.test.ts | 4 +- .../lib/dns-validated-certificate.ts | 2 +- .../aws-certificatemanager/test/util.test.ts | 6 +- .../test/integ.nested-stack.ts | 2 +- .../test/integ.nested-stacks-assets.ts | 2 +- .../test/integ.trivial-lambda-resource.ts | 2 +- .../aws-cloudformation/test/test.deps.ts | 36 +-- .../test/test.nested-stack.ts | 2 +- .../aws-cloudformation/test/test.resource.ts | 9 +- .../test/s3-origin.test.ts | 2 +- .../aws-cloudfront/lib/distribution.ts | 38 +-- .../@aws-cdk/aws-cloudfront/lib/origin.ts | 22 +- .../lib/origin_access_identity.ts | 2 +- .../aws-cloudfront/lib/web_distribution.ts | 15 +- .../aws-cloudfront/test/distribution.test.ts | 8 +- .../test/integ.cloudfront-custom-s3.ts | 2 +- .../test/integ.cloudfront-geo-restrictions.ts | 2 +- .../test/integ.cloudfront-ipv6-disabled.ts | 2 +- .../integ.cloudfront-lambda-association.ts | 11 +- .../aws-cloudfront/test/integ.cloudfront.ts | 2 +- .../test/web_distribution.test.ts | 28 +- .../@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts | 4 +- .../aws-cloudtrail/test/cloudtrail.test.ts | 12 +- .../integ.cloudtrail-supplied-bucket.lit.ts | 8 +- .../test/integ.cloudtrail.lit.ts | 2 +- .../@aws-cdk/aws-cloudwatch/lib/dashboard.ts | 24 +- .../@aws-cdk/aws-cloudwatch/lib/layout.ts | 2 +- .../aws-cloudwatch/lib/private/rendering.ts | 2 +- .../test/test.cross-environment.ts | 27 +- .../aws-cloudwatch/test/test.dashboard.ts | 24 +- .../aws-cloudwatch/test/test.graphs.ts | 37 +-- .../aws-cloudwatch/test/test.layout.ts | 2 +- .../aws-cloudwatch/test/test.metric-math.ts | 97 ++++--- .../@aws-cdk/aws-codebuild/lib/build-spec.ts | 5 +- .../test/integ.docker-asset.lit.ts | 4 +- .../test/integ.docker-registry.lit.ts | 2 +- .../aws-codebuild/test/integ.ecr.lit.ts | 2 +- .../aws-codebuild/test/test.codebuild.ts | 10 +- .../@aws-cdk/aws-codecommit/lib/repository.ts | 22 +- .../lib/lambda/deployment-config.ts | 16 +- .../lib/server/deployment-config.ts | 4 +- .../test/lambda/integ.deployment-group.ts | 2 +- .../aws-codepipeline-actions/lib/action.ts | 30 +- .../lib/s3/deploy-action.ts | 2 +- .../lib/s3/source-action.ts | 2 +- .../test.cloudformation-pipeline-actions.ts | 60 ++-- .../cloudformation/test.pipeline-actions.ts | 19 +- .../test/integ.lambda-pipeline.ts | 4 +- .../test/integ.pipeline-events.ts | 2 +- .../test/integ.pipeline-stepfunctions.ts | 2 +- .../test/s3/test.s3-deploy-action.ts | 2 +- .../test.stepfunctions-invoke-actions.ts | 4 +- .../test/test.manual-approval.ts | 2 +- .../test/test.pipeline.ts | 2 +- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 8 +- .../@aws-cdk/aws-codepipeline/lib/stage.ts | 12 +- .../aws-codepipeline/test/test.pipeline.ts | 4 +- .../aws-cognito/lib/user-pool-client.ts | 8 +- .../aws-cognito/lib/user-pool-domain.ts | 2 +- .../aws-cognito/lib/user-pool-idps/amazon.ts | 2 +- .../lib/user-pool-idps/facebook.ts | 2 +- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 6 +- .../integ.user-pool-client-explicit-props.ts | 2 +- .../test/integ.user-pool-domain-signinurl.ts | 2 +- .../aws-cognito/test/user-pool-client.test.ts | 52 ++-- .../aws-cognito/test/user-pool-domain.test.ts | 4 +- .../test/user-pool-idps/amazon.test.ts | 2 +- .../test/user-pool-idps/facebook.test.ts | 2 +- .../aws-cognito/test/user-pool.test.ts | 34 +-- packages/@aws-cdk/aws-config/lib/rule.ts | 4 +- .../@aws-cdk/aws-docdb/test/cluster.test.ts | 16 +- .../@aws-cdk/aws-docdb/test/instance.test.ts | 4 +- .../lib/global-table-coordinator.ts | 2 +- .../test/test.dynamodb.global.ts | 6 +- packages/@aws-cdk/aws-dynamodb/lib/table.ts | 17 +- .../aws-dynamodb/test/dynamodb.test.ts | 3 +- packages/@aws-cdk/aws-ec2/lib/cfn-init.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/instance.ts | 20 +- .../@aws-cdk/aws-ec2/lib/machine-image.ts | 6 +- packages/@aws-cdk/aws-ec2/lib/nat.ts | 6 +- packages/@aws-cdk/aws-ec2/lib/network-acl.ts | 4 +- packages/@aws-cdk/aws-ec2/lib/network-util.ts | 2 +- .../@aws-cdk/aws-ec2/lib/security-group.ts | 6 +- packages/@aws-cdk/aws-ec2/lib/user-data.ts | 5 +- packages/@aws-cdk/aws-ec2/lib/util.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/volume.ts | 18 +- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 28 +- packages/@aws-cdk/aws-ec2/lib/vpn.ts | 2 +- .../aws-ec2/test/bastion-host.test.ts | 4 +- .../@aws-cdk/aws-ec2/test/connections.test.ts | 40 +-- .../@aws-cdk/aws-ec2/test/instance.test.ts | 10 +- .../aws-ec2/test/integ.instance-init.ts | 2 +- .../aws-ec2/test/machine-image.test.ts | 8 +- .../aws-ec2/test/security-group.test.ts | 4 +- .../aws-ec2/test/vpc-endpoint.test.ts | 14 +- .../aws-ec2/test/vpc.from-lookup.test.ts | 2 +- packages/@aws-cdk/aws-ec2/test/vpc.test.ts | 42 +-- packages/@aws-cdk/aws-ec2/test/vpn.test.ts | 2 +- .../aws-ecr-assets/lib/image-asset.ts | 12 +- .../test/integ.assets-docker.ts | 2 +- .../test/integ.nested-stacks-docker.ts | 2 +- .../aws-ecr-assets/test/test.image-asset.ts | 4 +- packages/@aws-cdk/aws-ecr/lib/repository.ts | 4 +- .../@aws-cdk/aws-ecr/test/test.repository.ts | 7 +- .../application-load-balanced-service-base.ts | 6 +- ...ion-multiple-target-groups-service-base.ts | 6 +- ...ork-multiple-target-groups-service-base.ts | 6 +- ...tion-multiple-target-groups-ecs-service.ts | 6 +- ...work-multiple-target-groups-ecs-service.ts | 6 +- ...-multiple-target-groups-fargate-service.ts | 6 +- ...-multiple-target-groups-fargate-service.ts | 6 +- .../aws-ecs-patterns/test/ec2/test.l3s.ts | 26 +- .../test/fargate/integ.asset-image.ts | 2 +- .../integ.scheduled-fargate-task.lit.ts | 2 +- .../test.load-balanced-fargate-service.ts | 6 +- .../test.queue-processing-fargate-service.ts | 4 +- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 6 +- .../aws-ecs/lib/base/task-definition.ts | 5 +- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 6 +- .../lib/drain-hook/instance-drain-hook.ts | 6 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 2 +- .../aws-ecs/lib/fargate/fargate-service.ts | 2 +- .../@aws-cdk/aws-ecs/lib/linux-parameters.ts | 4 +- .../lib/log-drivers/firelens-log-driver.ts | 2 +- .../test/ec2/integ.firelens-s3-config.ts | 2 +- .../aws-ecs/test/ec2/test.ec2-service.ts | 4 +- .../test/ec2/test.ec2-task-definition.ts | 2 +- .../test/fargate/integ.nlb-awsvpc-nw.ts | 2 +- .../test/fargate/test.fargate-service.ts | 6 +- .../fargate/test.fargate-task-definition.ts | 4 +- .../aws-ecs/test/test.container-definition.ts | 4 +- .../aws-ecs/test/test.firelens-log-driver.ts | 4 +- .../aws-efs/test/efs-file-system.test.ts | 8 +- packages/@aws-cdk/aws-efs/test/integ.efs.ts | 2 +- .../@aws-cdk/aws-eks-legacy/lib/aws-auth.ts | 2 +- .../aws-eks-legacy/lib/cluster-resource.ts | 12 +- .../@aws-cdk/aws-eks-legacy/lib/cluster.ts | 6 +- .../@aws-cdk/aws-eks-legacy/lib/helm-chart.ts | 4 +- .../aws-eks-legacy/lib/kubectl-layer.ts | 4 +- .../integ.eks-cluster.kubectl-disabled.ts | 2 +- .../test/integ.eks-cluster.lit.ts | 2 +- .../test/integ.eks-kubectl.lit.ts | 4 +- .../aws-eks-legacy/test/test.cluster.ts | 22 +- .../aws-eks-legacy/test/test.user-data.ts | 2 +- packages/@aws-cdk/aws-eks-legacy/test/util.ts | 2 +- packages/@aws-cdk/aws-eks/lib/aws-auth.ts | 2 +- .../lib/cluster-resource-handler/cluster.ts | 12 +- .../lib/cluster-resource-handler/fargate.ts | 6 +- .../aws-eks/lib/cluster-resource-provider.ts | 2 +- .../@aws-cdk/aws-eks/lib/cluster-resource.ts | 26 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 18 +- .../@aws-cdk/aws-eks/lib/fargate-cluster.ts | 4 +- .../@aws-cdk/aws-eks/lib/fargate-profile.ts | 2 +- .../@aws-cdk/aws-eks/lib/kubectl-layer.ts | 4 +- .../@aws-cdk/aws-eks/lib/kubectl-provider.ts | 6 +- .../@aws-cdk/aws-eks/lib/legacy-cluster.ts | 4 +- .../@aws-cdk/aws-eks/lib/service-account.ts | 4 +- .../test/cluster-resource-handler-mocks.ts | 4 +- packages/@aws-cdk/aws-eks/test/hello-k8s.ts | 4 +- .../integ.eks-cluster.kubectl-disabled.ts | 2 +- .../test/test.cluster-resource-provider.ts | 12 +- .../@aws-cdk/aws-eks/test/test.cluster.ts | 45 +-- .../@aws-cdk/aws-eks/test/test.fargate.ts | 68 ++--- .../@aws-cdk/aws-eks/test/test.helm-chart.ts | 2 +- .../aws-eks/test/test.k8s-object-value.ts | 12 +- .../@aws-cdk/aws-eks/test/test.k8s-patch.ts | 10 +- .../@aws-cdk/aws-eks/test/test.nodegroup.ts | 2 +- .../@aws-cdk/aws-eks/test/test.user-data.ts | 2 +- packages/@aws-cdk/aws-eks/test/util.ts | 2 +- .../lib/load-balancer.ts | 8 +- .../test/test.loadbalancer.ts | 2 +- .../test/cognito.test.ts | 2 +- .../test/instance-target.test.ts | 2 +- .../test/lambda-target.test.ts | 2 +- .../lib/alb/application-listener-rule.ts | 2 +- .../lib/alb/application-target-group.ts | 2 +- .../lib/nlb/network-target-group.ts | 10 +- .../lib/shared/base-load-balancer.ts | 4 +- .../lib/shared/base-target-group.ts | 18 +- .../test/alb/test.listener.ts | 21 +- .../test/alb/test.load-balancer.ts | 4 +- .../test/alb/test.security-groups.ts | 6 +- .../test/alb/test.target-group.ts | 2 +- .../test/integ.alb.dualstack.ts | 2 +- .../test/integ.alb2.ts | 2 +- .../test/nlb/test.listener.ts | 4 +- .../test/nlb/test.load-balancer.ts | 25 +- .../aws-events-targets/lib/aws-api.ts | 2 +- .../test/aws-api/aws-api-handler.test.ts | 4 +- .../integ.pipeline-event-target.ts | 8 +- .../test/codepipeline/pipeline.test.ts | 16 +- .../test/ecs/event-rule-target.test.ts | 6 +- .../test/ecs/integ.event-ec2-task.lit.ts | 2 +- .../test/ecs/integ.event-fargate-task.ts | 2 +- .../test/kinesis/kinesis-stream.test.ts | 8 +- .../aws-events-targets/test/sns/sns.test.ts | 2 +- packages/@aws-cdk/aws-events/lib/input.ts | 6 +- .../@aws-cdk/aws-events/test/test.rule.ts | 60 ++-- .../@aws-cdk/aws-events/test/test.util.ts | 30 +- packages/@aws-cdk/aws-fsx/lib/file-system.ts | 6 +- .../aws-fsx/lib/lustre-file-system.ts | 12 +- .../aws-fsx/test/integ.lustre-file-system.ts | 6 +- .../aws-fsx/test/lustre-file-system.test.ts | 18 +- .../aws-fsx/test/maintenance-time.test.ts | 10 +- .../lib/accelerator-security-group.ts | 2 +- .../lib/endpoint-group.ts | 2 +- .../test/integ.globalaccelerator.ts | 4 +- packages/@aws-cdk/aws-glue/lib/table.ts | 2 +- .../@aws-cdk/aws-glue/test/database.test.ts | 6 +- .../@aws-cdk/aws-iam/lib/managed-policy.ts | 2 +- .../aws-iam/lib/oidc-provider/index.ts | 2 +- .../@aws-cdk/aws-iam/lib/policy-statement.ts | 10 +- .../aws-iam/lib/private/immutable-role.ts | 2 +- packages/@aws-cdk/aws-iam/lib/util.ts | 12 +- .../test/auto-cross-stack-refs.test.ts | 2 +- .../@aws-cdk/aws-iam/test/example.role.lit.ts | 3 +- packages/@aws-cdk/aws-iam/test/group.test.ts | 22 +- .../aws-iam/test/integ.oidc-provider.ts | 2 +- .../aws-iam/test/managed-policy.test.ts | 62 +++-- .../aws-iam/test/oidc-provider.test.ts | 64 ++--- .../aws-iam/test/policy-document.test.ts | 62 +++-- .../aws-iam/test/policy-statement.test.ts | 4 +- packages/@aws-cdk/aws-iam/test/policy.test.ts | 256 ++++++++++++------ .../@aws-cdk/aws-iam/test/principals.test.ts | 2 +- .../aws-iam/test/role.from-role-arn.test.ts | 9 +- packages/@aws-cdk/aws-iam/test/role.test.ts | 120 +++++--- packages/@aws-cdk/aws-kinesis/lib/stream.ts | 4 +- packages/@aws-cdk/aws-kms/lib/alias.ts | 2 +- packages/@aws-cdk/aws-kms/test/test.alias.ts | 6 +- .../aws-lambda-event-sources/lib/dynamodb.ts | 4 +- .../aws-lambda-event-sources/lib/kinesis.ts | 4 +- .../aws-lambda-event-sources/test/integ.s3.ts | 4 +- .../aws-lambda-event-sources/test/test.api.ts | 8 +- .../test/test.dynamo.ts | 2 +- .../aws-lambda-event-sources/test/test.s3.ts | 2 +- .../aws-lambda-nodejs/test/docker.test.ts | 6 +- .../test/integ.dependencies.ts | 2 +- .../aws-lambda-nodejs/test/integ.function.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/alias.ts | 2 +- .../aws-lambda/lib/event-source-mapping.ts | 2 +- .../@aws-cdk/aws-lambda/lib/filesystem.ts | 10 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 12 +- .../@aws-cdk/aws-lambda/lib/lambda-version.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 34 +-- .../aws-lambda/test/integ.autoscaling.lit.ts | 4 +- .../@aws-cdk/aws-lambda/test/test.alias.ts | 26 +- .../@aws-cdk/aws-lambda/test/test.code.ts | 3 +- .../@aws-cdk/aws-lambda/test/test.function.ts | 5 +- .../@aws-cdk/aws-lambda/test/test.layers.ts | 2 +- .../@aws-cdk/aws-lambda/test/test.runtime.ts | 20 +- .../aws-lambda/test/test.singleton-lambda.ts | 12 +- .../aws-lambda/test/test.vpc-lambda.ts | 44 +-- .../test/kinesis.test.ts | 26 +- .../aws-logs-destinations/test/lambda.test.ts | 12 +- .../aws-logs/lib/cross-account-destination.ts | 5 +- packages/@aws-cdk/aws-logs/lib/log-group.ts | 2 +- .../aws-logs/test/test.destination.ts | 2 +- .../@aws-cdk/aws-logs/test/test.loggroup.ts | 6 +- .../aws-logs/test/test.metricfilter.ts | 2 +- packages/@aws-cdk/aws-rds/lib/proxy.ts | 6 +- .../@aws-cdk/aws-rds/test/test.cluster.ts | 8 +- .../@aws-cdk/aws-rds/test/test.instance.ts | 8 +- .../aws-redshift/lib/parameter-group.ts | 2 +- .../lib/bucket-website-target.ts | 10 +- .../test/bucket-website-target.test.ts | 6 +- .../test/classic-load-balancer-target.test.ts | 4 +- .../test/cloudfront-target.test.ts | 2 +- .../test/integ.cloudfront-alias-target.ts | 2 +- .../test/load-balancer-target.test.ts | 4 +- packages/@aws-cdk/aws-route53/lib/util.ts | 2 +- .../@aws-cdk/aws-route53/test/test.route53.ts | 4 +- packages/@aws-cdk/aws-s3-assets/lib/asset.ts | 2 +- .../@aws-cdk/aws-s3-assets/test/asset.test.ts | 8 +- .../lib/bucket-deployment.ts | 2 +- .../integ.bucket-deployment-cloudfront.ts | 2 +- .../aws-s3-notifications/lib/lambda.ts | 2 +- .../@aws-cdk/aws-s3-notifications/lib/sns.ts | 2 +- .../@aws-cdk/aws-s3-notifications/lib/sqs.ts | 2 +- .../test/lambda/lambda.test.ts | 26 +- .../test/notifications.test.ts | 13 +- .../aws-s3-notifications/test/queue.test.ts | 8 +- .../aws-s3-notifications/test/sns.test.ts | 2 +- packages/@aws-cdk/aws-s3/lib/bucket.ts | 8 +- .../notifications-resource.ts | 2 +- packages/@aws-cdk/aws-s3/test/test.bucket.ts | 19 +- packages/@aws-cdk/aws-s3/test/test.metrics.ts | 4 +- .../@aws-cdk/aws-s3/test/test.notification.ts | 9 +- .../aws-secretsmanager/lib/secret-rotation.ts | 4 +- .../aws-servicediscovery/lib/service.ts | 2 +- packages/@aws-cdk/aws-ses-actions/lib/s3.ts | 2 +- .../aws-sns-subscriptions/test/subs.test.ts | 12 +- .../aws-sns/lib/subscription-filter.ts | 4 +- packages/@aws-cdk/aws-sns/lib/topic-base.ts | 2 +- packages/@aws-cdk/aws-sns/test/test.sns.ts | 4 +- .../aws-sns/test/test.subscription.ts | 6 +- packages/@aws-cdk/aws-sqs/lib/queue-base.ts | 2 +- packages/@aws-cdk/aws-sqs/test/test.sqs.ts | 16 +- .../aws-ssm/test/integ.parameter-arns.ts | 6 +- .../@aws-cdk/aws-ssm/test/test.parameter.ts | 46 ++-- .../lib/ecs/run-ecs-ec2-task.ts | 2 +- .../lib/ecs/run-task.ts | 2 +- .../lib/emr/emr-add-step.ts | 2 +- .../lib/emr/emr-terminate-cluster.ts | 2 +- .../lib/evaluate-expression.ts | 2 +- .../test/batch/integ.run-batch-job.ts | 2 +- .../test/batch/integ.submit-job.ts | 2 +- .../test/batch/run-batch-job.test.ts | 2 +- .../test/batch/submit-job.test.ts | 2 +- .../test/ecs/ecs-tasks.test.ts | 56 ++-- .../test/ecs/integ.ec2-run-task.ts | 2 +- .../test/ecs/integ.ec2-task.ts | 2 +- .../test/ecs/integ.fargate-run-task.ts | 2 +- .../test/ecs/integ.fargate-task.ts | 2 +- .../test/emr/emr-create-cluster.test.ts | 7 +- .../test/glue/integ.glue-task.ts | 2 +- .../test/glue/integ.start-job-run.ts | 2 +- .../test/lambda/integ.invoke-function.ts | 2 +- .../test/private/task-utils.test.ts | 6 +- .../sagemaker/create-training-job.test.ts | 20 +- .../sagemaker/create-transform-job.test.ts | 4 +- .../sagemaker/integ.create-training-job.ts | 11 +- .../test/sns/integ.publish.ts | 2 +- .../test/sns/publish-to-topic.test.ts | 50 ++-- .../test/sns/publish.test.ts | 2 +- .../test/sqs/send-to-queue.test.ts | 44 +-- .../test/stepfunctions/invoke-activity.ts | 2 +- .../aws-stepfunctions/lib/states/pass.ts | 6 +- .../aws-stepfunctions/lib/states/task-base.ts | 2 +- .../aws-stepfunctions/test/activity.test.ts | 2 +- .../aws-stepfunctions/test/parallel.test.ts | 2 +- .../aws-stepfunctions/test/pass.test.ts | 2 +- .../test/state-machine-resources.test.ts | 38 ++- .../test/states-language.test.ts | 8 +- .../@aws-cdk/aws-synthetics/lib/canary.ts | 6 +- packages/@aws-cdk/aws-synthetics/lib/code.ts | 2 +- .../aws-synthetics/test/canary.test.ts | 10 +- .../aws-synthetics/test/metric.test.ts | 2 +- .../lib/cloud-assembly/artifact-schema.ts | 2 +- .../scripts/update-schema.ts | 6 +- .../cloudformation-diff/lib/diff/index.ts | 2 +- .../cloudformation-diff/lib/diff/types.ts | 3 +- .../cloudformation-diff/lib/format-table.ts | 2 +- .../cloudformation-diff/lib/format.ts | 8 +- .../test/diff-template.test.ts | 8 +- .../test/iam/detect-changes.test.ts | 76 +++--- .../test/iam/statement.test.ts | 8 +- .../cloudformation-include/lib/cfn-include.ts | 2 +- .../test/nested-stacks.test.ts | 108 +++++--- .../test/valid-templates.test.ts | 7 +- packages/@aws-cdk/core/lib/app.ts | 4 +- packages/@aws-cdk/core/lib/arn.ts | 4 +- packages/@aws-cdk/core/lib/aspect.ts | 2 +- packages/@aws-cdk/core/lib/asset-staging.ts | 2 +- packages/@aws-cdk/core/lib/cfn-fn.ts | 28 +- .../custom-resource-provider.ts | 2 +- .../core/lib/private/asset-parameters.ts | 2 +- .../core/lib/private/cfn-reference.ts | 2 +- .../core/lib/private/cloudformation-lang.ts | 17 +- .../@aws-cdk/core/lib/private/encoding.ts | 2 +- .../@aws-cdk/core/lib/private/node-version.ts | 2 +- packages/@aws-cdk/core/lib/private/refs.ts | 2 +- packages/@aws-cdk/core/lib/private/resolve.ts | 2 +- .../@aws-cdk/core/lib/private/token-map.ts | 6 +- packages/@aws-cdk/core/lib/resolvable.ts | 2 +- packages/@aws-cdk/core/lib/secret-value.ts | 6 +- packages/@aws-cdk/core/lib/stack-trace.ts | 2 +- packages/@aws-cdk/core/lib/stack.ts | 13 +- packages/@aws-cdk/core/lib/stage.ts | 2 +- .../@aws-cdk/core/lib/string-fragments.ts | 2 +- packages/@aws-cdk/core/lib/token.ts | 10 +- packages/@aws-cdk/core/lib/util.ts | 4 +- .../core/test/fs/test.fs-fingerprint.ts | 6 +- .../test.new-style-synthesis.ts | 2 +- packages/@aws-cdk/core/test/test.app.ts | 20 +- packages/@aws-cdk/core/test/test.arn.ts | 26 +- packages/@aws-cdk/core/test/test.cfn-json.ts | 6 +- .../core/test/test.cloudformation-json.ts | 24 +- packages/@aws-cdk/core/test/test.condition.ts | 23 +- packages/@aws-cdk/core/test/test.construct.ts | 18 +- packages/@aws-cdk/core/test/test.context.ts | 6 +- .../@aws-cdk/core/test/test.environment.ts | 12 +- packages/@aws-cdk/core/test/test.fn.ts | 29 +- packages/@aws-cdk/core/test/test.include.ts | 14 +- .../@aws-cdk/core/test/test.logical-id.ts | 8 +- packages/@aws-cdk/core/test/test.mappings.ts | 62 +++-- packages/@aws-cdk/core/test/test.output.ts | 14 +- packages/@aws-cdk/core/test/test.parameter.ts | 9 +- packages/@aws-cdk/core/test/test.resource.ts | 202 +++++++++----- packages/@aws-cdk/core/test/test.rule.ts | 6 +- .../@aws-cdk/core/test/test.runtime-info.ts | 4 +- packages/@aws-cdk/core/test/test.size.ts | 6 +- packages/@aws-cdk/core/test/test.stack.ts | 103 ++++--- packages/@aws-cdk/core/test/test.stage.ts | 4 +- packages/@aws-cdk/core/test/test.staging.ts | 14 +- packages/@aws-cdk/core/test/test.synthesis.ts | 6 +- .../@aws-cdk/core/test/test.tag-aspect.ts | 60 ++-- .../@aws-cdk/core/test/test.tag-manager.ts | 30 +- packages/@aws-cdk/core/test/test.tokens.ts | 32 +-- packages/@aws-cdk/core/test/test.util.ts | 26 +- .../provider-framework/runtime/framework.ts | 4 +- .../waiter-state-machine.ts | 16 +- .../aws-custom-resource.test.ts | 34 +-- .../integ.aws-custom-resource.ts | 6 +- .../integration-test-fixtures/s3-assert.ts | 2 +- .../integration-test-fixtures/s3-file.ts | 5 +- .../test/provider-framework/provider.test.ts | 6 +- .../waiter-state-machine.test.ts | 2 +- .../@aws-cdk/cx-api/lib/cloud-artifact.ts | 2 +- .../@aws-cdk/cx-api/lib/cloud-assembly.ts | 2 +- packages/@aws-cdk/cx-api/lib/environment.ts | 2 +- packages/@aws-cdk/cx-api/lib/placeholders.ts | 2 +- .../test/cloud-assembly-builder.test.ts | 4 +- .../cx-api/test/cloud-assembly.test.ts | 6 +- .../pipelines/lib/private/asset-manifest.ts | 2 +- .../@aws-cdk/pipelines/test/builds.test.ts | 2 +- .../test/cross-environment-infra.test.ts | 4 +- .../pipelines/test/pipeline-assets.test.ts | 28 +- .../@aws-cdk/pipelines/test/pipeline.test.ts | 6 +- .../pipelines/test/validation.test.ts | 6 +- packages/aws-cdk/bin/cdk.ts | 10 +- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 2 +- .../lib/api/bootstrap/legacy-template.ts | 6 +- packages/aws-cdk/lib/api/cxapp/exec.ts | 2 +- packages/aws-cdk/lib/api/deploy-stack.ts | 4 +- packages/aws-cdk/lib/api/toolkit-info.ts | 7 +- .../cloudformation/stack-activity-monitor.ts | 2 +- packages/aws-cdk/lib/cdk-toolkit.ts | 4 +- packages/aws-cdk/lib/commands/docs.ts | 2 +- packages/aws-cdk/lib/commands/doctor.ts | 2 +- .../endpoint-service-availability-zones.ts | 2 +- .../aws-cdk/lib/context-providers/index.ts | 2 +- .../app/csharp/add-project.hook.ts | 4 +- .../app/fsharp/add-project.hook.ts | 4 +- .../sample-app/csharp/add-project.hook.ts | 4 +- .../sample-app/fsharp/add-project.hook.ts | 4 +- packages/aws-cdk/lib/init.ts | 2 +- packages/aws-cdk/lib/os.ts | 2 +- packages/aws-cdk/lib/settings.ts | 2 +- packages/aws-cdk/test/api/bootstrap.test.ts | 132 +++++---- .../aws-cdk/test/api/deploy-stack.test.ts | 36 +-- packages/aws-cdk/test/api/exec.test.ts | 2 +- .../aws-cdk/test/api/sdk-provider.test.ts | 11 +- packages/aws-cdk/test/cdk-toolkit.test.ts | 2 +- .../context-providers/asymmetric-vpcs.test.ts | 46 +++- .../test/context-providers/vpcs.test.ts | 8 +- packages/aws-cdk/test/context.test.ts | 2 +- packages/aws-cdk/test/diff.test.ts | 4 +- .../test/integ/cli/bootstrapping.integtest.ts | 13 +- .../aws-cdk/test/integ/cli/cdk-helpers.ts | 4 +- .../aws-cdk/test/integ/cli/cli.integtest.ts | 22 +- .../aws-cdk/test/util/cloudformation.test.ts | 11 +- packages/aws-cdk/test/util/mock-sdk.ts | 2 +- packages/awslint/bin/awslint.ts | 10 +- packages/awslint/lib/linter.ts | 2 +- packages/awslint/lib/rules/module.ts | 8 +- packages/awslint/lib/rules/resource.ts | 6 +- packages/cdk-assets/bin/cdk-assets.ts | 2 +- packages/cdk-assets/bin/publish.ts | 8 +- packages/cdk-assets/lib/asset-manifest.ts | 8 +- packages/cdk-assets/lib/private/docker.ts | 2 +- packages/cdk-assets/lib/private/shell.ts | 2 +- .../cdk-assets/test/docker-images.test.ts | 2 +- packages/cdk-assets/test/files.test.ts | 2 +- packages/cdk-assets/test/mock-aws.ts | 12 +- packages/cdk-assets/test/placeholders.test.ts | 2 +- tools/cdk-build-tools/bin/cdk-package.ts | 6 +- tools/cdk-build-tools/config/eslintrc.js | 67 +++-- tools/cdk-build-tools/lib/lint.ts | 2 +- tools/cdk-build-tools/lib/os.ts | 4 +- tools/cdk-integ-tools/bin/cdk-integ.ts | 4 +- tools/cdk-integ-tools/lib/integ-helpers.ts | 8 +- tools/cfn2ts/lib/codegen.ts | 5 +- tools/cfn2ts/lib/genspec.ts | 2 +- tools/cfn2ts/lib/spec-utils.ts | 2 +- tools/pkglint/bin/pkglint.ts | 2 +- tools/pkglint/lib/rules.ts | 2 +- 545 files changed, 3202 insertions(+), 2627 deletions(-) diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index 918279b480b30..7cb661c8601f7 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -504,7 +504,7 @@ function createSelfUpdatingStack(pipelineStack: cdk.Stack): SelfUpdatingPipeline stageName: 'build', actions: [buildAction], }); - return {synthesizedApp: buildOutput, pipeline}; + return { synthesizedApp: buildOutput, pipeline }; } function hasPipelineAction(expectedAction: any): (props: any) => boolean { diff --git a/packages/@aws-cdk/assert/test/assertions.test.ts b/packages/@aws-cdk/assert/test/assertions.test.ts index adf61cb0dec9a..a73b64aaf0329 100644 --- a/packages/@aws-cdk/assert/test/assertions.test.ts +++ b/packages/@aws-cdk/assert/test/assertions.test.ts @@ -92,8 +92,8 @@ passingExample('expect to be a superset of