Skip to content

Extending Copilot with the CDK #4208

Closed
Closed
@efekarakus

Description

@efekarakus

Hi folks! This issue is a design proposal for overriding Copilot abstracted resources using the AWS Cloud Development Kit (CDK). The goal is to provide a "break the glass" mechanism to access and configure functionality that is not surfaced by Copilot manifests by leveraging the expressive power of a programming language.

Problem statement

Clients can describe their application configuration with declarative manifest.yml files. Manifests enable users to define their application in terms of “architecture as code” that gets transformed into AWS CloudFormation (CFN) templates by Copilot. Today, customers have the ability to define additional AWS resources with “addons” CFN templates. However, the resources encapsulated by manifests remain unmodifiable by users. We're looking into providing extension solutions for customers to self-unblock themselves and access the internal layers of Copilot.

This document proposes a user experience for extending manifests with the CDK.

Proposal

  1. Copilot will introduce a new command copilot [noun] override to create a CDK application that overrides Copilot resources, for example:

    $ copilot svc override
    > Which service's resources would you like to override?
    * frontend
    * api
    
    > Which infrastructure as code tool would you like to use to override "frontend"?
    * AWS Cloud Development Kit (CDK)
    * CloudFormation YAML patches
    
    > Which programming language would you like to use with the CDK?
    * Typescript(ts)
    
    > Which resources in "frontend" would you like to to override?
    [ ] DiscoveryService          (AWS::ServiceDiscovery::Service)
    [x] Service                   (AWS::ECS::Service)
    [x] PublicNetworkLoadBalancer  (AWS::ElasticLoadBalancingV2::LoadBalancer)
    [ ] TaskDefinition            (AWS::ECS::TaskDefinition)
    ✅ Created a new CDK app under `copilot/frontend/overrides/` to override the resources:
    .
    ├── .gitignore
    ├── .build/
    ├── bin/
    │   └── override.ts
    ├── cdk.json
    ├── package.json
    ├── stack.ts
    ├── tsconfig.json
    └── README.md
    
    Copilot did not detect npm installed.
    > Would you like Copilot to install npm? [Y/n] y
    ✅ Installed npm v9.1.1 and NodeJS v1.18.2.
    ✅ Ran `npm install` to install dependencies.
    
    > Recommended Actions:
    1. Please follow the guide under README.md

    The file structure generated by the override command is equivalent to cdk init output. The only additional file created by Copilot is the hidden .build/ directory.

    In order to simplify onboarding with the CDK, Copilot will assist clients with installing their dependencies. On macOS and Linux, Copilot can install npm for customers on their local machine. On Windows, we won't be able to help customers with installing npm and will skip to asking them to follow the README instead. Once npm is present, Copilot will run npm install to install the dependencies on behalf of the client.

  2. Clients have to edit only stack.ts file in order to implement the transform* methods for the selected resources. The following content will be present in the file by default:

    import * as cdk from 'aws-cdk-lib';
    import { aws_elasticloadbalancingv2 as elbv2 } from 'aws-cdk-lib';
    import { aws_ecs as ecs } from 'aws-cdk-lib';
    
    export class TransformedStack extends cdk.Stack {
        constructor (scope: cdk.App, id: string, props?: cdk.StackProps) {
            super(scope, id, props);
             this.template = new cdk.cloudformation_include.CfnInclude(this, 'Template', {
                templateFile: path.join('.build', 'in.yaml'),
            });
            this.appName = template.getParameter('AppName').valueAsString;
            this.envName = template.getParameter('EnvName').valueAsString;
    
            this.transformService();
            this.transformPublicNetworkLoadBalancer();
        }
     
        // TODO: implement me.
        transformService() {
          const service = this.template.getResource("Service") as ecs.CfnService;
          throw new error("not implemented");
        }
    
        // TODO: implement me.
        transformPublicNetworkLoadBalancer() {
          const publicNetworkLoadBalancer = this.template.getResource("PublicNetworkLoadBalancer") as elbv2.CfnLoadBalancer;
          throw new error("not implemented");
        }
    }

    As can be seen above, Copilot will use the clouformation_include module provided by the CDK to help author transformations. This library is the CDK’s recommendation from their “Import or migrate an existing AWS CloudFormation template” guide. It enables accessing the resources hidden by the manifest as L1 constructs and ensures the CDK retains the resources on synthesis. The CfnInclude object is initialized from the .build/in.yaml CFN template. This is how Copilot and the CDK communicates. Copilot writes the manifest generated CFN template under the .build/ directory, which then gets parsed by the cloudformation_include library into a CDK construct.

That's it! From this point forward, copilot svc package or copilot svc deploy will apply the overrides written in stack.ts. Under the hood, Copilot will invoke cdk synth to synthesize the transformed template and use that to deploy to the existing CFN stack.

Feedback

We'd love to hear your feedback on the user experience above:

  1. Do you see yourself using this feature? (overriding resources with the CDK)
  2. Does the feature meet your needs?
  3. If not, can you elaborate on what you'd like to do that can't be done with the above proposal?

Appendix: Sample issues

Every new “capability” feature request can be boiled down to having an override functionality. I’ve captured only a sample of these to test the proposal against.

Link Description
#4063 Override nlb configuration to assign elastic IPs to the network load balancer
#4010 Assign ReadOnlyRootFS: true to all containers in the task definition by default
#4005, #3840 Support for internal NLB for Backend Services. Customers can create the nlb with addons/ but need to wire it by overriding the ECS::Service definition
#3733 Allow setting external launch type for ECS tasks
#3721 Allow setting VPC flow logs for environments
#3720 Turn off assigning public IP addresses for tasks launched in public subnets
#3594, #1783 Multiple port support for services behind NLB and ALB
#3506 Add additional permissions to the generated environment manager role
#3504 Specify additional security groups for the app runner vpc connector

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/designIssues that are design proposals.

    Type

    No type

    Projects

    Status

    Design

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions