Generate local include file containing ARNs of my yml created resources

I’m creating several AWS resources with using cloudformation in severless.yml. I need to get the ARNs from these resources into a local file so that I can include them in the build of my client app. Is there some scheme to do this?

can you set outputs in serverless.yml, then you can easily get it by command serverless info -v?

For example, you can output the dynamodb ARN with below codes:

Resources:
  DynamoDbTable:
    Type: 'AWS::DynamoDB::Table'
    ....

  Outputs:
    DynamoDbTable:
      Value:
        "Ref": DynamoDbTable
    DynamoDbTableARN:
      Value:
        "Fn::GetAtt": [ DynamoDbTable, Arn ]

I did this like so.

In serverless.yml

plugins:
  # allow for scripts to inject into serverless lifecycle events
  - serverless-scriptable-plugin

custom:
  scriptHooks:
    # compile the web app after cloudformation stack data is known
    after:aws:info:gatherData: scripts/compile-web.js

Where my cloudformation template has outputs like this:

  Outputs:
    AttachmentsBucketName:
      Description: name of the attachments s3 bucket
      Value:
        Ref: AttachmentsBucket

And scripts/compile-web.js:

/* global serverless, options */

serverless.cli.log('compile-web: compiling web package with cloudformation outputs')

const shell = require('shelljs')

const awsInfo = serverless.pluginManager.plugins.find(p => p.constructor.name === 'AwsInfo')

const getOutput = (key) => {
  const entry = awsInfo.gatheredData.outputs.find(o => o.OutputKey === key)
  return entry && entry.OutputValue
}

const region = options.region || serverless.service.provider.region
const apiRoot = getOutput('ServiceEndpoint')
const attachmentsBucketName = getOutput('AttachmentsBucketName')
const cognitoUserPoolId = getOutput('CognitoUserPoolId')
const cognitoUserPoolClientId = getOutput('CognitoUserPoolClientId')
const cognitoIdentityPoolId = getOutput('CognitoIdentityPoolId')
const webBucketName = getOutput('WebBucketName')
const webCloudfrontDistId = getOutput('WebCloudfrontDistId')
const webRedirectCloudfrontDistId = getOutput('WebRedirectCloudfrontDistId')

serverless.cli.log(`compile-web: gathered cloudformation outputs:\n
  region: ${region}\n
  apiRoot: ${apiRoot}\n
  attachmentsBucketName: ${attachmentsBucketName}\n
  cognitoUserPoolId: ${cognitoUserPoolId}\n
  cognitoUserPoolClientId: ${cognitoUserPoolClientId}\n
  cognitoIdentityPoolId: ${cognitoIdentityPoolId}\n
  webBucketName: ${webBucketName}\n
  webCloudfrontDistId: ${webCloudfrontDistId}\n
  webRedirectCloudfrontDistId: ${webRedirectCloudfrontDistId}`)

// invalidate redirect distribution only if it is present
let webRedirectInvalidationCommand = ''
if (webRedirectCloudfrontDistId) {
  webRedirectInvalidationCommand = `&& aws cloudfront create-invalidation --distribution-id ${webRedirectCloudfrontDistId} --paths '/*'`
}

if (options.web) {
  serverless.cli.log('compile-web: compiling and deploying for web as --web option is provided')

  if (shell.exec(
    `cd ${__dirname}/../src/web && \
    REACT_APP_AWS_REGION=${region} \
    REACT_APP_API_ROOT=${apiRoot} \
    REACT_APP_ATTACHMENTS_BUCKET=${attachmentsBucketName} \
    REACT_APP_COGNITO_USER_POOL_ID=${cognitoUserPoolId} \
    REACT_APP_COGNITO_USER_POOL_CLIENT_ID=${cognitoUserPoolClientId} \
    REACT_APP_COGNITO_IDENTITY_POOL_ID=${cognitoIdentityPoolId} \
    npm run build && \
    aws s3 sync build/ s3://${webBucketName} && \
    aws configure set preview.cloudfront true && \
    aws cloudfront create-invalidation --distribution-id ${webCloudfrontDistId} --paths '/*' \
    ${webRedirectInvalidationCommand}`).code !== 0
  ) {
    shell.echo('compile-web error: web build and deployment failed with exit code !== 0')
    shell.exit(1)
  }
} else {
  serverless.cli.log('compile-web: skipping web compilation as --web option is not provided')
}

Note that this is making use of create react app’s support for injecting environment variables with webpack.

Does this answer your question?

That is the conclusion I have come to also, you need to write plugin scripts to do it. Thanks for the example.

No worries. Are you going to open source your plugin? Would be interesting to compare our approaches.

First I would need to write it. I just hit this problem yesterday.

You can also checkout the serverless-stack-output plugin. It will write the cloudformation outputs to a file and you can run custom code against it (if you wish. eg postDeploy stuff)

Here is an example of postDeploy script writing all my endpoints out to a manifest file https://github.com/serverless/forms-service/blob/master/backend/serverless.yml#L15-L17

@DavidWells that’s a good tip as well, though keep in mind that with this approach you won’t have access to the serverless or options objects. Of course you can try to parse serverless.yml, but that doesn’t give you all information (e.g. command line options, compiled references, or dynamic modifications made by other plugins).

Do you .gitignore the output file? Just curious. I guess it’d be better to do so, since it is technically not source code and you wouldn’t want to have to recommit after every deploy.

Yeah I’m trying to figure out the best way for the using the manifest file I create in a CI/CD flow.

Committing the manifest.json to git is the simple solution. But that has it’s limitations for multiple stages etc.

The serverless-stack-outputs plugin doesn’t have the serverless object but it could =) https://github.com/sbstjn/serverless-stack-output/blob/master/src/plugin.ts#L51. That might be nice as a secondary param. @sbstjn what do you think? (require(splits.join('.'))[func](data, serverless))

Currently I am parsing the raw serverless.yml and that has the pitfalls you mention (no env vars and limited variable usage)

I just released v0.2.2 of serverless-stack-output on GitHub which includes this feature ( hopefully :wink: ). Should be on NPM in a few minutes, please test the changes and let me know if this works for you!

The CLI options are available in the handler as well.

1 Like