Brad Wehrwein
Brad Wehrwein

Reputation: 165

AWS CDK: run external build command in CDK sequence?

Is it possible to run an external build command as part of a CDK stack sequence? Intention: 1) create a rest API, 2) write rest URL to config file, 3) build and deploy a React app:

import apigateway = require('@aws-cdk/aws-apigateway');
import cdk = require('@aws-cdk/core');
import fs = require('fs')
import s3deployment = require('@aws-cdk/aws-s3-deployment');

export class MyStack extends cdk.Stack {

  const restApi = new apigateway.RestApi(this, ..);
  fs.writeFile('src/app-config.json', 
    JSON.stringify({ "api": restApi.deploymentStage.urlForPath('/myResource') }))

  // TODO locally run 'npm run build', create 'build' folder incl rest api config

  const websiteBucket = new s3.Bucket(this, ..)
  new s3deployment.BucketDeployment(this, .. {
      sources: [s3deployment.Source.asset('build')],
      destinationBucket: websiteBucket
  }) 
}

Upvotes: 8

Views: 7707

Answers (5)

Michael Behrens
Michael Behrens

Reputation: 1157

In my case, I'm using the Python language for CDK. I have a Makefile which I invoke directly from my app.py like this: os.system("make"). I use the make to build up a layer zip file per AWS Docs. Technically you can invoke whatever you'd like. You must import the os package of course. Hope this helps.

Upvotes: 0

jayair
jayair

Reputation: 1261

As others have mentioned, this isn't supported within CDK. So this how we solved it in SST: https://github.com/serverless-stack/serverless-stack

  1. On the CDK side, allow defining React environment variables using the outputs of other constructs.

    // Create a React.js app
    const site = new sst.ReactStaticSite(this, "Site", {
      path: "frontend",
      environment: {
        // Pass in the API endpoint to our app
        REACT_APP_API_URL: api.url,
      },
    });
    
  2. Spit out a config file while starting the local environment for the backend.

  3. Then start React using sst-env -- react-scripts start, where we have a simple CLI that reads from the config file and loads them as build-time environment variables in React.

  4. While deploying, replace these environment variables inside a custom resource based on the outputs.

We wrote about it here: https://serverless-stack.com/chapters/setting-serverless-environments-variables-in-a-react-app.html

And here's the source for the ReactStaticSite and StaticSite constructs for reference.

Upvotes: 0

Romande
Romande

Reputation: 571

I solved a similar issue:

  • Needed to build and upload react-app as well
  • Supported dynamic configuration reading from react-app - look here
  • Released my react-app with specific version (in a separate flow)
  • Then, during CDK deployment of my app, it took a specific version of my react-app (version retrieved from local configuration) and uploaded its zip file to S3 bucket using CDK BucketDeployment
  • Then, using AwsCustomResource I generated a configuration file with references to Cognito and API-GW and uploaded this file to S3 as well:

        // create s3 bucket for react-app
        const uiBucket = new Bucket(this, "ui", {
            bucketName: this.stackName + "-s3-react-app",
            blockPublicAccess: BlockPublicAccess.BLOCK_ALL
        });

       
        let confObj = {
            "myjsonobj" : {
                "region": `${this.region}`,
                "identity_pool_id": `${props.CognitoIdentityPool.ref}`,
                "myBackend": `${apiGw.deploymentStage.urlForPath("/")}`
            }
        };


        const dataString = JSON.stringify(confObj, null, 4);

        
        const bucketDeployment = new BucketDeployment(this, this.stackName + "-app", {
            destinationBucket: uiBucket,
            sources: [Source.asset(`reactapp-v1.zip`)]
        });

        bucketDeployment.node.addDependency(uiBucket)

        const s3Upload = new custom.AwsCustomResource(this, 'config-json', {
            policy: custom.AwsCustomResourcePolicy.fromSdkCalls({resources: custom.AwsCustomResourcePolicy.ANY_RESOURCE}),
            onCreate: {
                service: "S3",
                action: "putObject",
                parameters: {
                    Body: dataString,
                    Bucket: `${uiBucket.bucketName}`,
                    Key: "app-config.json",
                },
                physicalResourceId: PhysicalResourceId.of(`${uiBucket.bucketName}`)
            }
        });
        s3Upload.node.addDependency(bucketDeployment);

Upvotes: 0

Brad Wehrwein
Brad Wehrwein

Reputation: 165

Accepting that cdk doesn't support this, I split logic into two cdk scripts, accessed API gateway URL as cdk output via the cli, then wrapped everything in a bash script.

AWS CDK:

// API gateway
const api = new apigateway.RestApi(this, 'my-api', ..)

// output url
const myResourceURL = api.deploymentStage.urlForPath('/myResource');
new cdk.CfnOutput(this, 'MyRestURL', { value: myResourceURL });

Bash:

# deploy api gw
cdk deploy --app (..)  

# read url via cli with --query
export rest_url=`aws cloudformation describe-stacks --stack-name (..) --query "Stacks[0].Outputs[?OutputKey=='MyRestURL'].OutputValue" --output text`

# configure React app
echo "{ \"api\" : { \"invokeUrl\" : \"$rest_url\" } }" > src/app-config.json

# build React app with url
npm run build

# run second cdk app to deploy React built output folder
cdk deploy --app (..)  

Is there a better way?

Upvotes: 2

Luca T
Luca T

Reputation: 668

Unfortunately, it is not possible, as the necessary references are only available after deploy and therefore after you try to write the file (the file will contain cdk tokens).

I personally have solved this problem by telling cdk to output the apigateway URLs to a file and then parse it after the deploy to upload it so a S3 bucket, to do it you need:

  • deploy with the output file options, for example: cdk deploy -O ./cdk.out/deploy-output.json
  • In ./cdk.out/deploy-output.json you will find a JSON object with a key for each stack that produced an output (e.g. your stack that contains an API gateway)
  • manually parse that JSON to get your apigateway url
  • create your configuration file and upload it to S3 (you can do it via aws-sdk)

Of course, you have the last steps in a custom script, which means that you have to wrap your cdk deploy. I suggest to do so with a nodejs script, so that you can leverage aws-sdk to upload your file to S3 easily.

Upvotes: 3

Related Questions