Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: aws/aws-cdk
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.134.0
Choose a base ref
...
head repository: aws/aws-cdk
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.135.0
Choose a head ref

Commits on Nov 23, 2021

  1. chore: note about code snippets that uses consumers (#17645)

    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 23, 2021

    Verified

    This commit was signed with the committer’s verified signature.
    renovate-bot Mend Renovate
    Copy the full SHA
    9eedaa0 View commit details
  2. Copy the full SHA
    b45be32 View commit details
  3. chore(ecs): undeprecate Cluster.addCapacity (#17652)

    The `Cluster.addCapacity` method was deprecated in #14386 as part of the
    introduction of `Cluster.addAsgCapacityProvider`. However, the corresponding
    `ClusterProps.capacity` property and `AddCapacityOptions` interface were not
    deprecated, leading to a confusing mismash of deprecated and undeprecated usage.
    The README for ECS still heavily references `Cluster.addCapacity`, further
    leading to potential confusion for users just following the module's guidance.
    
    As part of cleaning up deprecated usage as part of the lead-up to the V2 launch,
    opting to un-deprecate the `addCapacity` method rather than deprecating the
    other two elements and rewriting the README.
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    njlynch authored Nov 23, 2021
    Copy the full SHA
    765c274 View commit details
  4. fix(s3-deployment): updating memoryLimit or vpc results in stack upda…

    …te failure (#17530)
    
    This fixes the issue where updating the memoryLimit or vpc of the
    BucketDeployment resource would result in a stack update failure. In
    order to fix this the ID of the BucketDeployment CustomResource includes
    info on the memoryLimit and vpc in the same way that the Lambda function
    does. This means that when either of these values are updated, the
    BucketDeployment CustomResource will be recreated along with the Lambda
    function.
    
    If anyone is setting `retainOnDelete=false` (default is `true`) then
    this change would result in the data in the bucket being deleted. In
    order to avoid this scenario, this PR introduces a bucket tag that
    controls whether or not a BucketDeployment resource can delete data from
    the bucket.
    
    The BucketDeployment resource will now add a tag to the deployment
    bucket with a format like `aws-cdk:cr-owned:{keyPrefix}:{uniqueHash}`.
    For example:
    
    ```
    {
      Key: 'aws-cdk:cr-owned:deploy/here/:240D17B3',
      Value: 'true',
    }
    ```
    
    Each bucket+keyPrefix can be "owned" by 1 or more BucketDeployment
    resources. Since there are some scenarios where multiple BucketDeployment
    resources can deploy to the same bucket and key prefix
    (e.g. using include/exclude) we also append part of the id to
    make the key unique.
    
    As long as a bucket+keyPrefix is "owned" by a BucketDeployment
    resource, another CR cannot delete data. There are a couple of
    scenarios where this comes into play.
    
    1. If the LogicalResourceId of the CustomResource changes
    (e.g. memoryLimit is updated) CloudFormation will first issue a 'Create'
    to create the new CustomResource and will update the Tag on the bucket.
    CloudFormation will then issue a 'Delete' on the old CustomResource
    and since the new CR "owns" the Bucket+keyPrefix (indicated by the
    presence of the tag), the old CR will not delete the contents of the bucket
    
    2. If the BucketDeployment resource is deleted _and_ it is the only CR
    for that bucket+keyPrefix then CloudFormation will first remove the tag
    from the bucket and then issue a "Delete" to the CR. Since there are no
    tags indicating that this bucket+keyPrefix is "owned" then it will delete
    the contents.
    
    3. If the BucketDeployment resource is deleted _and_ it is *not* the only
    CR for that bucket:keyPrefix then CloudFormation will first remove the tag
    from the bucket and then issue a "Delete" to the CR.
    Since there are other CRs that also "own" that bucket+keyPrefix
    (indicated by the presence of tags), there will
    still be a tag on the bucket and the contents will not be removed. The
    contents will only be removed after _all_ BucketDeployment resource that
    "own" the bucket+keyPrefix have been removed.
    
    4. If the BucketDeployment resource _and_ the S3 Bucket are both removed,
    then CloudFormation will first issue a "Delete" to the CR and since there
    is a tag on the bucket the contents will not be removed. If you want the
    contents of the bucket to be removed on bucket deletion, then
    `autoDeleteObjects` property should be set to true on the Bucket.
    
    Scenario 3 & 4 are changes to the existing functionality in that they
    now do *not* delete data in some scenarios when they did previously.
    
    fixes #7128
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    corymhall authored Nov 23, 2021
    Copy the full SHA
    2ba40d1 View commit details
  5. chore(aws-cdk-lib): change description and keywords (#17654)

    This is done with the express purpose of improving its discoverability on Construct Hub.
    
    ----
    Changed the description and added keywords
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    addihorowitz authored Nov 23, 2021
    Copy the full SHA
    82ce371 View commit details
  6. Copy the full SHA
    fc5dde9 View commit details
  7. Copy the full SHA
    ddf2881 View commit details

Commits on Nov 24, 2021

  1. chore(rds): undeprecated APIs whose migration will cause interruption (

    …#17683)
    
    All deprecated APIs will be removed from CDKv2.
    
    Migrating from `SnapshotCredentials.fromGeneratedPassword()` to
    its documented alternative will modify the RDS
    instance in ways that may impact usability of the resource. This API
    must not be deprecated.
    
    The alternative APIs to the `DatabaseInstanceEngine` APIs refereced in
    this PR will cause the [CFN EngineVersion][1] to be modified.
    Modification of this property causes [some interruption][2] to the
    resource.
    This may cause "some interruption" to users' running applications.
    
    [1]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-engineversion
    [2]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-some-interrupt
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 24, 2021
    Copy the full SHA
    797edbd View commit details
  2. feat(custom-resources): fixed Lambda function name (#17670)

    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    cyuste authored Nov 24, 2021
    Copy the full SHA
    5710fe5 View commit details
  3. fix(pipelines): stack outputs used in stackSteps not recognized (#17311)

    fixes #17272
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    bmmin authored Nov 24, 2021
    Copy the full SHA
    5e4a219 View commit details
  4. feat(iam): support fromGroupName() for IAM groups (#17243)

    IAM Policies and Users already support import by name. Extending same for Groups
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    nom3ad authored Nov 24, 2021
    Copy the full SHA
    29b379c View commit details
  5. fix(core): bundling skipped with --exclusively option and stacks unde…

    …r stage (#17210)
    
    We were comparing bundling stacks of the form `Stage/Stack` with stack
    names of the form `Stage-Stack`.
    
    For stacks with `NodejsFunction`s this leads to assets containing the whole CDK project because
    when bundling is skipped the asset references the source directory which is the project root.
    
    Closes #12898
    Closes #15346
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    jogold authored Nov 24, 2021
    Copy the full SHA
    cda6601 View commit details
  6. feat(ec2): add r6i instances (#17663)

    New R6I instances just got released:
    
    https://aws.amazon.com/about-aws/whats-new/2021/11/amazon-ec2-r6i-instances/
    
    Docs have already been updated:
    https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-instancetype
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    robertd authored Nov 24, 2021
    Copy the full SHA
    0138292 View commit details
  7. feat(cloudfront): Add support for response headers policy (#17359)

    feat(cloudfront): Add support for response headers policy
    
    closes #17290 
    
    Notes:
    ~1. Currently the CFNSpec is not up-to-date with the latest available cloudformation changes for `ResponseHeadersPolicyId` in `AWS::CloudFront::Distribution CacheBehavior`. Some aspects of the same are added to the PR but are left commented. Would update the PR once the spec is updated.~
    
    Refs:
    1. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-response-headers.html
    2. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-responseheaderspolicy.html
    3. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-cachebehavior.html#cfn-cloudfront-distribution-cachebehavior-responseheaderspolicyid
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    ayush987goyal authored Nov 24, 2021
    Copy the full SHA
    ea0acff View commit details
  8. chore: npm-check-updates && yarn upgrade (#17687)

    Ran npm-check-updates and yarn upgrade to keep the `yarn.lock` file up-to-date.
    aws-cdk-automation authored Nov 24, 2021
    Copy the full SHA
    6c7550b View commit details
  9. feat(s3): support Transfer Acceleration (#17636)

    Add support for S3 [Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html).
    
    This PR introduces:
    - New boolean property `transferAcceleration` to enable Transfer Acceleration.
    - New operation `transferAccelerationUrlForObject()` to get HTTPS endpoint for Transfer Acceleration.
    
    Closes #12570.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    jumic authored Nov 24, 2021
    Copy the full SHA
    b432822 View commit details
  10. feat(stepfunctions-tasks): add 'Emr on Eks' tasks (#17103)

    This CDK feature adds support for Emr on Eks by implementing API service integrations for the following three APIs.
    
    This PR adds three tasks which support Emr on Eks:
    1) [Create Virtual Cluster](https://docs.aws.amazon.com/emr-on-eks/latest/APIReference/API_CreateVirtualCluster.html)
    2) [ Start a job run](https://docs.aws.amazon.com/emr-on-eks/latest/APIReference/API_StartJobRun.html)
    3) [Delete virtual cluster ](https://docs.aws.amazon.com/emr-on-eks/latest/APIReference/API_DeleteVirtualCluster.html)
    
    
    Continuation of #15262 by @matthewsvu and @BenChaimberg:
    
    Closes #15234.
    
    ----
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    sanava2010 authored Nov 24, 2021
    Copy the full SHA
    f2bf322 View commit details
  11. feat(iot): add Action to capture CloudWatch metrics (#17503)

    I'm trying to implement aws-iot L2 Constructs.
    
    This PR is one of steps after following PR: 
    - #16681 (comment)
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    yamatatsu authored Nov 24, 2021
    Copy the full SHA
    ec4187c View commit details
  12. feat(docdb): implement audit and profiler logs (#17570)

    closes #17478 
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    markussiebert authored Nov 24, 2021
    Copy the full SHA
    4982aca View commit details

Commits on Nov 25, 2021

  1. feat(servicecatalog): Add TagOptions to a CloudformationProduct (#17672)

    Users can now associate TagOptions to a cloudformation product through an association call
    or upon instantiation. TagOptions added to a portfolio are made available for any products within it,
    but you can also have separate, product level tag options.  We only create unique TagOption constructs in the template
    but we can have the same Tag Option associated with both a portfolio and a product in that portfolio, the logic that
    resolves this is handled by service catalog.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    
    
    Co-authored-by: Dillon Ponzo <dponzo18@gmail.com>
    arcrank and dponzo authored Nov 25, 2021
    Copy the full SHA
    2d19e15 View commit details
  2. fix(docdb): secret rotation ignores excluded characters in password (#…

    …17609)
    
    We need to pass whatever `excludeCharacters` were passed to the generated Secret to the application responsible for the rotation.
    
    Fixes #17347
    Fixes #17575 
    
    ------
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    markussiebert authored Nov 25, 2021
    Copy the full SHA
    1fe2215 View commit details
  3. fix(codepipeline): cannot trigger on all tags anymore in EcrSourceAct…

    …ion (#17270)
    
    The EcrSourceAction could previously be used to trigger on changes to all tags of an image. As part of the fix #13818, the imageTag was defaulted to latest if not provided. Therefore it was no longer possible to call the underlying onCloudTrailImagePushed function with an undefined imageTag to watch changes on all tags.
    
    Reintroduce triggering on all tags by passing an empty string as the imageTag.
    
    Fixes #13818
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    sparten11740 authored Nov 25, 2021
    Copy the full SHA
    39fe11b View commit details
  4. fix(assert): support multiline strings with stringLike() (#17692)

    Updates the `RegExp` constructor to support multiline. Resolves #17691
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    diegotry authored Nov 25, 2021
    Copy the full SHA
    37596e6 View commit details
  5. feat(apigateway): step functions integration (#16827)

    - Added StepFunctionsRestApi and StepFunctionsIntegration implementation
    
    closes #15081.
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    saqibdhuka authored Nov 25, 2021
    Copy the full SHA
    cb31547 View commit details
  6. docs: add links to CloudFormation documentation to READMEs (#17696)

    Currently, the documentation of our CFN-only libraries leaves a lot to
    be desired, which is confusing users.
    
    Update the READMEs to make it very clear that we don't have anything for
    them, and point them to the right location for getting documentation.
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    rix0rrr authored Nov 25, 2021
    Copy the full SHA
    b03b4dc View commit details
  7. chore(assertions): snippets avoid using intermediate variable assignm…

    …ent (#17714)
    
    Previously, the rosetta translations could not determine the type of
    objects being passed into methods when they are untyped. Some were being
    interpreted as 'props' type while they should just be regarding as
    `Record` or `any`.
    
    To compensate for this, the README in this module assigned them to
    variables, so the translator did a better job at knowing this.
    
    This has now been fixed in rosetta. Move back to using this inline,
    since the usage is just nicer.
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 25, 2021
    Copy the full SHA
    9d436a0 View commit details
  8. chore(apigatewayv2): remove dependency of "http" from "common" (#17715)

    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 25, 2021
    Copy the full SHA
    b8a4a9a View commit details
  9. chore(aws-cdk-lib): prevent deep imports (#17707)

    Sometimes, IDEs like VSCode will autocomplete deep imports into the CDK
    library. For example, they may generate the following:
    
    ```ts
    import { Bucket } from 'aws-cdk-lib/aws-s3/lib/bucket';
    ```
    
    Whereas the correct import should have been:
    
    ```ts
    import { Bucket } from 'aws-cdk-lib/aws-s3';
    ```
    
    If we allow people to write the former, they will be broken every time
    we change the internal file layout of our module (or conversely, we
    will not be allowed to change the file layout at all).
    
    Use the `package.json` `"exports"` mechanism to advertise the select
    paths that users are allowed to import from, and disallow the rest.
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    rix0rrr authored Nov 25, 2021
    Copy the full SHA
    53620e9 View commit details

Commits on Nov 26, 2021

  1. feat(ec2): add mac1 instance (#17677)

    `mac1` instances got released last year:
    https://aws.amazon.com/about-aws/whats-new/2021/10/amazon-ec2-mac-instances-additional-regions/
    
    Docs have already been updated a while ago:
    https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-instancetype
    
    Note: Whenever `mac2` comes out (probably on M1, or most likely on M1Pro/M1Max) we'll have to update `InstanceArchitecture` enum [here](https://github.com/aws/aws-cdk/blob/ddf2881ee24cbf3083463a6e772a5c91acc229aa/packages/%40aws-cdk/aws-ec2/lib/instance-types.ts#L573).
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    robertd authored Nov 26, 2021
    Copy the full SHA
    88a5204 View commit details
  2. feat(cfnspec): cloudformation spec v49.0.0 (#17727)

    Co-authored-by: AWS CDK Team <aws-cdk@amazon.com>
    Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
    3 people authored Nov 26, 2021
    Copy the full SHA
    7e0c9a3 View commit details
  3. fix(cli): S3 asset uploads are rejected by commonly referenced encryp…

    …tion SCP (introduces bootstrap stack v9) (#17668)
    
    Many organizations around the world have started to use a specific Service Control Policy (SCP) from this blog post: https://aws.amazon.com/blogs/security/how-to-prevent-uploads-of-unencrypted-objects-to-amazon-s3/ in order to make sure all S3 bucket uploads are encrypted.
    
    CDK configures the `DefaultEncryptionConfiguration` on the bucket so that objects are always encrypted by default, but this specific SCP can only check that individual upload actions include the "enable encryption" option. That means that even though the end result would still be satisfied (objects are encrypted in S3), the SCP would nevertheless reject the CDK upload. We would rather people use AWS Config to make sure all buckets have `DefaultEncryptionConfiguration` set, so that this SCP is not necessary... but there is no arguing with deployed reality.
    
    Change the CDK upload code path to first read out the default encryption configuration from the bucket, only to then mirror those exact same settings in the `PutObject` call so that the SCP can see that they are present.
    
    This requires adding a new permission to the `cdk-assets` role, namely `s3:GetEncryptionConfiguration`, so requires a new bootstrap stack version: version 9.
    
    If you want this new behavior because your organization applies this specific SCP, you must upgrade to bootstrap stack version 9. If you do not care about this new behavior you don't have to do anything: if the call to `getEncryptionConfiguration` fails, the CDK will fall back to the old behavior (not specifying any header).
    
    Fixes #11265.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    ArlindNocaj authored Nov 26, 2021
    Copy the full SHA
    8191f1f View commit details
  4. fix(apigatewayv2): integration class does not render an integration r…

    …esource (#17729)
    
    Routes on APIGateway V2 can integrate with different backends. This is
    achieved by creating the CFN resource `AWS::ApiGatewayV2::Integration`
    that is then referenced in the resource for the Route.
    
    Currently, the `IHttpRouteIntegration` (and `IWebSocketRouteIntegration`)
    interface represents a unique backend that a route can integrate with,
    using the CDK "bind" pattern.
    An integration can be bound to any number of routes but should be
    rendered into a single instance of `AWS::ApiGatewayV2::Integration`
    resource.
    To achieve this currently, the `HttpApi` (and `WebSocketApi`) class
    holds a cache of all integrations defined against its routes.
    
    This is the wrong level of caching and causes a number of problems.
    
    1. We rely on the configuration of the `AWS::ApiGateway::Integration`
       resource to determine if one already exists.
       This means that two instances of `IHttpRouteIntegration` can result
       in rendering only one instance of `AWS::ApiGateway::Integration`
       resource.
    
       Users may want to intentionally generate multiple instances of
       `AWS::ApiGateway::Integration` classes with the same configuration.
       Taking away this power with CDK "magic" is just confusing.
    
    2. Currently, we allow using the same instance of
       `IHttpRouteIntegration` (or `IWebSocketRouteIntegration`) to be bound
       to routes in different `HttpApi`. When bound to the route, the CDK
       renders an instance of `AWS::ApiGatewayV2::Integration` for each API.
    
       This is another "magic" that has the potential for user confusion and
       bugs.
    
    The solution is to KeepItSimple™.
    
    Remove the API level caching and simply cache at the level of each
    integration. This ensures that each instance of `HttpRouteIntegration`
    (previously `IHttpRouteIntegration`) renders to exactly one instance of
    `AWS::ApiGatewayV2::Integration`.
    
    Disallow using the same instance of `HttpRouteIntegration` across
    different instances of `HttpApi`.
    
    fixes #13213
    
    BREAKING CHANGE: The interface `IHttpRouteIntegration` is replaced by
    the abstract class `HttpRouteIntegration`.
    * **apigatewayv2:** The interface `IWebSocketRouteIntegration` is now
      replaced by the abstract class `WebSocketRouteIntegration`.
    * **apigatewayv2:** Previously, we allowed the usage of integration
      classes to be used with routes defined in multiple `HttpApi` instances
      (or `WebSocketApi` instances). This is now disallowed, and separate
      instances must be created for each instance of `HttpApi` or
      `WebSocketApi`.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 26, 2021
    Copy the full SHA
    3b5b97a View commit details
  5. feat(lambda): function construct exposes configured timeout (#17594)

    Supersedes #17000.
    
    I didn't realise that the "allow edits by maintainers" was not supported under organisation accounts, so I've forked under my account instead. Otherwise, these changes are the same as the linked PR.
    
    Thanks! 👍 
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    ben-eb authored Nov 26, 2021
    Copy the full SHA
    87fd60f View commit details
  6. fix(codepipeline): cross-env pipeline cannot be created in Stage (#…

    …17730)
    
    Because a cross-environment pipeline cannot be created in `Stage`,
    it cannot be deployed using CDK Pipelines.
    
    The error is:
    
    ```
    Error: You cannot add a dependency from 'AAA' (in Stage 'BBB') to 'CCC' (in the App): dependency cannot cross stage boundaries
    ```
    
    Root cause is that the `Pipeline` construct creates a support stack
    in the `App` scope, which is outside its containing `Stage`, and hence
    the dependency crosses stage boundaries.
    
    Fixes #17643.
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    rix0rrr authored Nov 26, 2021
    Copy the full SHA
    f17f29e View commit details
  7. chore(cognito): docs showed incorrect default value for preventUserEx…

    …istenceErrors (#17667)
    
    The docs for preventUserExistenceErrors showed a default of `true` when
    the default has always been `false`. Update the docs to reflect the
    actual default. Also remove references to "new stacks" since the
    functionality is the same between new and existing stacks.
    
    fixes #17044
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    corymhall authored Nov 26, 2021
    Copy the full SHA
    efaaaf5 View commit details
  8. feat(ecs-service-extensions): Auto scaling for Queue Extension (#17430)

    ----
    This PR adds target tracking auto scaling policy for the the SQS Queues provided to and created by the `QueueExtension` (in the `useService()` hook).
    
    The auto scaling is based on `backlogPerTask` custom metric which is emitted by an AWS Lambda Function. The PR also contains this Lambda Function and its tests.
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    upparekh authored Nov 26, 2021
    Copy the full SHA
    df7b9b4 View commit details

Commits on Nov 29, 2021

  1. chore(deps): bump actions/cache from 2.1.6 to 2.1.7 (#17745)

    Bumps [actions/cache](https://github.com/actions/cache) from 2.1.6 to 2.1.7.
    <details>
    <summary>Release notes</summary>
    <p><em>Sourced from <a href="https://github.com/actions/cache/releases">actions/cache's releases</a>.</em></p>
    <blockquote>
    <h2>v2.1.7</h2>
    <p>Support 10GB cache upload using the latest version <code>1.0.8</code> of <a href="https://www.npmjs.com/package/@actions/cache"><code>@actions/cache</code> </a></p>
    </blockquote>
    </details>
    <details>
    <summary>Commits</summary>
    <ul>
    <li><a href="https://github.com/actions/cache/commit/937d24475381cd9c75ae6db12cb4e79714b926ed"><code>937d244</code></a> bumping up action version to 2.1.7 (<a href="https://github-redirect.dependabot.com/actions/cache/issues/683">#683</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/eb0698d1c508f8573fddfe25566f10a4b1344504"><code>eb0698d</code></a> Bumping up <code>@​actions/cache</code> version to 1.0.8 (<a href="https://github-redirect.dependabot.com/actions/cache/issues/682">#682</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/67b6d52d50609f6166e3ea1d8872aca3a4763bd2"><code>67b6d52</code></a> (R renv) Remove unused renv-cache-path variable (<a href="https://github-redirect.dependabot.com/actions/cache/issues/663">#663</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/92f67a482915a145e9372ed84b9e7f13538ecc69"><code>92f67a4</code></a> (R renv) Fix Renv package cache location in examples (<a href="https://github-redirect.dependabot.com/actions/cache/issues/660">#660</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/6bbe742add91b3db4abf110e742a967ec789958f"><code>6bbe742</code></a> Use existing check-dist implementation (<a href="https://github-redirect.dependabot.com/actions/cache/issues/618">#618</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/c9db520cf31dc27e42864cc3687b0d70284cc5fc"><code>c9db520</code></a> Create check-dist.yml (<a href="https://github-redirect.dependabot.com/actions/cache/issues/604">#604</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/10906ba9cd642bcc07f0f38a95a57e5c1361d156"><code>10906ba</code></a> Bump ws from 5.2.2 to 5.2.3 (<a href="https://github-redirect.dependabot.com/actions/cache/issues/610">#610</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/2ebdcff279bac9704c2b319b25ac54b63d6800c2"><code>2ebdcff</code></a> Add &quot;see more&quot; link to GHE-not-supported warning (<a href="https://github-redirect.dependabot.com/actions/cache/issues/609">#609</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/5807af2642b6ffc80df306359122fd0ff9b571b8"><code>5807af2</code></a> Fix bugs in example of how to use with pipenv (<a href="https://github-redirect.dependabot.com/actions/cache/issues/607">#607</a>)</li>
    <li><a href="https://github.com/actions/cache/commit/0638051e9af2c23d10bb70fa9beffcad6cff9ce3"><code>0638051</code></a> Golang example tweak - add <code>go-build</code> path - rebuild page TOC (<a href="https://github-redirect.dependabot.com/actions/cache/issues/577">#577</a>)</li>
    <li>See full diff in <a href="https://github.com/actions/cache/compare/v2.1.6...v2.1.7">compare view</a></li>
    </ul>
    </details>
    <br />
    
    
    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=2.1.6&new-version=2.1.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
    
    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.
    
    [//]: # (dependabot-automerge-start)
    [//]: # (dependabot-automerge-end)
    
    ---
    
    <details>
    <summary>Dependabot commands and options</summary>
    <br />
    
    You can trigger Dependabot actions by commenting on this PR:
    - `@dependabot rebase` will rebase this PR
    - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
    - `@dependabot merge` will merge this PR after your CI passes on it
    - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
    - `@dependabot cancel merge` will cancel a previously requested merge and block automerging
    - `@dependabot reopen` will reopen this PR if it is closed
    - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    
    
    </details>
    dependabot[bot] authored Nov 29, 2021
    Copy the full SHA
    171cbc1 View commit details
  2. fix(aws-elasticloadbalancingv2): Set stickiness.enabled unless target…

    … type is lambda (#17271)
    
    Avoid setting `stickiness.enabled` to `false` when the target group type is lambda as it breaks on deployment.
    
    Fixes #17261.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    topecongiro authored Nov 29, 2021
    Copy the full SHA
    168a98f View commit details
  3. feat(ec2): explicit mapPublicIpOnLaunch configuration for public subn…

    …ets (#17346)
    
    **Issue (Fixes #14194, #16838
    When creating a VPC you can define a SubnetConfiguration but it is not possible to define `mapPublicIpOnLaunch` for public subnets.
    
    VPC Example:
    ```
            const vpc = new ec2.Vpc(this, 'vpc-id', {
                maxAzs: 2,
                subnetConfiguration: [
                    {
                        name: 'private-subnet-1',
                        subnetType: ec2.SubnetType.PRIVATE,
                        cidrMask: 24,
                    },
                    {
                        name: 'public-subnet-1',
                        subnetType: ec2.SubnetType.PUBLIC,
                        cidrMask: 24,
                    },
                ]
            });
    ```
    
    Proposal:
    ```
            const vpc = new ec2.Vpc(this, 'vpc-id', {
                maxAzs: 2,
                subnetConfiguration: [
                    {
                        name: 'private-subnet-1',
                        subnetType: ec2.SubnetType.PRIVATE,
                        cidrMask: 24,
                    },
                    {
                        name: 'public-subnet-1',
                        subnetType: ec2.SubnetType.PUBLIC,
                        cidrMask: 24,
                        mapPublicIpOnLaunch: false, // or true
                    },
                ]
            });
    ```
    hguillermo authored Nov 29, 2021
    Copy the full SHA
    a1685c6 View commit details
  4. chore(apigatewayv2): integration api re-organization (#17752)

    There are three major changes.
    
    `HttpRouteIntegration` (and its sibling `WebSocketRouteIntegration`)
    creates a CDK construct (`HttpIntegration` and `WebSocketIntegration`)
    as part of its bind operation. The id to this CDK construct is
    determined by hashing the results of the bind.
    Using hashes makes the construct id fragile/sensitive, consequently the
    CFN resource's logical id fragile.
    The fragility comes mainly from the question - have we hashed all of the
    expected properties that should be hashed, and nothing extra?
    If we have not hashed properties that should be there, or hashed too
    much, we end up with a hash change, hence resource replacement that
    is unexpected.
    
    This commit changes this approach and asks the user to provide the
    construct's id. This is more aligned with the current CDK expectation
    that users provide an id when initializing constructs.
    We just don't have a good way to validate that our hashing is accurate,
    so let's not do it at all. This change makes the user provide a unique
    name within a scope, which is already a standard requirement for CDK
    constructs.
    
    Secondly, the ergonomics of specific integration implementations, such
    as, `LambdaProxyIntegration`, `HttpAlbIntegration`, etc. is modified so
    that the integrating primitive is moved out of the 'props', and to the
    constructor.
    The API ergonomics of this feels much better than having to always
    provide a 'props'.
    
    Since this package contains constructs around both http api and
    websocket api, the convention to follow is that all classes specific to
    the former will be prefixed with `Http` and the latter will be prefixed
    with `WebSocket`.
    Bring the integration classes `LambdaProxyIntegration` and
    `HttpProxyIntegration` in line with this convention. These are renamed
    to `HttpLambdaIntegration` and `HttpUrlIntegration` respectively.
    
    BREAKING CHANGE: The `HttpIntegration` and `WebSocketIntegration`
    classes require an "id" parameter to be provided during its initialization.
    * **apigatewayv2-integrations:** The `LambdaWebSocketIntegration` is now
      renamed to `WebSocketLambdaIntegration`. The new class accepts the
      handler to the target lambda function directly in its constructor.
    * **apigatewayv2-integrations:** `HttpProxyIntegration` and
      `HttpProxyIntegrationProps` are now renamed to `HttpUrlIntegration`
      and `HttpUrlIntegrationProps` respectively. The new class accepts the
      target url directly in its constructor.
    * **apigatewayv2-integrations:** `LambdaProxyIntegration` and
      `LambdaProxyIntegrationProps` are now renamed to
      `HttpLambdaIntegration` and `HttpLambdaIntegrationProps` respectively.
      The new class accepts the lambda function handler directly in its
      constructor.
    * **apigatewayv2-integrations:** `HttpAlbIntegration` now accepts the
      ELB listener directly in its constructor.
    * **apigatewayv2-integrations:** `HttpNlbIntegration` now accepts the
      ELB listener directly in its constructor.
    * **apigatewayv2-integrations:** `HttpServiceDiscoveryIntegration` now
      accepts the service discovery Service directly in its constructor.
    * **apigatewayv2-authorizers:** `UserPoolAuthorizerProps` is now
      renamed to `HttpUserPoolAuthorizerProps`.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 29, 2021
    Copy the full SHA
    29039e8 View commit details

Commits on Nov 30, 2021

  1. chore: avoid runtime dependency between cfnspec and pkglint (#17751)

    Our build pipeline is currently failing because `cfnspec` (which is a public package) takes a runtime dependency on `pkglint` (which is a private package). This was introduced in #17696.
    
    To resolve this, we moved the functionality that generates new L1 library code from `lib/` to `build-tools/` since it's only needed in CDK build context (technically this is breaking public API, but we could not see any use case for external users to generate L1 modules in CDK-repository format).
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Elad Ben-Israel authored Nov 30, 2021
    Copy the full SHA
    5fc0141 View commit details
  2. chore(generate-examples): generate fixtures (#17737)

    Motivation: we want the ability for `rosetta:extract` to be called after `generate-examples`. Currently, this results in errors because the generated examples are only stored in the assembly, and only the visible source, so they fail compilation when we attempt to extract them.
    
    Solution: while we generate examples, we also generate a fixture per assembly, `_generated.ts-fixture`, that stores the necessary import statement for compilation. Then, when we later (might) call extract, we have a fixture to place the example in.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    kaizencc authored Nov 30, 2021
    Copy the full SHA
    446f5fd View commit details
  3. feat(ec2): add vt1 instances (#17756)

    `vt1` instances release note:
    https://aws.amazon.com/blogs/aws/new-amazon-ec2-vt1-instances-for-live-multi-stream-video-transcoding/
    
    Docs have already been updated a while ago:
    https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-instancetype
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    robertd authored Nov 30, 2021
    Copy the full SHA
    245c059 View commit details
  4. Copy the full SHA
    b06f120 View commit details
  5. Copy the full SHA
    1799f7e View commit details
  6. chore(apigatewayv2-authorizers): re-organize authorizer api (#17772)

    This is a follow on to a previous commit 29039e8.
    
    Update the ergonomics of the authorizer construct APIs to be aligned
    with the rest of the module, specifically the integration construct
    APIs.
    
    The API now takes the construct id and the integration target as part of
    the constructor, instead of in the props class.
    
    In most cases, except in the case of jwt, all properties in the props
    struct become optional, which improves API ergonomics.
    It also removes the need for `authorizerName` property to be required.
    
    BREAKING CHANGE: The default value for the prop `authorizerName`
    in `HttpJwtAuthorizerProps` has changed.
    * **apigatewayv2-authorizers:** `HttpJwtAuthorizer` now takes the
      construct id and the target jwt issuer as part of its constructor.
    * **apigatewayv2-authorizers:** `HttpLambdaAuthorizer` now takes
      the construct id and the target lambda function handler as part of
      its constructor.
    * **apigatewayv2-authorizers:** The default value for the prop
      `authorizerName` in `HttpUserPoolAuthorizerProps` has changed.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    Niranjan Jayakar authored Nov 30, 2021
    Copy the full SHA
    719f33e View commit details
  7. feat(ec2): extend BastionHostLinux to support CloudFormationInit (#17507

    )
    
    Implements #17161
    
    Extends the `BastionHostLinux` constructor to accept optional `CloudFormationInit` and `ApplyCloudFormationInitOptions` arguments to be passed to the underlying instance.
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    mpvosseller authored Nov 30, 2021
    Copy the full SHA
    c62377e View commit details
  8. fix(apprunner): startCommand and environment are ignored in imageConf…

    …iguration (#16939)
    
    This PR addresses the following issues
    
    1. custom environment variables and start commands should be allowed for `imageConfiguration`
    2. buildCommand, environment and startCommand should be allowed for `codeConfigurationValues`
    
    
    Fixes: #16812 
    
    - [x] add tests
    - [x] implementation
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    pahud authored Nov 30, 2021
    Copy the full SHA
    d911c58 View commit details
  9. feat(neptune): add engine version 1.1.0.0 and instance types t4g, r6g (

    …#17669)
    
    Add new instance types general-purpose T4g and memory-optimized R6g (see [announcement](https://aws.amazon.com/about-aws/whats-new/2021/11/aws-graviton2-based-instances-amazon-neptune/)). The specific instance types were copied from the [pricing page](https://aws.amazon.com/de/neptune/pricing/).
    
    Add new Neptune engine version 1.1.0.0 which was part of the announcement, too.
    
    Deployment tested successfully in region us-east-1:
    
    ```ts
    new neptune.DatabaseCluster(this, 'Database', {
      vpc,
      instanceType: neptune.InstanceType.T4G_MEDIUM,
      engineVersion: neptune.EngineVersion.V1_1_0_0,
    });
    
    new neptune.DatabaseCluster(this, 'Database2', {
      vpc,
      instanceType: neptune.InstanceType.R6G_LARGE,
      engineVersion: neptune.EngineVersion.V1_1_0_0,
    });
    ```
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    jumic authored Nov 30, 2021
    Copy the full SHA
    83e669d View commit details
  10. fix(lambda-nodejs): bundling with nodeModules fails with paths cont…

    …aining spaces (#17632)
    
    Enclose paths with double quotes (`"`).
    
    Closes #17631
    
    
    ----
    
    *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
    jogold authored Nov 30, 2021
    Copy the full SHA
    986f291 View commit details
Showing 714 changed files with 25,564 additions and 2,945 deletions.
2 changes: 2 additions & 0 deletions .gitallowed
Original file line number Diff line number Diff line change
@@ -32,3 +32,5 @@ account: '919830735681' #cn-northwest-1
account: '297356227824'
# partition aws-cn
account: '193023089310'
# partition aws-us-gov
account: '023102451235'
40 changes: 20 additions & 20 deletions .github/workflows/issue-label-assign.yml
Original file line number Diff line number Diff line change
@@ -23,17 +23,17 @@ jobs:
{"area":"package/tools","keywords":["cli","command line","init","synth","diff","bootstrap"],"labels":["package/tools"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/alexa-ask","keywords":["alexa-ask","alexa", "cfnskill"],"labels":["@aws-cdk/alexa-ask"],"assignees":["madeline-k"]},
{"area":"@aws-cdk/app-delivery","keywords":["app-delivery","PipelineDeployStackAction"],"labels":["@aws-cdk/app-delivery"],"assignees":["skinny85"]},
{"area":"@aws-cdk/assert","keywords":["assert"],"labels":["@aws-cdk/assert"],"assignees":["nija-at"]},
{"area":"@aws-cdk/assertions","keywords":["assertions"],"labels":["@aws-cdk/assertions"],"assignees":["nija-at"]},
{"area":"@aws-cdk/assert","keywords":["assert"],"labels":["@aws-cdk/assert"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/assertions","keywords":["assertions"],"labels":["@aws-cdk/assertions"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/assets","keywords":["assets","staging"],"labels":["@aws-cdk/assets"],"assignees":["eladb"]},
{"area":"@aws-cdk/aws-accessanalyzer","keywords":["aws-accessanalyzer","accessanalyzer","cfnanalyzer"],"labels":["@aws-cdk/aws-accessanalyzer"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-acmpca","keywords":["aws-acmpca","acmpca","certificateauthority"],"labels":["@aws-cdk/aws-acmpca"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-amazonmq","keywords":["aws-amazonmq","amazonmq","cfnbroker"],"labels":["@aws-cdk/aws-amazonmq"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-amplify","keywords":["aws-amplify","amplify","GitHubSourceCodeProvider","CodeCommitSourceCodeProvider","GitLabSourceCodeProvider"],"labels":["@aws-cdk/aws-amplify"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-apigateway","keywords":["aws-apigateway","api-gateway"],"labels":["@aws-cdk/aws-apigateway"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-apigatewayv2","keywords":["aws-apigatewayv2","api-gateway-v2","apimapping","httpapi","httproute","httpstage","httpauthorizer","httpintegration"],"labels":["@aws-cdk/aws-apigatewayv2"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-apigatewayv2-authorizers","keywords":["(aws-apigatewayv2-authorizers)","(apigatewayv2-authorizers)"],"labels":["@aws-cdk/aws-apigatewayv2-authorizers"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-apigatewayv2-integrations","keywords":["aws-apigatewayv2-integrations","api-gateway-v2-integrations","httpalbintegration","httpnlbintegration","httpproxyintegration","lambdaproxyintegration","httpservicediscoveryintegration","lambdawebsocketintegration"],"labels":["@aws-cdk/aws-apigatewayv2-integrations"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-apigateway","keywords":["aws-apigateway","api-gateway"],"labels":["@aws-cdk/aws-apigateway"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-apigatewayv2","keywords":["aws-apigatewayv2","api-gateway-v2","apimapping","httpapi","httproute","httpstage","httpauthorizer","httpintegration"],"labels":["@aws-cdk/aws-apigatewayv2"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-apigatewayv2-authorizers","keywords":["(aws-apigatewayv2-authorizers)","(apigatewayv2-authorizers)"],"labels":["@aws-cdk/aws-apigatewayv2-authorizers"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-apigatewayv2-integrations","keywords":["aws-apigatewayv2-integrations","api-gateway-v2-integrations","httpalbintegration","httpnlbintegration","httpproxyintegration","lambdaproxyintegration","httpservicediscoveryintegration","lambdawebsocketintegration"],"labels":["@aws-cdk/aws-apigatewayv2-integrations"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-appconfig","keywords":["aws-appconfig","app-config"],"labels":["@aws-cdk/aws-appconfig"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/aws-appflow","keywords":["aws-appflow","appflow"],"labels":["@aws-cdk/aws-appflow"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-appintegrations","keywords":["(aws-appintegrations)","(appintegrations)"],"labels":["@aws-cdk/aws-appintegrations"],"assignees":["skinny85"]},
@@ -74,15 +74,15 @@ jobs:
{"area":"@aws-cdk/aws-codestar","keywords":["aws-codestar","codestar","githubrepository"],"labels":["@aws-cdk/aws-codestar"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-codestarconnections","keywords":["aws-codestarconnections","codestar-connections"],"labels":["@aws-cdk/aws-codestarconnections"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-codestarnotifications","keywords":["aws-codestarnotifications","codestar-notifications"],"labels":["@aws-cdk/aws-codestarnotifications"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-cognito","keywords":["aws-cognito","cognito","userpool","userpoolclient","userpooldomain"],"labels":["@aws-cdk/aws-cognito"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-cognito","keywords":["aws-cognito","cognito","userpool","userpoolclient","userpooldomain"],"labels":["@aws-cdk/aws-cognito"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-config","keywords":["aws-config","accesskeysrotated","CloudFormationStackDriftDetectionCheck","CloudFormationStackNotificationCheck","managedrule"],"labels":["@aws-cdk/aws-config"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/aws-customerprofiles","keywords":["(aws-customerprofiles)","(customerprofiles)"],"labels":["@aws-cdk/aws-customerprofiles"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-databrew","keywords":["(aws-databrew)","(databrew)"],"labels":["@aws-cdk/aws-databrew"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/aws-datapipeline","keywords":["aws-datapipeline","data-pipeline"],"labels":["@aws-cdk/aws-datapipeline"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/aws-datasync","keywords":["(aws-datasync)","(datasync)"],"labels":["@aws-cdk/aws-datasync"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/aws-dax","keywords":["aws-dax","dax"],"labels":["@aws-cdk/aws-dax"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-detective","keywords":["aws-detective","detective"],"labels":["@aws-cdk/aws-detective"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-devopsguru","keywords":["(aws-devopsguru)","(devopsguru)"],"labels":["@aws-cdk/aws-devopsguru"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-devopsguru","keywords":["(aws-devopsguru)","(devopsguru)"],"labels":["@aws-cdk/aws-devopsguru"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-directoryservice","keywords":["aws-directoryservice","directory-service"],"labels":["@aws-cdk/aws-directoryservice"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/aws-dlm","keywords":["aws-dlm","dlm"],"labels":["@aws-cdk/aws-dlm"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-dms","keywords":["aws-dms","dms"],"labels":["@aws-cdk/aws-dms"],"assignees":["njlynch"]},
@@ -94,7 +94,7 @@ jobs:
{"area":"@aws-cdk/aws-ecr-assets","keywords":["aws-ecr-assets","ecrassets"],"labels":["@aws-cdk/aws-ecr-assets"],"assignees":["eladb"]},
{"area":"@aws-cdk/aws-ecs","keywords":["(aws-ecs)","(ecs)"],"labels":["@aws-cdk/aws-ecs"],"assignees":["madeline-k"]},
{"area":"@aws-cdk/aws-ecs-patterns","keywords":["(aws-ecs-patterns)","(ecs-patterns)"],"labels":["@aws-cdk/aws-ecs-patterns"],"assignees":["madeline-k"]},
{"area":"@aws-cdk/aws-efs","keywords":["aws-efs","efs","accesspoint"],"labels":["@aws-cdk/aws-efs"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-efs","keywords":["aws-efs","efs","accesspoint"],"labels":["@aws-cdk/aws-efs"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-eks","keywords":["aws-eks","eks","fargateprofile","fargatecluster"],"labels":["@aws-cdk/aws-eks"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-eks-legacy","keywords":["(aws-eks-legacy)","(eks-legacy)"],"labels":["@aws-cdk/aws-eks-legacy"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-elasticache","keywords":["aws-elasticache","elastic-cache"],"labels":["@aws-cdk/aws-elasticache"],"assignees":["otaviomacedo"]},
@@ -142,12 +142,12 @@ jobs:
{"area":"@aws-cdk/aws-kinesisfirehose","keywords":["aws-kinesisfirehose","kinesisfirehose"],"labels":["@aws-cdk/aws-kinesisfirehose"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-kms","keywords":["key-management-service","aws-kms","kms"],"labels":["@aws-cdk/aws-kms"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-lakeformation","keywords":["data-lake","aws-lakeformation","lakeformation"],"labels":["@aws-cdk/aws-lakeformation"],"assignees":["comcalvi"]},
{"area":"@aws-cdk/aws-lambda","keywords":["function","layerversion","aws-lambda","lambda"],"labels":["@aws-cdk/aws-lambda"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-lambda-destinations","keywords":["(aws-lambda-destinations)","(lambda-destinations)"],"labels":["@aws-cdk/aws-lambda-destinations"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-lambda-event-sources","keywords":["dynamoeventsource","aws-lambda-event-sources","lambda-event-sources","apieventsource","kinesiseventsource"],"labels":["@aws-cdk/aws-lambda-event-sources"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-lambda-go","keywords":["(aws-lambda-go)","(lambda-go)"],"labels":["@aws-cdk/aws-lambda-go"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-lambda-nodejs","keywords":["nodejsfunction","aws-lambda-nodejs","lambda-nodejs"],"labels":["@aws-cdk/aws-lambda-nodejs"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-lambda-python","keywords":["aws-lambda-python","lambda-python","pythonfunction"],"labels":["@aws-cdk/aws-lambda-python"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-lambda","keywords":["function","layerversion","aws-lambda","lambda"],"labels":["@aws-cdk/aws-lambda"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/aws-lambda-destinations","keywords":["(aws-lambda-destinations)","(lambda-destinations)"],"labels":["@aws-cdk/aws-lambda-destinations"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/aws-lambda-event-sources","keywords":["dynamoeventsource","aws-lambda-event-sources","lambda-event-sources","apieventsource","kinesiseventsource"],"labels":["@aws-cdk/aws-lambda-event-sources"],"assignees":["kaizen3031593"]},
{"area":"@aws-cdk/aws-lambda-go","keywords":["(aws-lambda-go)","(lambda-go)"],"labels":["@aws-cdk/aws-lambda-go"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-lambda-nodejs","keywords":["nodejsfunction","aws-lambda-nodejs","lambda-nodejs"],"labels":["@aws-cdk/aws-lambda-nodejs"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-lambda-python","keywords":["aws-lambda-python","lambda-python","pythonfunction"],"labels":["@aws-cdk/aws-lambda-python"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-licensemanager","keywords":["(aws-licensemanager)","(licensemanager)"],"labels":["@aws-cdk/aws-licensemanager"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-logs","keywords":["loggroup","aws-logs","logs","logretention"],"labels":["@aws-cdk/aws-logs"],"assignees":["comcalvi"]},
{"area":"@aws-cdk/aws-logs-destinations","keywords":["aws-logs-destinations","lambdadestination","kinesisdestination","logs-destinations"],"labels":["@aws-cdk/aws-logs-destinations"],"assignees":["rix0rrr"]},
@@ -187,7 +187,7 @@ jobs:
{"area":"@aws-cdk/aws-s3-assets","keywords":["aws-s3-assets","s3-assets"],"labels":["@aws-cdk/aws-s3-assets"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-s3-deployment","keywords":["aws-s3-deployment","s3-deployment"],"labels":["@aws-cdk/aws-s3-deployment"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-s3-notifications","keywords":["aws-s3-notifications","s3-notifications"],"labels":["@aws-cdk/aws-s3-notifications"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-s3objectlambda","keywords":["(aws-s3objectlambda)","(s3objectlambda)"],"labels":["@aws-cdk/aws-s3objectlambda"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-s3objectlambda","keywords":["(aws-s3objectlambda)","(s3objectlambda)"],"labels":["@aws-cdk/aws-s3objectlambda"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-s3outposts","keywords":["(aws-s3outposts)","(s3outposts)"],"labels":["@aws-cdk/aws-s3outposts"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-sagemaker","keywords":["aws-sagemaker","sagemaker"],"labels":["@aws-cdk/aws-sagemaker"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-sam","keywords":["serverless-application-model","aws-sam","sam"],"labels":["@aws-cdk/aws-sam"],"assignees":["njlynch"]},
@@ -199,7 +199,7 @@ jobs:
{"area":"@aws-cdk/aws-servicediscovery","keywords":["aws-servicediscovery","service-discovery"],"labels":["@aws-cdk/aws-servicediscovery"],"assignees":["madeline-k"]},
{"area":"@aws-cdk/aws-ses","keywords":["recipet-filter","reciept-rule","aws-ses","ses"],"labels":["@aws-cdk/aws-ses"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-ses-actions","keywords":["aws-ses-actions","ses-actions"],"labels":["@aws-cdk/aws-ses-actions"],"assignees":["otaviomacedo"]},
{"area":"@aws-cdk/aws-signer","keywords":["aws-signer","signer"],"labels":["@aws-cdk/aws-signer"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-signer","keywords":["aws-signer","signer"],"labels":["@aws-cdk/aws-signer"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-sns","keywords":["simple-notification-service","aws-sns","sns","topic"],"labels":["@aws-cdk/aws-sns"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-sns-subscriptions","keywords":["aws-sns-subscriptions","sns-subscriptions","subscription"],"labels":["@aws-cdk/aws-sns-subscriptions"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-sqs","keywords":["queue","simple-queue-service","aws-sqs","sqs","fifo"],"labels":["@aws-cdk/aws-sqs"],"assignees":["njlynch"]},
@@ -214,7 +214,7 @@ jobs:
{"area":"@aws-cdk/aws-wafregional","keywords":["wafregional","cfnwebacl"],"labels":["@aws-cdk/aws-wafregional"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-wafv2","keywords":["wafv2","aws-wafv2"],"labels":["@aws-cdk/aws-wafv2"],"assignees":["njlynch"]},
{"area":"@aws-cdk/aws-workspaces","keywords":["aws-workspaces","workspaces"],"labels":["@aws-cdk/aws-workspaces"],"assignees":["madeline-k"]},
{"area":"@aws-cdk/aws-xray","keywords":["(aws-xray)","(xray)"],"labels":["@aws-cdk/aws-xray"],"assignees":["nija-at"]},
{"area":"@aws-cdk/aws-xray","keywords":["(aws-xray)","(xray)"],"labels":["@aws-cdk/aws-xray"],"assignees":["corymhall"]},
{"area":"@aws-cdk/cfnspec","keywords":["cfn-spec"],"labels":["@aws-cdk/cfnspec"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/cloud-assembly-schema","keywords":["cloud-assembly-schema","manifest"],"labels":["@aws-cdk/cloud-assembly-schema"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/cloudformation-diff","keywords":["cloudformation-diff","cfn-diff"],"labels":["@aws-cdk/cloudformation-diff"],"assignees":["skinny85"]},
@@ -226,8 +226,8 @@ jobs:
{"area":"@aws-cdk/aws-lambda-layer-kubectl","keywords":["(aws-lambda-layer-kubectl)","(lambda-layer-kubectl)"],"labels":["@aws-cdk/aws-lambda-layer-kubectl"],"assignees":["eladb"]},
{"area":"@aws-cdk/pipelines","keywords":["pipelines","cdk-pipelines","sourceaction","synthaction"],"labels":["@aws-cdk/pipelines"],"assignees":["rix0rrr"]},
{"area":"@aws-cdk/region-info","keywords":["region-info","fact"],"labels":["@aws-cdk/region-info"],"assignees":["skinny85"]},
{"area":"aws-cdk-lib","keywords":["aws-cdk-lib","cdk-v2","v2","ubergen"],"labels":["aws-cdk-lib"],"assignees":["nija-at"]},
{"area":"monocdk","keywords":["monocdk","monocdk-experiment"],"labels":["monocdk"],"assignees":["nija-at"]},
{"area":"aws-cdk-lib","keywords":["aws-cdk-lib","cdk-v2","v2","ubergen"],"labels":["aws-cdk-lib"],"assignees":["njlynch"]},
{"area":"monocdk","keywords":["monocdk","monocdk-experiment"],"labels":["monocdk"],"assignees":["njlynch"]},
{"area":"@aws-cdk/yaml-cfn","keywords":["(aws-yaml-cfn)","(yaml-cfn)"],"labels":["@aws-cdk/aws-yaml-cfn"],"assignees":["skinny85"]},
{"area":"@aws-cdk/aws-apprunner","keywords":["apprunner","aws-apprunner"],"labels":["@aws-cdk/aws-apprunner"],"assignees":["corymhall"]},
{"area":"@aws-cdk/aws-lightsail","keywords":["lightsail","aws-lightsail"],"labels":["@aws-cdk/aws-lightsail"],"assignees":["corymhall"]},
4 changes: 2 additions & 2 deletions .github/workflows/yarn-upgrade.yml
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v2

- name: Set up Node
uses: actions/setup-node@v2.4.1
uses: actions/setup-node@v2.5.0
with:
node-version: 12

@@ -27,7 +27,7 @@ jobs:
run: echo "::set-output name=dir::$(yarn cache dir)"

- name: Restore Yarn cache
uses: actions/cache@v2.1.6
uses: actions/cache@v2.1.7
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
118 changes: 118 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -556,6 +556,12 @@ contain three slashes to achieve the same effect:
For a practical example of how making sample code compilable works, see the
`aws-ec2` package.

> ⚠️ NOTE: README files often contain code snippets that refer to modules that are consumers
> of the current module, and hence not present in the current module's dependency closure.
> Compilation of these snippets will fail if the module referenced has not been built.
> For the best experience when working on snippets, a full build of the CDK repo is required.
> However, it may be prudent to "build up" these modules as required.
#### Recommendations

In order to offer a consistent documentation style throughout the AWS CDK
11 changes: 0 additions & 11 deletions DEPRECATED_APIs.md
Original file line number Diff line number Diff line change
@@ -600,20 +600,10 @@
| @aws-cdk/aws-dynamodb | Table.​grantListStreams() | Use {@link #grantTableListStreams} for more granular permission |
| @aws-cdk/aws-dynamodb | Table.​metricSystemErrors() | use `metricSystemErrorsForOperations`. |
| @aws-cdk/aws-dynamodb | TableOptions.​serverSideEncryption | This property is deprecated. In order to obtain the same behavior as enabling this, set the `encryption` property to `TableEncryption.AWS_MANAGED` instead. |
| @aws-cdk/aws-rds | Credentials.​fromUsername() | use `fromGeneratedSecret()` or `fromPassword()` for new Clusters and Instances. Note that switching from `fromUsername()` to `fromGeneratedSecret()` or `fromPassword()` for already deployed Clusters or Instances will result in their replacement! |
| @aws-cdk/aws-rds | CredentialsFromUsernameOptions | supporting API `fromUsername()` has been deprecated. See deprecation notice of the API. |
| @aws-cdk/aws-rds | CredentialsFromUsernameOptions.​password | supporting API `fromUsername()` has been deprecated. See deprecation notice of the API. |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​MARIADB | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link mariaDb()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​MYSQL | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link mysql()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​ORACLE_​EE | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link oracleEe()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​ORACLE_​SE | instances can no longer be created with this engine. See https://forums.aws.amazon.com/ann.jspa?annID=7341 |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​ORACLE_​SE1 | instances can no longer be created with this engine. See https://forums.aws.amazon.com/ann.jspa?annID=7341 |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​ORACLE_​SE2 | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link oracleSe2()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​POSTGRES | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link postgres()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​SQL_​SERVER_​EE | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link sqlServerEe()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​SQL_​SERVER_​EX | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link sqlServerEx()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​SQL_​SERVER_​SE | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link sqlServerSe()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​SQL_​SERVER_​WEB | using unversioned engines is an availability risk. We recommend using versioned engines created using the {@link sqlServerWeb()} method |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​oracleSe() | instances can no longer be created with this engine. See https://forums.aws.amazon.com/ann.jspa?annID=7341 |
| @aws-cdk/aws-rds | DatabaseInstanceEngine.​oracleSe1() | instances can no longer be created with this engine. See https://forums.aws.amazon.com/ann.jspa?annID=7341 |
| @aws-cdk/aws-rds | DatabaseInstanceNewProps.​vpcPlacement | use `vpcSubnets` |
@@ -751,7 +741,6 @@
| @aws-cdk/aws-ecs | BaseService.​configureAwsVpcNetworking() | use configureAwsVpcNetworkingWithSecurityGroups instead. |
| @aws-cdk/aws-ecs | BaseServiceOptions.​propagateTaskTagsFrom | Use `propagateTags` instead. |
| @aws-cdk/aws-ecs | Cluster.​addAutoScalingGroup() | Use {@link Cluster.addAsgCapacityProvider} instead. |
| @aws-cdk/aws-ecs | Cluster.​addCapacity() | Use {@link Cluster.addAsgCapacityProvider} instead. |
| @aws-cdk/aws-ecs | Cluster.​addCapacityProvider() | Use {@link enableFargateCapacityProviders} instead. |
| @aws-cdk/aws-ecs | ClusterProps.​capacityProviders | Use {@link ClusterProps.enableFargateCapacityProviders} instead. |
| @aws-cdk/aws-ecs | Ec2ServiceProps.​securityGroup | use securityGroups instead. |
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -154,6 +154,7 @@ this capability, please see the

## More Resources
* [CDK Workshop](https://cdkworkshop.com/)
* [Construct Hub](https://constructs.dev) - Find and use open-source Cloud Development Kit (CDK) libraries
* **[CDK Construction Zone](https://www.twitch.tv/collections/9kCOGphNZBYVdA)** - A Twitch live coding series hosted by the CDK team, season one episodes:
* Triggers: Join us as we implement [Triggers](https://github.com/aws/aws-cdk-rfcs/issues/71), a Construct for configuring deploy time actions. Episodes 1-3:
* [S1E1](https://www.twitch.tv/videos/917691798): Triggers (part 1); **Participants:** @NetaNir, @eladb, @richardhboyd
11 changes: 0 additions & 11 deletions deprecated_apis.txt
Original file line number Diff line number Diff line change
@@ -599,17 +599,8 @@ constructs.Node#uniqueId
@aws-cdk/aws-rds.Credentials#fromUsername
@aws-cdk/aws-rds.CredentialsFromUsernameOptions
@aws-cdk/aws-rds.CredentialsFromUsernameOptions#password
@aws-cdk/aws-rds.DatabaseInstanceEngine#MARIADB
@aws-cdk/aws-rds.DatabaseInstanceEngine#MYSQL
@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_EE
@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_SE
@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_SE1
@aws-cdk/aws-rds.DatabaseInstanceEngine#ORACLE_SE2
@aws-cdk/aws-rds.DatabaseInstanceEngine#POSTGRES
@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_EE
@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_EX
@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_SE
@aws-cdk/aws-rds.DatabaseInstanceEngine#SQL_SERVER_WEB
@aws-cdk/aws-rds.DatabaseInstanceEngine#oracleSe
@aws-cdk/aws-rds.DatabaseInstanceEngine#oracleSe1
@aws-cdk/aws-rds.DatabaseInstanceNewProps#vpcPlacement
@@ -721,7 +712,6 @@ constructs.Node#uniqueId
@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_6
@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_8
@aws-cdk/aws-rds.PostgresEngineVersion#VER_9_6_9
@aws-cdk/aws-rds.SnapshotCredentials#fromGeneratedPassword
@aws-cdk/aws-rds.SqlServerEngineVersion#VER_15_00_4043_23_V1
@aws-cdk/aws-autoscaling.BlockDevice#mappingEnabled
@aws-cdk/aws-autoscaling.CommonAutoScalingGroupProps#notificationsTopic
@@ -747,7 +737,6 @@ constructs.Node#uniqueId
@aws-cdk/aws-ecs.BaseService#configureAwsVpcNetworking
@aws-cdk/aws-ecs.BaseServiceOptions#propagateTaskTagsFrom
@aws-cdk/aws-ecs.Cluster#addAutoScalingGroup
@aws-cdk/aws-ecs.Cluster#addCapacity
@aws-cdk/aws-ecs.Cluster#addCapacityProvider
@aws-cdk/aws-ecs.ClusterProps#capacityProviders
@aws-cdk/aws-ecs.Ec2ServiceProps#securityGroup
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -20,10 +20,10 @@
"fs-extra": "^9.1.0",
"graceful-fs": "^4.2.8",
"jest-junit": "^13.0.0",
"jsii-diff": "^1.46.0",
"jsii-pacmak": "^1.46.0",
"jsii-reflect": "^1.46.0",
"jsii-rosetta": "^1.46.0",
"jsii-diff": "^1.47.0",
"jsii-pacmak": "^1.47.0",
"jsii-reflect": "^1.47.0",
"jsii-rosetta": "^1.47.0",
"lerna": "^4.0.0",
"patch-package": "^6.4.7",
"standard-version": "^9.3.2",
35 changes: 33 additions & 2 deletions packages/@aws-cdk-containers/ecs-service-extensions/README.md
Original file line number Diff line number Diff line change
@@ -392,11 +392,42 @@ For setting up a topic-specific queue subscription, you can provide a custom que
```ts
nameDescription.add(new QueueExtension({
queue: myEventsQueue,
eventsQueue: myEventsQueue,
subscriptions: [new TopicSubscription({
topic: new sns.Topic(stack, 'my-topic'),
// `myTopicQueue` will subscribe to the `my-topic` instead of `eventsQueue`
queue: myTopicQueue,
topicSubscriptionQueue: {
queue: myTopicQueue,
},
}],
}));
```
### Configuring auto scaling based on SQS Queues
You can scale your service up or down to maintain an acceptable queue latency by tracking the backlog per task. It configures a target tracking scaling policy with target value (acceptable backlog per task) calculated by dividing the `acceptableLatency` by `messageProcessingTime`. For example, if the maximum acceptable latency for a message to be processed after its arrival in the SQS Queue is 10 mins and the average processing time for a task is 250 milliseconds per message, then `acceptableBacklogPerTask = 10 * 60 / 0.25 = 2400`. Therefore, each queue can hold up to 2400 messages before the service starts to scale up. For this, a target tracking policy will be attached to the scaling target for your service with target value `2400`. For more information, please refer: https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-using-sqs-queue.html .
You can configure auto scaling based on SQS Queue for your service as follows:
```ts
nameDescription.add(new QueueExtension({
eventsQueue: myEventsQueue,
// Need to specify `scaleOnLatency` to configure auto scaling based on SQS Queue
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(10),
messageProcessingTime: cdk.Duration.millis(250),
},
subscriptions: [new TopicSubscription({
topic: new sns.Topic(stack, 'my-topic'),
// `myTopicQueue` will subscribe to the `my-topic` instead of `eventsQueue`
topicSubscriptionQueue: {
queue: myTopicQueue,
// Optionally provide `scaleOnLatency` for configuring separate autoscaling for `myTopicQueue`
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(10),
messageProcessingTime: cdk.Duration.millis(250),
}
},
}],
}));
```
Original file line number Diff line number Diff line change
@@ -6,5 +6,5 @@ export * from './cloudwatch-agent';
export * from './scale-on-cpu-utilization';
export * from './xray';
export * from './assign-public-ip';
export * from './queue';
export * from './queue/queue';
export * from './injecter';

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './queue';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os
import boto3
from queue_backlog_calculator import QueueHandler

def queue_handler(event, context):
"""
Handler for the lambda trigger
"""

ecs = boto3.client('ecs')
sqs = boto3.client('sqs')

queue_handler = QueueHandler(ecs_client=ecs, sqs_client=sqs, environ=os.environ)

return queue_handler.emit()
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from math import ceil
import time
import json

class QueueHandler:
def __init__(self, ecs_client, sqs_client, environ):
self.ecs = ecs_client
self.sqs = sqs_client
self.cluster_name = environ['CLUSTER_NAME']
self.service_name = environ['SERVICE_NAME']
self.namespace = environ['NAMESPACE']
self.queue_names = environ['QUEUE_NAMES'].split(',')

def emit(self):
try:
running_count = self.get_running_task_count()
backlogs = [self.get_queue_backlog(queue_name, running_count) for queue_name in self.queue_names]
self.timestamp = int(time.time() * 1000)
for backlog in backlogs:
self.emit_backlog_per_task_metric(backlog['queueName'], backlog['backlogPerTask'])
except Exception as e:
Exception('Exception: {}'.format(e))

"""
Write the backlogPerTask metric to the stdout according to the Cloudwatch embedded metric format.
"""
def emit_backlog_per_task_metric(self, queue_name, backlog_per_task):
print(json.dumps({
"_aws": {
"Timestamp": self.timestamp,
"CloudWatchMetrics": [{
"Namespace": self.namespace,
"Dimensions": [["QueueName"]],
"Metrics": [{"Name":"BacklogPerTask", "Unit": "Count"}]
}],
},
"QueueName": queue_name,
"BacklogPerTask": backlog_per_task,
}))

"""
Get the number of tasks in the 'RUNNING' state for the service 'service_name'.
"""
def get_running_task_count(self):
service_desc = self.ecs.describe_services(
cluster=self.cluster_name,
services=[self.service_name],
)
if len(service_desc['services']) == 0:
raise Exception('There are no services with name {} in cluster: {}'.format(self.service_name, self.cluster_name))
return service_desc['services'][0].get('runningCount', 0)

"""
This method calculates and returns the backlogPerTask metric for the given queue.
"""
def get_queue_backlog(self, queue_name, count):
queue_url = self.sqs.get_queue_url(QueueName=queue_name)
running_count = 1 if count == 0 else count

def get_backlog_per_task():
queue_attributes = self.sqs.get_queue_attributes(
QueueUrl=queue_url['QueueUrl'],
AttributeNames=['ApproximateNumberOfMessages']
)
num_of_msgs = int(queue_attributes['Attributes'].get('ApproximateNumberOfMessages', 0))
return ceil(num_of_msgs/running_count)

return {
'queueName': queue_name,
'backlogPerTask': get_backlog_per_task()
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -37,19 +37,20 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@types/jest": "^27.0.2",
"@types/jest": "^27.0.3",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/aws-autoscaling": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"jest": "^27.3.1",
"jest": "^27.4.3",
"@aws-cdk/pkglint": "0.0.0",
"@aws-cdk/assert-internal": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-applicationautoscaling": "0.0.0",
"@aws-cdk/aws-appmesh": "0.0.0",
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-dynamodb": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-ecr": "0.0.0",
@@ -77,6 +78,7 @@
"@aws-cdk/aws-applicationautoscaling": "0.0.0",
"@aws-cdk/aws-appmesh": "0.0.0",
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-dynamodb": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-ecr": "0.0.0",
Original file line number Diff line number Diff line change
@@ -1190,7 +1190,7 @@
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3Bucket4C71F166"
"Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E"
},
"S3Key": {
"Fn::Join": [
@@ -1203,7 +1203,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3VersionKey0124EFC4"
"Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632"
}
]
}
@@ -1216,7 +1216,7 @@
"Fn::Split": [
"||",
{
"Ref": "AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3VersionKey0124EFC4"
"Ref": "AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632"
}
]
}
@@ -1266,17 +1266,17 @@
"Type": "String",
"Description": "Artifact hash for asset \"daeb79e3cee39c9b902dc0d5c780223e227ed573ea60976252947adab5fb2be1\""
},
"AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3Bucket4C71F166": {
"AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3BucketF482197E": {
"Type": "String",
"Description": "S3 bucket for asset \"1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2d\""
"Description": "S3 bucket for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\""
},
"AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dS3VersionKey0124EFC4": {
"AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2S3VersionKey38B69632": {
"Type": "String",
"Description": "S3 key for asset version \"1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2d\""
"Description": "S3 key for asset version \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\""
},
"AssetParameters1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2dArtifactHash6350D824": {
"AssetParameters6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2ArtifactHash4BE92B79": {
"Type": "String",
"Description": "Artifact hash for asset \"1c4eb88f5a8270f387281dcff6e3493840634113c4d57044f4aff74e3ef94c2d\""
"Description": "Artifact hash for asset \"6ee0a36dd10d630708c265bcf7616c64030040c1bbc383b34150db74b744cad2\""
}
},
"Outputs": {
Original file line number Diff line number Diff line change
@@ -95,15 +95,15 @@
"productionenvironmentvpcPublicSubnet1NATGateway6075E4CA": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089"
},
"AllocationId": {
"Fn::GetAtt": [
"productionenvironmentvpcPublicSubnet1EIP54BA88DB",
"AllocationId"
]
},
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089"
},
"Tags": [
{
"Key": "Name",
@@ -192,15 +192,15 @@
"productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31"
},
"AllocationId": {
"Fn::GetAtt": [
"productionenvironmentvpcPublicSubnet2EIP14CA46AA",
"AllocationId"
]
},
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31"
},
"Tags": [
{
"Key": "Name",
@@ -289,15 +289,15 @@
"productionenvironmentvpcPublicSubnet3NATGateway94604057": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D"
},
"AllocationId": {
"Fn::GetAtt": [
"productionenvironmentvpcPublicSubnet3EIP53405AED",
"AllocationId"
]
},
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D"
},
"Tags": [
{
"Key": "Name",
Original file line number Diff line number Diff line change
@@ -95,15 +95,15 @@
"productionenvironmentvpcPublicSubnet1NATGateway6075E4CA": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089"
},
"AllocationId": {
"Fn::GetAtt": [
"productionenvironmentvpcPublicSubnet1EIP54BA88DB",
"AllocationId"
]
},
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet1Subnet8D92C089"
},
"Tags": [
{
"Key": "Name",
@@ -192,15 +192,15 @@
"productionenvironmentvpcPublicSubnet2NATGatewayE1850FCC": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31"
},
"AllocationId": {
"Fn::GetAtt": [
"productionenvironmentvpcPublicSubnet2EIP14CA46AA",
"AllocationId"
]
},
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet2Subnet298E6C31"
},
"Tags": [
{
"Key": "Name",
@@ -289,15 +289,15 @@
"productionenvironmentvpcPublicSubnet3NATGateway94604057": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D"
},
"AllocationId": {
"Fn::GetAtt": [
"productionenvironmentvpcPublicSubnet3EIP53405AED",
"AllocationId"
]
},
"SubnetId": {
"Ref": "productionenvironmentvpcPublicSubnet3SubnetC7B5665D"
},
"Tags": [
{
"Key": "Name",
@@ -621,15 +621,15 @@
"developmentenvironmentvpcPublicSubnet1NATGateway6B01CC4E": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "developmentenvironmentvpcPublicSubnet1Subnet0D18046B"
},
"AllocationId": {
"Fn::GetAtt": [
"developmentenvironmentvpcPublicSubnet1EIPE8B9F4D4",
"AllocationId"
]
},
"SubnetId": {
"Ref": "developmentenvironmentvpcPublicSubnet1Subnet0D18046B"
},
"Tags": [
{
"Key": "Name",
@@ -718,15 +718,15 @@
"developmentenvironmentvpcPublicSubnet2NATGateway15A9C252": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "developmentenvironmentvpcPublicSubnet2SubnetC2026CD3"
},
"AllocationId": {
"Fn::GetAtt": [
"developmentenvironmentvpcPublicSubnet2EIP560C2BBA",
"AllocationId"
]
},
"SubnetId": {
"Ref": "developmentenvironmentvpcPublicSubnet2SubnetC2026CD3"
},
"Tags": [
{
"Key": "Name",
@@ -815,15 +815,15 @@
"developmentenvironmentvpcPublicSubnet3NATGatewayAD65DE0B": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"SubnetId": {
"Ref": "developmentenvironmentvpcPublicSubnet3Subnet00B8A77C"
},
"AllocationId": {
"Fn::GetAtt": [
"developmentenvironmentvpcPublicSubnet3EIPE2E366D3",
"AllocationId"
]
},
"SubnetId": {
"Ref": "developmentenvironmentvpcPublicSubnet3Subnet00B8A77C"
},
"Tags": [
{
"Key": "Name",
Original file line number Diff line number Diff line change
@@ -944,7 +944,6 @@
"MaximumPercent": 200,
"MinimumHealthyPercent": 100
},
"DesiredCount": 1,
"EnableECSManagedTags": false,
"LaunchType": "FARGATE",
"NetworkConfiguration": {
@@ -991,6 +990,353 @@
"Ref": "productionenvironmentvpcAEB47DF7"
}
}
},
"WorkerserviceTaskCountTarget6636D808": {
"Type": "AWS::ApplicationAutoScaling::ScalableTarget",
"Properties": {
"MaxCapacity": 10,
"MinCapacity": 1,
"ResourceId": {
"Fn::Join": [
"",
[
"service/",
{
"Ref": "productionenvironmentclusterC6599D2D"
},
"/",
{
"Fn::GetAtt": [
"WorkerserviceService68C5A5C3",
"Name"
]
}
]
]
},
"RoleARN": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::",
{
"Ref": "AWS::AccountId"
},
":role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService"
]
]
},
"ScalableDimension": "ecs:service:DesiredCount",
"ServiceNamespace": "ecs"
}
},
"WorkerserviceTaskCountTargetEventsQueueautoscalingpolicyD12B62ED": {
"Type": "AWS::ApplicationAutoScaling::ScalingPolicy",
"Properties": {
"PolicyName": "awsecsintegWorkerserviceTaskCountTargetEventsQueueautoscalingpolicyDBD40B57",
"PolicyType": "TargetTrackingScaling",
"ScalingTargetId": {
"Ref": "WorkerserviceTaskCountTarget6636D808"
},
"TargetTrackingScalingPolicyConfiguration": {
"CustomizedMetricSpecification": {
"Dimensions": [
{
"Name": "QueueName",
"Value": {
"Fn::GetAtt": [
"EventsQueueB96EB0D2",
"QueueName"
]
}
}
],
"MetricName": "BacklogPerTask",
"Namespace": "production-Worker",
"Statistic": "Average",
"Unit": "Count"
},
"TargetValue": 15
}
}
},
"WorkerserviceTaskCountTargetsignupqueueautoscalingpolicyB7321DB7": {
"Type": "AWS::ApplicationAutoScaling::ScalingPolicy",
"Properties": {
"PolicyName": "awsecsintegWorkerserviceTaskCountTargetsignupqueueautoscalingpolicyDF93FC37",
"PolicyType": "TargetTrackingScaling",
"ScalingTargetId": {
"Ref": "WorkerserviceTaskCountTarget6636D808"
},
"TargetTrackingScalingPolicyConfiguration": {
"CustomizedMetricSpecification": {
"Dimensions": [
{
"Name": "QueueName",
"Value": {
"Fn::GetAtt": [
"signupqueue33AFF2E6",
"QueueName"
]
}
}
],
"MetricName": "BacklogPerTask",
"Namespace": "production-Worker",
"Statistic": "Average",
"Unit": "Count"
},
"TargetValue": 30
}
}
},
"BackLogPerTaskCalculatorFunctionServiceRoleEFA723A4": {
"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"
]
]
}
]
}
},
"BackLogPerTaskCalculatorFunctionServiceRoleDefaultPolicyB6B10266": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "ecs:DescribeServices",
"Condition": {
"ArnEquals": {
"ecs:cluster": {
"Fn::GetAtt": [
"productionenvironmentclusterC6599D2D",
"Arn"
]
}
}
},
"Effect": "Allow",
"Resource": {
"Ref": "WorkerserviceService68C5A5C3"
}
},
{
"Action": [
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"EventsQueueB96EB0D2",
"Arn"
]
},
{
"Fn::GetAtt": [
"signupqueue33AFF2E6",
"Arn"
]
}
]
}
],
"Version": "2012-10-17"
},
"PolicyName": "BackLogPerTaskCalculatorFunctionServiceRoleDefaultPolicyB6B10266",
"Roles": [
{
"Ref": "BackLogPerTaskCalculatorFunctionServiceRoleEFA723A4"
}
]
}
},
"BackLogPerTaskCalculatorFunction95AA21D5": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameterscc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19dS3Bucket151170D5"
},
"S3Key": {
"Fn::Join": [
"",
[
{
"Fn::Select": [
0,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParameterscc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19dS3VersionKey3D692C3D"
}
]
}
]
},
{
"Fn::Select": [
1,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParameterscc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19dS3VersionKey3D692C3D"
}
]
}
]
}
]
]
}
},
"Role": {
"Fn::GetAtt": [
"BackLogPerTaskCalculatorFunctionServiceRoleEFA723A4",
"Arn"
]
},
"Environment": {
"Variables": {
"CLUSTER_NAME": {
"Ref": "productionenvironmentclusterC6599D2D"
},
"SERVICE_NAME": {
"Fn::GetAtt": [
"WorkerserviceService68C5A5C3",
"Name"
]
},
"NAMESPACE": "production-Worker",
"QUEUE_NAMES": {
"Fn::Join": [
"",
[
{
"Fn::GetAtt": [
"EventsQueueB96EB0D2",
"QueueName"
]
},
",",
{
"Fn::GetAtt": [
"signupqueue33AFF2E6",
"QueueName"
]
}
]
]
}
}
},
"Handler": "index.queue_handler",
"Runtime": "python3.9"
},
"DependsOn": [
"BackLogPerTaskCalculatorFunctionServiceRoleDefaultPolicyB6B10266",
"BackLogPerTaskCalculatorFunctionServiceRoleEFA723A4"
]
},
"BacklogPerTaskScheduledRuleB871DD15": {
"Type": "AWS::Events::Rule",
"Properties": {
"ScheduleExpression": "rate(1 minute)",
"State": "ENABLED",
"Targets": [
{
"Arn": {
"Fn::GetAtt": [
"BackLogPerTaskCalculatorFunction95AA21D5",
"Arn"
]
},
"Id": "Target0"
}
]
}
},
"BacklogPerTaskScheduledRuleAllowEventRuleawsecsintegBackLogPerTaskCalculatorFunctionEB2B91C7CCD725BB": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"BackLogPerTaskCalculatorFunction95AA21D5",
"Arn"
]
},
"Principal": "events.amazonaws.com",
"SourceArn": {
"Fn::GetAtt": [
"BacklogPerTaskScheduledRuleB871DD15",
"Arn"
]
}
}
},
"WorkerBackLogPerTaskCalculatorLogsA4B5AF42": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"LogGroupName": {
"Fn::Join": [
"",
[
"/aws/lambda/",
{
"Ref": "BackLogPerTaskCalculatorFunction95AA21D5"
}
]
]
},
"RetentionInDays": 3
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
}
},
"Parameters": {
"AssetParameterscc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19dS3Bucket151170D5": {
"Type": "String",
"Description": "S3 bucket for asset \"cc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19d\""
},
"AssetParameterscc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19dS3VersionKey3D692C3D": {
"Type": "String",
"Description": "S3 key for asset version \"cc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19d\""
},
"AssetParameterscc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19dArtifactHash167B1C30": {
"Type": "String",
"Description": "Artifact hash for asset \"cc8d03e1cef62b38b47438d429cdc3828f57a52cffd1a84c4cda032bc21be19d\""
}
}
}
Original file line number Diff line number Diff line change
@@ -53,17 +53,30 @@ subServiceDescription.add(new Container({

const topicSubscription1 = new TopicSubscription({
topic: topic1.topic,
queue: new sqs.Queue(stack, 'sign-up-queue'),
topicSubscriptionQueue: {
queue: new sqs.Queue(stack, 'sign-up-queue'),
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(10),
messageProcessingTime: cdk.Duration.seconds(20),
},
},
});
const topicSubscription2 = new TopicSubscription({
topic: topic2.topic,
});

subServiceDescription.add(new QueueExtension({
subscriptions: [topicSubscription1, topicSubscription2],
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(5),
messageProcessingTime: cdk.Duration.seconds(20),
},
}));

new Service(stack, 'Worker', {
environment: environment,
serviceDescription: subServiceDescription,
autoScaleTaskCount: {
maxTaskCount: 10,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM public.ecr.aws/lambda/python:latest

ADD . /opt/lambda
WORKDIR /opt/lambda

RUN pip3 install boto3
RUN python3 test_index.py

ENTRYPOINT [ "/bin/bash" ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
#---------------------------------------------------------------------------------------------------
# executes unit tests
#
# prepares a staging directory with the requirements
set -e
script_dir=$(cd $(dirname $0) && pwd)

# prepare staging directory
staging=$(mktemp -d)
mkdir -p ${staging}
cd ${staging}

# copy src and overlay with test
cp ${script_dir}/../../lib/extensions/queue/lambda/queue_backlog_calculator.py $PWD
cp ${script_dir}/test_index.py $PWD
cp ${script_dir}/Dockerfile $PWD

docker build .
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import json
import io
import unittest
import unittest.mock as mock
import time
from botocore.exceptions import ClientError
from queue_backlog_calculator import QueueHandler

mock_time = time.time()

class TestQueueAutoscaling(unittest.TestCase):
maxDiff = None
'''
Test for unexpected error.
'''
def test_unexpected_error(self):
ecs_client = mock.Mock()
ecs_client.describe_services.side_effect = ClientError({'Error': {'Code': 'UnexpectedError'}}, 'DescribeServices')

sqs_client = mock.Mock()
environ = {
'CLUSTER_NAME': 'TEST_CLUSTER',
'SERVICE_NAME': 'TEST_SERVICE',
'NAMESPACE': 'TEST',
'QUEUE_NAMES': 'queue1'
}

queue_handler = QueueHandler(ecs_client, sqs_client, environ)
queue_handler.emit()

self.assertRaisesRegex(Exception, r'UnexpectedError')

'''
Test for Exception when the service doesn't exist in cluster.
'''
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_no_services_in_cluster(self, _):
ecs_client = mock.Mock()
ecs_client.describe_services.return_value = {'services': []}

sqs_client = mock.Mock()
environ = {
'CLUSTER_NAME': 'TEST_CLUSTER',
'SERVICE_NAME': 'TEST_SERVICE',
'NAMESPACE': 'TEST',
'QUEUE_NAMES': 'queue1'
}

queue_handler = QueueHandler(ecs_client, sqs_client, environ)
queue_handler.emit()

self.assertRaisesRegex(Exception, r'There are no services with name {} in cluster: {}'.format(environ['SERVICE_NAME'], environ['CLUSTER_NAME']))

'''
Test 'backPerTask' value is equal to 'ApproximateNumberOfMessages' in the queue when no tasks are running.
'''
@mock.patch('time.time', mock.MagicMock(return_value=mock_time))
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_backlog_with_no_running_tasks(self, mock_stdout):
ecs_client = mock.Mock()
ecs_client.describe_services.return_value = {'services': [{'runningCount': 0}]}

sqs_client = mock.Mock()
sqs_client.get_queue_url.return_value = {'QueueUrl': 'queue1_url'}
sqs_client.get_queue_attributes.return_value = {'Attributes': {'ApproximateNumberOfMessages':100}}
environ = {
'CLUSTER_NAME': 'TEST_CLUSTER',
'SERVICE_NAME': 'TEST_SERVICE',
'NAMESPACE': 'TEST',
'QUEUE_NAMES': 'queue1'
}

queue_handler = QueueHandler(ecs_client, sqs_client, environ)
queue_handler.emit()

metric = json.dumps({
"_aws": {
"Timestamp": int(mock_time*1000),
"CloudWatchMetrics": [{
"Namespace": "TEST",
"Dimensions": [["QueueName"]],
"Metrics": [{"Name":"BacklogPerTask", "Unit": "Count"}]
}],
},
"QueueName": "queue1",
"BacklogPerTask": 100,
})
self.assertEqual(mock_stdout.getvalue(), metric+'\n')

'''
Test 'backPerTask' metric is generated correctly for each queue.
'''
@mock.patch('time.time', mock.MagicMock(return_value=mock_time))
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_metric_generation_per_queue(self, mock_stdout):
ecs_client = mock.Mock()
ecs_client.describe_services.return_value = {'services': [{'runningCount': 2}]}

val1 = {
'queue1': {'QueueUrl': 'queue1_url'},
'queue2': {'QueueUrl': 'queue2_url'}
}
val2 = {
'queue1_url': {'Attributes': {'ApproximateNumberOfMessages':101}},
'queue2_url': {'Attributes': {'ApproximateNumberOfMessages':200}}
}

sqs_client = mock.Mock()
sqs_client.get_queue_url.side_effect = [val1['queue1'], val1['queue2']]
sqs_client.get_queue_attributes.side_effect = [val2['queue1_url'], val2['queue2_url']]
environ = {
'CLUSTER_NAME': 'TEST_CLUSTER',
'SERVICE_NAME': 'TEST_SERVICE',
'NAMESPACE': 'TEST',
'QUEUE_NAMES': 'queue1,queue2'
}

queue_handler = QueueHandler(ecs_client, sqs_client, environ)
queue_handler.emit()

metric1 = json.dumps({
"_aws": {
"Timestamp": int(mock_time*1000),
"CloudWatchMetrics": [{
"Namespace": "TEST",
"Dimensions": [["QueueName"]],
"Metrics": [{"Name":"BacklogPerTask", "Unit": "Count"}]
}],
},
"QueueName": "queue1",
"BacklogPerTask": 51,
})
metric2 = json.dumps({
"_aws": {
"Timestamp": int(mock_time*1000),
"CloudWatchMetrics": [{
"Namespace": "TEST",
"Dimensions": [["QueueName"]],
"Metrics": [{"Name":"BacklogPerTask", "Unit": "Count"}]
}],
},
"QueueName": "queue2",
"BacklogPerTask": 100,
})

self.assertEqual(mock_stdout.getvalue(), metric1+'\n'+metric2+'\n')

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { spawnSync } from 'child_process';
import * as path from 'path';

test('queue handler', () => {
const testScript = path.join(__dirname, 'queue-handler', 'test.sh');
const result = spawnSync(testScript, { stdio: 'inherit' });
expect(result.status).toBe(0);
});
Original file line number Diff line number Diff line change
@@ -327,7 +327,9 @@ describe('queue', () => {

const topicSubscription1 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic1'),
queue: new sqs.Queue(stack, 'myQueue'),
topicSubscriptionQueue: {
queue: new sqs.Queue(stack, 'myQueue'),
},
});
const topicSubscription2 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic2'),
@@ -501,7 +503,337 @@ describe('queue', () => {
},
],
});
});

test('should error when providing both the subscriptionQueue and queue (deprecated) props for a topic subscription', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const serviceDescription = new ServiceDescription();

serviceDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));

// THEN
expect(() => {
new TopicSubscription({
topic: new sns.Topic(stack, 'topic1'),
queue: new sqs.Queue(stack, 'delete-queue'),
topicSubscriptionQueue: {
queue: new sqs.Queue(stack, 'sign-up-queue'),
},
});
}).toThrow('Either provide the `subscriptionQueue` or the `queue` (deprecated) for the topic subscription, but not both.');
});

test('should be able to add target tracking scaling policy for the Events Queue with no subscriptions', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const environment = new Environment(stack, 'production');
const serviceDescription = new ServiceDescription();

serviceDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));

serviceDescription.add(new QueueExtension({
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(5),
messageProcessingTime: cdk.Duration.seconds(20),
},
}));

new Service(stack, 'my-service', {
environment,
serviceDescription,
autoScaleTaskCount: {
maxTaskCount: 10,
},
});

// THEN
expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', {
MaxCapacity: 10,
MinCapacity: 1,
});

expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', {
PolicyType: 'TargetTrackingScaling',
TargetTrackingScalingPolicyConfiguration: {
CustomizedMetricSpecification: {
Dimensions: [
{
Name: 'QueueName',
Value: {
'Fn::GetAtt': [
'EventsQueueB96EB0D2',
'QueueName',
],
},
},
],
MetricName: 'BacklogPerTask',
Namespace: 'production-my-service',
Statistic: 'Average',
Unit: 'Count',
},
TargetValue: 15,
},
});
});

test('should be able to add target tracking scaling policy for the SQS Queues', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const environment = new Environment(stack, 'production');
const serviceDescription = new ServiceDescription();

serviceDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));

const topicSubscription1 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic1'),
topicSubscriptionQueue: {
queue: new sqs.Queue(stack, 'myQueue'),
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(10),
messageProcessingTime: cdk.Duration.seconds(20),
},
},
});
const topicSubscription2 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic2'),
queue: new sqs.Queue(stack, 'tempQueue'),
});
serviceDescription.add(new QueueExtension({
subscriptions: [topicSubscription1, topicSubscription2],
eventsQueue: new sqs.Queue(stack, 'defQueue'),
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(5),
messageProcessingTime: cdk.Duration.seconds(20),
},
}));

new Service(stack, 'my-service', {
environment,
serviceDescription,
autoScaleTaskCount: {
maxTaskCount: 10,
},
});

// THEN
expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalableTarget', {
MaxCapacity: 10,
MinCapacity: 1,
});

expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', {
PolicyType: 'TargetTrackingScaling',
TargetTrackingScalingPolicyConfiguration: {
CustomizedMetricSpecification: {
Dimensions: [
{
Name: 'QueueName',
Value: {
'Fn::GetAtt': [
'defQueue1F91A65B',
'QueueName',
],
},
},
],
MetricName: 'BacklogPerTask',
Namespace: 'production-my-service',
Statistic: 'Average',
Unit: 'Count',
},
TargetValue: 15,
},
});

expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', {
PolicyType: 'TargetTrackingScaling',
TargetTrackingScalingPolicyConfiguration: {
CustomizedMetricSpecification: {
Dimensions: [
{
Name: 'QueueName',
Value: {
'Fn::GetAtt': [
'myQueue4FDFF71C',
'QueueName',
],
},
},
],
MetricName: 'BacklogPerTask',
Namespace: 'production-my-service',
Statistic: 'Average',
Unit: 'Count',
},
TargetValue: 30,
},
});

expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', {
PolicyType: 'TargetTrackingScaling',
TargetTrackingScalingPolicyConfiguration: {
CustomizedMetricSpecification: {
Dimensions: [
{
Name: 'QueueName',
Value: {
'Fn::GetAtt': [
'tempQueueEF946882',
'QueueName',
],
},
},
],
MetricName: 'BacklogPerTask',
Namespace: 'production-my-service',
Statistic: 'Average',
Unit: 'Count',
},
TargetValue: 15,
},
});
});

test('should error when adding scaling policy if scaling target has not been configured', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const environment = new Environment(stack, 'production');
const serviceDescription = new ServiceDescription();

serviceDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));

const topicSubscription1 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic1'),
});

serviceDescription.add(new QueueExtension({
subscriptions: [topicSubscription1],
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(10),
messageProcessingTime: cdk.Duration.seconds(20),
},
}));

// THEN
expect(() => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
});
}).toThrow(/Auto scaling target for the service 'my-service' hasn't been configured. Please use Service construct to configure 'minTaskCount' and 'maxTaskCount'./);
});

test('should error when message processing time for the queue is greater than acceptable latency', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const environment = new Environment(stack, 'production');
const serviceDescription = new ServiceDescription();

serviceDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));

const topicSubscription1 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic1'),
topicSubscriptionQueue: {
queue: new sqs.Queue(stack, 'sign-up-queue'),
},
});

serviceDescription.add(new QueueExtension({
subscriptions: [topicSubscription1],
scaleOnLatency: {
acceptableLatency: cdk.Duration.seconds(10),
messageProcessingTime: cdk.Duration.seconds(20),
},
}));

// THEN
expect(() => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
autoScaleTaskCount: {
maxTaskCount: 10,
},
});
}).toThrow('Message processing time (20s) for the queue cannot be greater acceptable queue latency (10s).');
});

test('should error when configuring auto scaling only for topic-specific queue', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const environment = new Environment(stack, 'production');
const serviceDescription = new ServiceDescription();

serviceDescription.add(new Container({
cpu: 256,
memoryMiB: 512,
trafficPort: 80,
image: ecs.ContainerImage.fromRegistry('nathanpeck/name'),
}));

const topicSubscription1 = new TopicSubscription({
topic: new sns.Topic(stack, 'topic1'),
topicSubscriptionQueue: {
queue: new sqs.Queue(stack, 'sign-up-queue'),
scaleOnLatency: {
acceptableLatency: cdk.Duration.minutes(10),
messageProcessingTime: cdk.Duration.seconds(20),
},
},
});

serviceDescription.add(new QueueExtension({
subscriptions: [topicSubscription1],
}));

// THEN
expect(() => {
new Service(stack, 'my-service', {
environment,
serviceDescription,
autoScaleTaskCount: {
maxTaskCount: 10,
},
});
}).toThrow(/Autoscaling for a topic-specific queue cannot be configured as autoscaling based on SQS Queues hasnt been set up for the service 'my-service'. If you want to enable autoscaling for this service, please also specify 'scaleOnLatency' in the 'QueueExtension'/);
});
});
17 changes: 15 additions & 2 deletions packages/@aws-cdk/alexa-ask/README.md
Original file line number Diff line number Diff line change
@@ -13,6 +13,19 @@

<!--END STABILITY BANNER-->

```ts
import * as alexaAsk from '@aws-cdk/alexa-ask';
This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.

```ts nofixture
import * as alexa_ask from '@aws-cdk/alexa-ask';
```

<!--BEGIN CFNONLY DISCLAIMER-->

There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet.
However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly.

For more information on the resources and properties available for this service, see the [CloudFormation documentation for Alexa::ASK](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Alexa_ASK.html).

(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) if you are interested in contributing to this construct library.)

<!--END CFNONLY DISCLAIMER-->
2 changes: 1 addition & 1 deletion packages/@aws-cdk/alexa-ask/package.json
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2"
"@types/jest": "^27.0.3"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
6 changes: 3 additions & 3 deletions packages/@aws-cdk/app-delivery/package.json
Original file line number Diff line number Diff line change
@@ -65,9 +65,9 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2",
"fast-check": "^2.19.0",
"jest": "^27.3.1"
"@types/jest": "^27.0.3",
"fast-check": "^2.20.0",
"jest": "^27.4.3"
},
"repository": {
"type": "git",
2 changes: 1 addition & 1 deletion packages/@aws-cdk/assert-internal/README.md
Original file line number Diff line number Diff line change
@@ -103,7 +103,7 @@ The following matchers exist:
back to exact value matching.
- `arrayWith(E, [F, ...])` - value must be an array containing the given elements (or matchers) in any order.
- `stringLike(S)` - value must be a string matching `S`. `S` may contain `*` as wildcard to match any number
of characters.
of characters. Multiline strings are supported.
- `anything()` - matches any value.
- `notMatching(M)` - any value that does NOT match the given matcher (or exact value) given.
- `encodedJson(M)` - value must be a string which, when decoded as JSON, matches the given matcher or
Original file line number Diff line number Diff line change
@@ -239,11 +239,11 @@ function isCallable(x: any): x is ((...args: any[]) => any) {
}

/**
* Do a glob-like pattern match (which only supports *s)
* Do a glob-like pattern match (which only supports *s). Supports multiline strings.
*/
export function stringLike(pattern: string): PropertyMatcher {
// Replace * with .* in the string, escape the rest and brace with ^...$
const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`);
const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`, 'm');

return annotateMatcher({ $stringContaining: pattern }, (value: any, failure: InspectionFailure) => {
if (typeof value !== 'string') {
8 changes: 4 additions & 4 deletions packages/@aws-cdk/assert-internal/package.json
Original file line number Diff line number Diff line change
@@ -26,9 +26,9 @@
"devDependencies": {
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2",
"jest": "^27.3.1",
"ts-jest": "^27.0.7"
"@types/jest": "^27.0.3",
"jest": "^27.4.3",
"ts-jest": "^27.1.1"
},
"dependencies": {
"@aws-cdk/cloud-assembly-schema": "0.0.0",
@@ -40,7 +40,7 @@
"peerDependencies": {
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69",
"jest": "^27.3.1"
"jest": "^27.4.3"
},
"repository": {
"url": "https://github.com/aws/aws-cdk.git",
36 changes: 35 additions & 1 deletion packages/@aws-cdk/assert-internal/test/have-resource.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { ABSENT, arrayWith, exactValue, expect as cdkExpect, haveResource, haveResourceLike, Capture, anything } from '../lib/index';
import {
ABSENT,
arrayWith,
exactValue,
expect as cdkExpect,
haveResource,
haveResourceLike,
Capture,
anything,
stringLike,
} from '../lib/index';
import { mkResource, mkStack } from './cloud-artifact';

test('support resource with no properties', () => {
@@ -156,6 +166,30 @@ describe('property absence', () => {
}).toThrowError(/Array did not contain expected element/);
});

test('can use matcher to test stringLike on single-line strings', () => {
const synthStack = mkResource({
Content: 'something required something',
});

expect(() => {
cdkExpect(synthStack).to(haveResource('Some::Resource', {
Content: stringLike('*required*'),
}));
}).not.toThrowError();
});

test('can use matcher to test stringLike on multi-line strings', () => {
const synthStack = mkResource({
Content: 'something\nrequired\nsomething',
});

expect(() => {
cdkExpect(synthStack).to(haveResource('Some::Resource', {
Content: stringLike('*required*'),
}));
}).not.toThrowError();
});

test('arrayContaining must match all elements in any order', () => {
const synthStack = mkResource({
List: ['a', 'b'],
6 changes: 3 additions & 3 deletions packages/@aws-cdk/assert/package.json
Original file line number Diff line number Diff line change
@@ -37,11 +37,11 @@
"@aws-cdk/assert-internal": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2",
"@types/jest": "^27.0.3",
"aws-cdk-migration": "0.0.0",
"constructs": "^3.3.69",
"jest": "^27.3.1",
"ts-jest": "^27.0.7"
"ts-jest": "^27.1.1"
},
"dependencies": {
"@aws-cdk/cloudformation-diff": "0.0.0",
@@ -74,6 +74,6 @@
"stability": "experimental",
"maturity": "developer-preview",
"publishConfig": {
"tag": "latest"
"tag": "latest-1"
}
}
157 changes: 100 additions & 57 deletions packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ The simplest assertion would be to assert that the template matches a given
template.

```ts
const expected = {
template.templateMatches({
Resources: {
BarLogicalId: {
Type: 'Foo::Bar',
@@ -46,9 +46,7 @@ const expected = {
},
},
},
};

template.templateMatches(expected);
});
```

By default, the `templateMatches()` API will use the an 'object-like' comparison,
@@ -84,23 +82,21 @@ The following code asserts that the `Properties` section of a resource of type
`Foo::Bar` contains the specified properties -

```ts
const expected = {
template.hasResourceProperties('Foo::Bar', {
Foo: 'Bar',
Baz: 5,
Qux: [ 'Waldo', 'Fred' ],
};
template.hasResourceProperties('Foo::Bar', expected);
});
```

Alternatively, if you would like to assert the entire resource definition, you
can use the `hasResource()` API.

```ts
const expected = {
template.hasResource('Foo::Bar', {
Properties: { Foo: 'Bar' },
DependsOn: [ 'Waldo', 'Fred' ],
};
template.hasResource('Foo::Bar', expected);
});
```

Beyond assertions, the module provides APIs to retrieve matching resources.
@@ -128,21 +124,17 @@ template.hasOutput('Foo', expected);
If you want to match against all Outputs in the template, use `*` as the `logicalId`.

```ts
const expected = {
template.hasOutput('*', {
Value: 'Bar',
Export: { Name: 'ExportBaz' },
};
template.hasOutput('*', expected);
});
```

`findOutputs()` will return a set of outputs that match the `logicalId` and `props`,
and you can use the `'*'` special case as well.

```ts
const expected = {
Value: 'Fred',
};
const result = template.findOutputs('*', expected);
const result = template.findOutputs('*', { Value: 'Fred' });
expect(result.Foo).toEqual({ Value: 'Fred', Description: 'FooFred' });
expect(result.Bar).toEqual({ Value: 'Fred', Description: 'BarFred' });
```
@@ -182,20 +174,18 @@ level, the list of keys in the target is a subset of the provided pattern.
// }

// The following will NOT throw an assertion error
const expected = {
template.hasResourceProperties('Foo::Bar', {
Fred: Match.objectLike({
Wobble: 'Flob',
}),
};
template.hasResourceProperties('Foo::Bar', expected);
});

// The following will throw an assertion error
const unexpected = {
template.hasResourceProperties('Foo::Bar', {
Fred: Match.objectLike({
Brew: 'Coffee',
}),
}
template.hasResourceProperties('Foo::Bar', unexpected);
});
```

The `Match.objectEquals()` API can be used to assert a target as a deep exact
@@ -223,20 +213,18 @@ or outside of any matchers.
// }

// The following will NOT throw an assertion error
const expected = {
template.hasResourceProperties('Foo::Bar', {
Fred: Match.objectLike({
Bob: Match.absent(),
}),
};
template.hasResourceProperties('Foo::Bar', expected);
});

// The following will throw an assertion error
const unexpected = {
template.hasResourceProperties('Foo::Bar', {
Fred: Match.objectLike({
Wobble: Match.absent(),
}),
};
template.hasResourceProperties('Foo::Bar', unexpected);
});
```

The `Match.anyValue()` matcher can be used to specify that a specific value should be found
@@ -261,20 +249,18 @@ This matcher can be combined with any of the other matchers.
// }

// The following will NOT throw an assertion error
const expected = {
template.hasResourceProperties('Foo::Bar', {
Fred: {
Wobble: [Match.anyValue(), "Flip"],
Wobble: [ Match.anyValue(), "Flip" ],
},
};
template.hasResourceProperties('Foo::Bar', expected);
});

// The following will throw an assertion error
const unexpected = {
template.hasResourceProperties('Foo::Bar', {
Fred: {
Wimble: Match.anyValue(),
},
};
template.hasResourceProperties('Foo::Bar', unexpected);
});
```

### Array Matchers
@@ -297,16 +283,14 @@ This API will perform subset match on the target.
// }

// The following will NOT throw an assertion error
const expected = {
template.hasResourceProperties('Foo::Bar', {
Fred: Match.arrayWith(['Flob']),
};
template.hasResourceProperties('Foo::Bar', expected);
});

// The following will throw an assertion error
const unexpected = Match.objectLike({
template.hasResourceProperties('Foo::Bar', Match.objectLike({
Fred: Match.arrayWith(['Wobble']),
});
template.hasResourceProperties('Foo::Bar', unexpected);
}));
```

*Note:* The list of items in the pattern array should be in order as they appear in the
@@ -334,16 +318,14 @@ not match the pattern specified.
// }

// The following will NOT throw an assertion error
const expected = {
template.hasResourceProperties('Foo::Bar', {
Fred: Match.not(['Flob']),
};
template.hasResourceProperties('Foo::Bar', expected);
});

// The following will throw an assertion error
const unexpected = Match.objectLike({
template.hasResourceProperties('Foo::Bar', Match.objectLike({
Fred: Match.not(['Flob', 'Cat']),
});
template.hasResourceProperties('Foo::Bar', unexpected);
}));
```

### Serialized JSON
@@ -370,20 +352,18 @@ The `Match.serializedJson()` matcher allows deep matching within a stringified J
// }

// The following will NOT throw an assertion error
const expected = {
template.hasResourceProperties('Foo::Bar', {
Baz: Match.serializedJson({
Fred: Match.arrayWith(["Waldo"]),
}),
};
template.hasResourceProperties('Foo::Bar', expected);
});

// The following will throw an assertion error
const unexpected = {
template.hasResourceProperties('Foo::Bar', {
Baz: Match.serializedJson({
Fred: ["Waldo", "Johnny"],
}),
};
template.hasResourceProperties('Foo::Bar', unexpected);
});
```

[Pipeline BuildSpec]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-source.html#cfn-codebuild-project-source-buildspec
@@ -411,12 +391,75 @@ matching resource.

const fredCapture = new Capture();
const waldoCapture = new Capture();
const expected = {
template.hasResourceProperties('Foo::Bar', {
Fred: fredCapture,
Waldo: ["Qix", waldoCapture],
}
template.hasResourceProperties('Foo::Bar', expected);
});

fredCapture.asArray(); // returns ["Flob", "Cat"]
waldoCapture.asString(); // returns "Qux"
```

With captures, a nested pattern can also be specified, so that only targets
that match the nested pattern will be captured. This pattern can be literals or
further Matchers.

```ts
// Given a template -
// {
// "Resources": {
// "MyBar1": {
// "Type": "Foo::Bar",
// "Properties": {
// "Fred": ["Flob", "Cat"],
// }
// }
// "MyBar2": {
// "Type": "Foo::Bar",
// "Properties": {
// "Fred": ["Qix", "Qux"],
// }
// }
// }
// }

const capture = new Capture(Match.arrayWith(['Cat']));
template.hasResourceProperties('Foo::Bar', {
Fred: capture,
});

capture.asArray(); // returns ['Flob', 'Cat']
```

When multiple resources match the given condition, each `Capture` defined in
the condition will capture all matching values. They can be paged through using
the `next()` API. The following example illustrates this -

```ts
// Given a template -
// {
// "Resources": {
// "MyBar": {
// "Type": "Foo::Bar",
// "Properties": {
// "Fred": "Flob",
// }
// },
// "MyBaz": {
// "Type": "Foo::Bar",
// "Properties": {
// "Fred": "Quib",
// }
// }
// }
// }

const fredCapture = new Capture();
template.hasResourceProperties('Foo::Bar', {
Fred: fredCapture,
});

fredCapture.asString(); // returns "Flob"
fredCapture.next(); // returns true
fredCapture.asString(); // returns "Quib"
```
85 changes: 61 additions & 24 deletions packages/@aws-cdk/assertions/lib/capture.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Match } from '.';
import { Matcher, MatchResult } from './matcher';
import { Type, getType } from './private/type';

@@ -8,31 +9,63 @@ import { Type, getType } from './private/type';
*/
export class Capture extends Matcher {
public readonly name: string;
private value: any = null;
/** @internal */
public _captured: any[] = [];
private idx = 0;

constructor() {
/**
* Initialize a new capture
* @param pattern a nested pattern or Matcher.
* If a nested pattern is provided `objectLike()` matching is applied.
*/
constructor(private readonly pattern?: any) {
super();
this.name = 'Capture';
}

public test(actual: any): MatchResult {
this.value = actual;

const result = new MatchResult(actual);
if (actual == null) {
result.push(this, [], `Can only capture non-nullish values. Found ${actual}`);
return result.recordFailure({
matcher: this,
path: [],
message: `Can only capture non-nullish values. Found ${actual}`,
});
}

if (this.pattern !== undefined) {
const innerMatcher = Matcher.isMatcher(this.pattern) ? this.pattern : Match.objectLike(this.pattern);
const innerResult = innerMatcher.test(actual);
if (innerResult.hasFailed()) {
return innerResult;
}
}

result.recordCapture({ capture: this, value: actual });
return result;
}

/**
* When multiple results are captured, move the iterator to the next result.
* @returns true if another capture is present, false otherwise
*/
public next(): boolean {
if (this.idx < this._captured.length - 1) {
this.idx++;
return true;
}
return false;
}

/**
* Retrieve the captured value as a string.
* An error is generated if no value is captured or if the value is not a string.
*/
public asString(): string {
this.checkNotNull();
if (getType(this.value) === 'string') {
return this.value;
this.validate();
const val = this._captured[this.idx];
if (getType(val) === 'string') {
return val;
}
this.reportIncorrectType('string');
}
@@ -42,9 +75,10 @@ export class Capture extends Matcher {
* An error is generated if no value is captured or if the value is not a number.
*/
public asNumber(): number {
this.checkNotNull();
if (getType(this.value) === 'number') {
return this.value;
this.validate();
const val = this._captured[this.idx];
if (getType(val) === 'number') {
return val;
}
this.reportIncorrectType('number');
}
@@ -54,9 +88,10 @@ export class Capture extends Matcher {
* An error is generated if no value is captured or if the value is not a boolean.
*/
public asBoolean(): boolean {
this.checkNotNull();
if (getType(this.value) === 'boolean') {
return this.value;
this.validate();
const val = this._captured[this.idx];
if (getType(val) === 'boolean') {
return val;
}
this.reportIncorrectType('boolean');
}
@@ -66,9 +101,10 @@ export class Capture extends Matcher {
* An error is generated if no value is captured or if the value is not an array.
*/
public asArray(): any[] {
this.checkNotNull();
if (getType(this.value) === 'array') {
return this.value;
this.validate();
const val = this._captured[this.idx];
if (getType(val) === 'array') {
return val;
}
this.reportIncorrectType('array');
}
@@ -78,21 +114,22 @@ export class Capture extends Matcher {
* An error is generated if no value is captured or if the value is not an object.
*/
public asObject(): { [key: string]: any } {
this.checkNotNull();
if (getType(this.value) === 'object') {
return this.value;
this.validate();
const val = this._captured[this.idx];
if (getType(val) === 'object') {
return val;
}
this.reportIncorrectType('object');
}

private checkNotNull(): void {
if (this.value == null) {
private validate(): void {
if (this._captured.length === 0) {
throw new Error('No value captured');
}
}

private reportIncorrectType(expected: Type): never {
throw new Error(`Captured value is expected to be ${expected} but found ${getType(this.value)}. ` +
`Value is ${JSON.stringify(this.value, undefined, 2)}`);
throw new Error(`Captured value is expected to be ${expected} but found ${getType(this._captured[this.idx])}. ` +
`Value is ${JSON.stringify(this._captured[this.idx], undefined, 2)}`);
}
}
72 changes: 60 additions & 12 deletions packages/@aws-cdk/assertions/lib/match.ts
Original file line number Diff line number Diff line change
@@ -124,12 +124,20 @@ class LiteralMatch extends Matcher {

const result = new MatchResult(actual);
if (typeof this.pattern !== typeof actual) {
result.push(this, [], `Expected type ${typeof this.pattern} but received ${getType(actual)}`);
result.recordFailure({
matcher: this,
path: [],
message: `Expected type ${typeof this.pattern} but received ${getType(actual)}`,
});
return result;
}

if (actual !== this.pattern) {
result.push(this, [], `Expected ${this.pattern} but received ${actual}`);
result.recordFailure({
matcher: this,
path: [],
message: `Expected ${this.pattern} but received ${actual}`,
});
}

return result;
@@ -166,10 +174,18 @@ class ArrayMatch extends Matcher {

public test(actual: any): MatchResult {
if (!Array.isArray(actual)) {
return new MatchResult(actual).push(this, [], `Expected type array but received ${getType(actual)}`);
return new MatchResult(actual).recordFailure({
matcher: this,
path: [],
message: `Expected type array but received ${getType(actual)}`,
});
}
if (!this.subsequence && this.pattern.length !== actual.length) {
return new MatchResult(actual).push(this, [], `Expected array of length ${this.pattern.length} but received ${actual.length}`);
return new MatchResult(actual).recordFailure({
matcher: this,
path: [],
message: `Expected array of length ${this.pattern.length} but received ${actual.length}`,
});
}

let patternIdx = 0;
@@ -200,7 +216,11 @@ class ArrayMatch extends Matcher {
for (; patternIdx < this.pattern.length; patternIdx++) {
const pattern = this.pattern[patternIdx];
const element = (Matcher.isMatcher(pattern) || typeof pattern === 'object') ? ' ' : ` [${pattern}] `;
result.push(this, [], `Missing element${element}at pattern index ${patternIdx}`);
result.recordFailure({
matcher: this,
path: [],
message: `Missing element${element}at pattern index ${patternIdx}`,
});
}

return result;
@@ -236,21 +256,33 @@ class ObjectMatch extends Matcher {

public test(actual: any): MatchResult {
if (typeof actual !== 'object' || Array.isArray(actual)) {
return new MatchResult(actual).push(this, [], `Expected type object but received ${getType(actual)}`);
return new MatchResult(actual).recordFailure({
matcher: this,
path: [],
message: `Expected type object but received ${getType(actual)}`,
});
}

const result = new MatchResult(actual);
if (!this.partial) {
for (const a of Object.keys(actual)) {
if (!(a in this.pattern)) {
result.push(this, [`/${a}`], 'Unexpected key');
result.recordFailure({
matcher: this,
path: [`/${a}`],
message: 'Unexpected key',
});
}
}
}

for (const [patternKey, patternVal] of Object.entries(this.pattern)) {
if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) {
result.push(this, [`/${patternKey}`], 'Missing key');
result.recordFailure({
matcher: this,
path: [`/${patternKey}`],
message: 'Missing key',
});
continue;
}
const matcher = Matcher.isMatcher(patternVal) ?
@@ -275,15 +307,23 @@ class SerializedJson extends Matcher {
public test(actual: any): MatchResult {
const result = new MatchResult(actual);
if (getType(actual) !== 'string') {
result.push(this, [], `Expected JSON as a string but found ${getType(actual)}`);
result.recordFailure({
matcher: this,
path: [],
message: `Expected JSON as a string but found ${getType(actual)}`,
});
return result;
}
let parsed;
try {
parsed = JSON.parse(actual);
} catch (err) {
if (err instanceof SyntaxError) {
result.push(this, [], `Invalid JSON string: ${actual}`);
result.recordFailure({
matcher: this,
path: [],
message: `Invalid JSON string: ${actual}`,
});
return result;
} else {
throw err;
@@ -311,7 +351,11 @@ class NotMatch extends Matcher {
const innerResult = matcher.test(actual);
const result = new MatchResult(actual);
if (innerResult.failCount === 0) {
result.push(this, [], `Found unexpected match: ${JSON.stringify(actual, undefined, 2)}`);
result.recordFailure({
matcher: this,
path: [],
message: `Found unexpected match: ${JSON.stringify(actual, undefined, 2)}`,
});
}
return result;
}
@@ -325,7 +369,11 @@ class AnyMatch extends Matcher {
public test(actual: any): MatchResult {
const result = new MatchResult(actual);
if (actual == null) {
result.push(this, [], 'Expected a value but found none');
result.recordFailure({
matcher: this,
path: [],
message: 'Expected a value but found none',
});
}
return result;
}
97 changes: 83 additions & 14 deletions packages/@aws-cdk/assertions/lib/matcher.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Capture } from './capture';

/**
* Represents a matcher that can perform special data matching
* capabilities between a given pattern and a target.
@@ -25,6 +27,43 @@ export abstract class Matcher {
public abstract test(actual: any): MatchResult;
}

/**
* Match failure details
*/
export interface MatchFailure {
/**
* The matcher that had the failure
*/
readonly matcher: Matcher;

/**
* The relative path in the target where the failure occurred.
* If the failure occurred at root of the match tree, set the path to an empty list.
* If it occurs in the 5th index of an array nested within the 'foo' key of an object,
* set the path as `['/foo', '[5]']`.
*/
readonly path: string[];

/**
* Failure message
*/
readonly message: string;
}

/**
* Information about a value captured during match
*/
export interface MatchCapture {
/**
* The instance of Capture class to which this capture is associated with.
*/
readonly capture: Capture;
/**
* The value that was captured
*/
readonly value: any;
}

/**
* The result of `Match.test()`.
*/
@@ -34,21 +73,26 @@ export class MatchResult {
*/
public readonly target: any;
private readonly failures: MatchFailure[] = [];
private readonly captures: Map<Capture, any[]> = new Map();
private finalized: boolean = false;

constructor(target: any) {
this.target = target;
}

/**
* Push a new failure into this result at a specific path.
* If the failure occurred at root of the match tree, set the path to an empty list.
* If it occurs in the 5th index of an array nested within the 'foo' key of an object,
* set the path as `['/foo', '[5]']`.
* @param path the path at which the failure occurred.
* @param message the failure
* DEPRECATED
* @deprecated use recordFailure()
*/
public push(matcher: Matcher, path: string[], message: string): this {
this.failures.push({ matcher, path, message });
return this.recordFailure({ matcher, path, message });
}

/**
* Record a new failure into this result at a specific path.
*/
public recordFailure(failure: MatchFailure): this {
this.failures.push(failure);
return this;
}

@@ -67,10 +111,29 @@ export class MatchResult {
* @param id the id of the parent tree.
*/
public compose(id: string, inner: MatchResult): this {
const innerF = (inner as any).failures as MatchFailure[];
const innerF = inner.failures;
this.failures.push(...innerF.map(f => {
return { path: [id, ...f.path], message: f.message, matcher: f.matcher };
}));
inner.captures.forEach((vals, capture) => {
vals.forEach(value => this.recordCapture({ capture, value }));
});
return this;
}

/**
* Prepare the result to be analyzed.
* This API *must* be called prior to analyzing these results.
*/
public finished(): this {
if (this.finalized) {
return this;
}

if (this.failCount === 0) {
this.captures.forEach((vals, cap) => cap._captured.push(...vals));
}
this.finalized = true;
return this;
}

@@ -83,10 +146,16 @@ export class MatchResult {
return '' + r.message + loc + ` (using ${r.matcher.name} matcher)`;
});
}
}

type MatchFailure = {
matcher: Matcher;
path: string[];
message: string;
}
/**
* Record a capture against in this match result.
*/
public recordCapture(options: MatchCapture): void {
let values = this.captures.get(options.capture);
if (values === undefined) {
values = [];
}
values.push(options.value);
this.captures.set(options.capture, values);
}
}
6 changes: 5 additions & 1 deletion packages/@aws-cdk/assertions/lib/private/matchers/absent.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,11 @@ export class AbsentMatch extends Matcher {
public test(actual: any): MatchResult {
const result = new MatchResult(actual);
if (actual !== undefined) {
result.push(this, [], `Received ${actual}, but key should be absent`);
result.recordFailure({
matcher: this,
path: [],
message: `Received ${actual}, but key should be absent`,
});
}
return result;
}
3 changes: 2 additions & 1 deletion packages/@aws-cdk/assertions/lib/private/section.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ export function matchSection(section: any, props: any): MatchSuccess | MatchFail

(logicalId, entry) => {
const result = matcher.test(entry);
result.finished();
if (!result.hasFailed()) {
matching[logicalId] = entry;
} else {
@@ -63,4 +64,4 @@ export function filterLogicalId(section: { [key: string]: {} }, logicalId: strin
return Object.entries(section ?? {})
.filter(([k, _]) => k === logicalId)
.reduce((agg, [k, v]) => { return { ...agg, [k]: v }; }, {});
}
}
6 changes: 3 additions & 3 deletions packages/@aws-cdk/assertions/package.json
Original file line number Diff line number Diff line change
@@ -65,10 +65,10 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.0.2",
"@types/jest": "^27.0.3",
"constructs": "^3.3.69",
"jest": "^27.3.1",
"ts-jest": "^27.0.7"
"jest": "^27.4.3",
"ts-jest": "^27.1.1"
},
"dependencies": {
"@aws-cdk/cloud-assembly-schema": "0.0.0",
82 changes: 68 additions & 14 deletions packages/@aws-cdk/assertions/test/capture.test.ts
Original file line number Diff line number Diff line change
@@ -15,55 +15,109 @@ describe('Capture', () => {
expect(result.toHumanStrings()[0]).toMatch(/Can only capture non-nullish values/);
});

test('no captures if not finished', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: capture });

matcher.test({ foo: 'bar' }); // Not calling finished()
expect(() => capture.asString()).toThrow(/No value captured/);
});

test('asString()', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: capture });

matcher.test({ foo: 'bar' });
expect(capture.asString()).toEqual('bar');
matcher.test({ foo: 'bar' }).finished();
matcher.test({ foo: 3 }).finished();

matcher.test({ foo: 3 });
expect(capture.asString()).toEqual('bar');
expect(capture.next()).toEqual(true);
expect(() => capture.asString()).toThrow(/expected to be string but found number/);
});

test('asNumber()', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: capture });

matcher.test({ foo: 3 });
expect(capture.asNumber()).toEqual(3);
matcher.test({ foo: 3 }).finished();
matcher.test({ foo: 'bar' }).finished();

matcher.test({ foo: 'bar' });
expect(capture.asNumber()).toEqual(3);
expect(capture.next()).toEqual(true);
expect(() => capture.asNumber()).toThrow(/expected to be number but found string/);
});

test('asArray()', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: capture });

matcher.test({ foo: ['bar'] });
expect(capture.asArray()).toEqual(['bar']);
matcher.test({ foo: ['bar'] }).finished();
matcher.test({ foo: 'bar' }).finished();

matcher.test({ foo: 'bar' });
expect(capture.asArray()).toEqual(['bar']);
expect(capture.next()).toEqual(true);
expect(() => capture.asArray()).toThrow(/expected to be array but found string/);
});

test('asObject()', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: capture });

matcher.test({ foo: { fred: 'waldo' } });
expect(capture.asObject()).toEqual({ fred: 'waldo' });
matcher.test({ foo: { fred: 'waldo' } }).finished();
matcher.test({ foo: 'bar' }).finished();

matcher.test({ foo: 'bar' });
expect(capture.asObject()).toEqual({ fred: 'waldo' });
expect(capture.next()).toEqual(true);
expect(() => capture.asObject()).toThrow(/expected to be object but found string/);
});

test('nested within an array', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: ['bar', capture] });

matcher.test({ foo: ['bar', 'baz'] });
matcher.test({ foo: ['bar', 'baz'] }).finished();
expect(capture.asString()).toEqual('baz');
});
});

test('multiple captures', () => {
const capture = new Capture();
const matcher = Match.objectEquals({ foo: capture, real: true });

matcher.test({ foo: 3, real: true }).finished();
matcher.test({ foo: 5, real: true }).finished();
matcher.test({ foo: 7, real: false }).finished();

expect(capture.asNumber()).toEqual(3);
expect(capture.next()).toEqual(true);
expect(capture.asNumber()).toEqual(5);
expect(capture.next()).toEqual(false);
});

test('nested pattern match', () => {
const capture = new Capture(Match.objectLike({ bar: 'baz' }));
const matcher = Match.objectLike({ foo: capture });

matcher.test({
foo: {
bar: 'baz',
fred: 'waldo',
},
}).finished();

expect(capture.asObject()).toEqual({ bar: 'baz', fred: 'waldo' });
expect(capture.next()).toEqual(false);
});

test('nested pattern does not match', () => {
const capture = new Capture(Match.objectLike({ bar: 'baz' }));
const matcher = Match.objectLike({ foo: capture });

matcher.test({
foo: {
fred: 'waldo',
},
}).finished();

expect(() => capture.asObject()).toThrow(/No value captured/);
});
});
29 changes: 28 additions & 1 deletion packages/@aws-cdk/assertions/test/template.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { App, CfnMapping, CfnOutput, CfnResource, NestedStack, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { Match, Template } from '../lib';
import { Capture, Match, Template } from '../lib';

describe('Template', () => {
test('fromString', () => {
@@ -293,6 +293,33 @@ describe('Template', () => {
Properties: Match.objectLike({ baz: 'qux' }),
})).toThrow(/No resource/);
});

test('capture', () => {
const stack = new Stack();
new CfnResource(stack, 'Bar1', {
type: 'Foo::Bar',
properties: { baz: 'qux', real: true },
});
new CfnResource(stack, 'Bar2', {
type: 'Foo::Bar',
properties: { baz: 'waldo', real: true },
});
new CfnResource(stack, 'Bar3', {
type: 'Foo::Bar',
properties: { baz: 'fred', real: false },
});

const capture = new Capture();
const inspect = Template.fromStack(stack);
inspect.hasResource('Foo::Bar', {
Properties: Match.objectLike({ baz: capture, real: true }),
});

expect(capture.asString()).toEqual('qux');
expect(capture.next()).toEqual(true);
expect(capture.asString()).toEqual('waldo');
expect(capture.next()).toEqual(false);
});
});

describe('hasResourceProperties', () => {
4 changes: 2 additions & 2 deletions packages/@aws-cdk/assets/package.json
Original file line number Diff line number Diff line change
@@ -73,10 +73,10 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2",
"@types/jest": "^27.0.3",
"@types/sinon": "^9.0.11",
"aws-cdk": "0.0.0",
"jest": "^27.3.1",
"jest": "^27.4.3",
"sinon": "^9.2.4",
"ts-mock-imports": "^1.3.8"
},
13 changes: 12 additions & 1 deletion packages/@aws-cdk/aws-accessanalyzer/README.md
Original file line number Diff line number Diff line change
@@ -15,6 +15,17 @@

This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.

```ts
```ts nofixture
import * as accessanalyzer from '@aws-cdk/aws-accessanalyzer';
```

<!--BEGIN CFNONLY DISCLAIMER-->

There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet.
However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly.

For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::AccessAnalyzer](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_AccessAnalyzer.html).

(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) if you are interested in contributing to this construct library.)

<!--END CFNONLY DISCLAIMER-->
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-accessanalyzer/package.json
Original file line number Diff line number Diff line change
@@ -78,7 +78,7 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2"
"@types/jest": "^27.0.3"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-acmpca/package.json
Original file line number Diff line number Diff line change
@@ -78,7 +78,7 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2"
"@types/jest": "^27.0.3"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
15 changes: 14 additions & 1 deletion packages/@aws-cdk/aws-amazonmq/README.md
Original file line number Diff line number Diff line change
@@ -13,6 +13,19 @@

<!--END STABILITY BANNER-->

```ts
This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.

```ts nofixture
import * as amazonmq from '@aws-cdk/aws-amazonmq';
```

<!--BEGIN CFNONLY DISCLAIMER-->

There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet.
However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly.

For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::AmazonMQ](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_AmazonMQ.html).

(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) if you are interested in contributing to this construct library.)

<!--END CFNONLY DISCLAIMER-->
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-amazonmq/package.json
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2"
"@types/jest": "^27.0.3"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-amplify/package.json
Original file line number Diff line number Diff line change
@@ -79,7 +79,7 @@
"@aws-cdk/cdk-integ-tools": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.0.2",
"@types/jest": "^27.0.3",
"@types/yaml": "1.9.6"
},
"dependencies": {
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-amplifyuibuilder/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc');
baseConfig.parserOptions.project = __dirname + '/tsconfig.json';
module.exports = baseConfig;
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-amplifyuibuilder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
*.js
*.js.map
*.d.ts
tsconfig.json
node_modules
*.generated.ts
dist
.jsii

.LAST_BUILD
.nyc_output
coverage
.nycrc
.LAST_PACKAGE
*.snk
nyc.config.js
!.eslintrc.js
!jest.config.js
junit.xml
29 changes: 29 additions & 0 deletions packages/@aws-cdk/aws-amplifyuibuilder/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Don't include original .ts files when doing `npm pack`
*.ts
!*.d.ts
coverage
.nyc_output
*.tgz

dist
.LAST_PACKAGE
.LAST_BUILD
!*.js

# Include .jsii
!.jsii

*.snk

*.tsbuildinfo

tsconfig.json

.eslintrc.js
jest.config.js

# exclude cdk artifacts
**/cdk.out
junit.xml
test/
!*.lit.ts
Loading