Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: way to get outputs from deployed stack #1773

Closed
clareliguori opened this issue Feb 14, 2019 · 36 comments · Fixed by #7020
Closed

CLI: way to get outputs from deployed stack #1773

clareliguori opened this issue Feb 14, 2019 · 36 comments · Fixed by #7020
Assignees
Labels
effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. in-progress This issue is being actively worked on. package/tools Related to AWS CDK Tools or CLI

Comments

@clareliguori
Copy link
Member

After deploying the stack, there's no way in the CLI to go back and get outputs from the deployed stack with the CDK CLI. I expected something like "cdk metadata" to give me this information.

For example, the LoadBalancedFargateService construct outputs the DNS name of the load balancer. It will be useful to be able to query that output value for a CDK stack with the CDK CLI.

@sam-goodwin
Copy link
Contributor

You can scrape the outputs from the CLI stdout after running cdk deploy. Is that not enough? Would you prefer something like:

> cdk metadata --name MyLoadBalancer
your.dns.name

@sam-goodwin sam-goodwin added package/tools Related to AWS CDK Tools or CLI feature-request A feature should be added or improved. labels Feb 15, 2019
@clareliguori
Copy link
Member Author

Yeah I'm really thinking of the scenario where I go back later after a deploy and need to grab the DNS name. It feels weird to issue a deploy again to get that. Even a list of the outputs would be helpful, since they tend to get auto-named something like ServiceLoadBalancerDNSEC5B149E

@skorfmann
Copy link
Contributor

Had the same issue, solved it for now by adding this as a script to the package.json

aws cloudformation describe-stacks --stack-name <YOUR_STACK_NAME> | jq '.Stacks | .[] | .Outputs | reduce .[] as $i ({}; .[$i.OutputKey] = $i.OutputValue)'

@nathanpeck
Copy link
Member

+1

This is a pretty significant challenge when using the CDK programmatically, as the outputs are also randomly named. For example my stack has:

"Outputs": [
  {
      "OutputKey": "apiLoadBalancerDNS77556DD8",
      "OutputValue": "api-apiLB28-7S29OJ4JYVNQ-1248184514.us-east-2.elb.amazonaws.com"
  }
]

For now I'm relying on manually querying the output from CloudFormation like this:

aws cloudformation describe-stacks --stack-name <my stack name> --query "Stacks[0].Outputs[0].OutputValue" --output text

But the usage of a zero index isn't particularly safe as a new output may be added in the future. However I have no way of knowing what the key will be for the particular output that I need

@eladb
Copy link
Contributor

eladb commented May 29, 2019

Would something like “cdk deploy —format=json” which will output a json key value map of outputs and their values work?

@cristianrat
Copy link

I am finding this quite difficult as well. I am using cdk in a Jenkins pipeline.
Just getting and using my output is proving tricky.

@cristianrat
Copy link

Would something like “cdk deploy —format=json” which will output a json key value map of outputs and their values work?

I think that would be a good place to start

@nathanpeck
Copy link
Member

nathanpeck commented Jul 10, 2019 via email

@cristianrat
Copy link

cristianrat commented Jul 10, 2019 via email

@nathanpeck
Copy link
Member

nathanpeck commented Jul 16, 2019

I've got a workaround for this. Rather than trying to get the value from an automatically named output we can manually create our own output in the CDK app like this:

import cdk = require('@aws-cdk/core');

// A manually named output
new cdk.CfnOutput(this, 'LoadBalancerDNS', {
  value: ecsService.loadBalancer.loadBalancerDnsName
});

Then its possible to locate the value for that manually named output in the CloudFormation outputs like this:

aws cloudformation describe-stacks \
  --stack-name <my stack name> \
  --query "Stacks[0].Outputs[?OutputKey==`LoadBalancerDNS`].OutputValue" \
  --output text

This CLI command will give you the value for the output, which in this case is the DNS for a load balancer. In my use case I needed to get the DNS name in order to run automated tests against the deployment, so I was able to embed this command in my build script after the CDK deploy to get the url for the deployed environment

@cristianrat
Copy link

Exactly what I did. But working around to get functionality that should already exist, is not ideal :)

@brettswift
Copy link

brettswift commented Jul 28, 2019

My 2 bits. From an automation perspective.

cdk outputs --format=json (so I don't have to run a deploy for instance)

Or even dump them in cdk.out/<stack_name>.outputs.json during the deploy command.

Currently I do something like: cdk deploy 2>&1 | tee -a .deploy-log

Then grep the outputs section, awk for the thing I want.

It works, but using jq or a native cdk command that knew how to parse the cdk.out/<stackname>.outputs.json would be great.

An output file(s) would be a great initial step to this path I think.

@eladb
Copy link
Contributor

eladb commented Aug 7, 2019

It doesn't really make sense that outputs will be saved to cdk.out but maybe something similar. @shivlaks this is a desired feature of this CLI

@mitchlloyd
Copy link
Contributor

I would like to add consideration for dynamic stack names in this feature. For integration tests we have dynamic stack names with a git branch appended. Being able to cdk deploy MyStack-branch-* and then get the outputs relevant for that deployment is a bit nicer than writing a file like MyStack-branch-abc123.outputs.json and then needing to ls | grep to find it.

Something like cdk deploy 'MyStack-*' --save-outputs=outputs.json where I don't need to know the full stack name is closer to turn-key for this use case.

@andreimcristof
Copy link

andreimcristof commented Sep 21, 2019

thank you so much @nathanpeck for the idea, the CfnOutput is amazing. Literally can output anything I need in precise detail like this.

@eladb “cdk deploy —format=json” sounds great too 👍 - is there a way to hook into an "all operations done, finishing" event that I can add an after-deployment action as callback ? In my case, I need to invoke a script that writes all outputs to a file.

@hoang-innomize
Copy link

I am also trying to get some outputs of the deployed CF stacks. Does anyone have any workaround solution to make it works?

@eladb
Copy link
Contributor

eladb commented Nov 11, 2019

@shivlaks this needs to be highly prioritized in the CLI backlog

@evcatalyst
Copy link

@shivlaks I agree with @eladb this needs to be prioritized

for example I need to output AppSync API Endpoint, ID and Keys that are generated. These are not in the cloudformation templates, and I can get the endpoint and keys from aws cli, but need to at least know what the ID is and output that.

@brettswift
Copy link

Another thing that would be valuable to me is resolved SSM parameters, or even just stack parameters of the currently deployed stacks.

Seeing these in a similar command as what we're discussing on this issue would be nice. If SSM params are resolved by Cloudformation, they show up in the parameters section of the stack, so seeing them on the CLI would also be nice.

Maybe this ask could be it's own issue? If we get outputs first, turning all params into an output would be a decent stop gap.

@lordjabez
Copy link

I'll second having cdk output (since it resembles the equivalent terraform command). CfnOutput is an okay solution but having a tiny bit of sugar around it would be nice so it feels more like a first class feature.

@lbjay
Copy link

lbjay commented Jan 8, 2020

Just ran into this myself. 👍

Will probably go with the manual cdk.CfnOutput workaround for now, but having a cdk output command would be much appreciated.

@duarten
Copy link
Contributor

duarten commented Jan 31, 2020

cdk deploy —format=json requires wrapper scripts. A better approach would be to have a post-deploy-app parameter, which CDK invokes. The stdin could be the json. But still, this feels incomplete.

A different design would have the CLI become a library that user code invokes to deploy a stack. Being aware of the object model, the deploy step could fill in all tokens.

@shivlaks shivlaks added the effort/medium Medium work item – several days of effort label Feb 5, 2020
@Dzhuneyt
Copy link
Contributor

It's really sad to see this not being available out of the box (like terraform output for example).

Since this is a common need for me and the projects I'm working on, I created a temporary solution in terms of an utility script: https://github.com/Dzhuneyt/cdk-get-stack-output script. I hope you find it useful.

Sample usage:
./cdk-output --name=s3bucket --fromStack=my-cdk-stack

Contributions welcome.

@shivlaks
Copy link
Contributor

shivlaks commented Mar 3, 2020

picking this task up!

@shivlaks shivlaks added the in-progress This issue is being actively worked on. label Mar 3, 2020
@duarten
Copy link
Contributor

duarten commented Mar 4, 2020

@shivlaks is there an rfc for the feature?

@jsdtaylor
Copy link

It's really sad to see this not being available out of the box (like terraform output for example).

Harsh considering it's only been in GA since July last year.

Thanks for the script link though, do you have any documentation for it?

@Dzhuneyt
Copy link
Contributor

Dzhuneyt commented Mar 7, 2020

@jsdtaylor Yes, the README.md. It's very simple to use with 2 parameters only.

@acomagu
Copy link
Contributor

acomagu commented Mar 15, 2020

@nathanpeck 's solution should be fixed like below for me ⭐ :

new cdk.CfnOutput(this, 'OutputId', {
  value: ...,
  exportName: 'OutputExportName',
});
aws cloudformation describe-stacks \
  --stack-name <my stack name> \
  --query "Stacks[0].Outputs[?ExportName==`OutputExportName`].OutputValue" \
  --output text

@0xdevalias
Copy link
Contributor

0xdevalias commented Mar 19, 2020

While this isn't directly related to the CLI, some 'syntactic sugar' for cdk.CfnOutput has been mentioned here, so it felt relevant.

It would be kind of cool if any cdk.CfnOutput were also accessible directly from the corresponding cdk.Stack it was used within.

As a short contrived example:

class FooStack extends cdk.Stack {
  // ..snip..
  new cdk.CfnOutput(this, 'Foo', { value: 'foo!' })
  // ..snip..
}

const app = new cdk.App()
const fooStack = new FooStack(app, 'foo', {})

console.log(fooStack.outputs['Foo'])

I know we can use Fn.importValue, but I guess I was hoping for something that flows a little more 'natively' in the code.

https://docs.aws.amazon.com/cdk/latest/guide/constructs.html talks about using standard JS class properties to expose relevant details, which is workable, but then we end up having to both define the property as well as the cdk.CfnOutput.

@mergify mergify bot closed this as completed in #7020 Apr 1, 2020
mergify bot pushed a commit that referenced this issue Apr 1, 2020
feat(cli): write stack outputs to a file

Write stack outputs from deployments into a file. A flag `--outputs-file` can be provided where stack outputs will be written in `json` format.

Supports multi-stack and wild-card deployments where all the generated outputs from deployed stacks will be written to the outputs file.

Closes #1773
@oscarnevarezleal
Copy link

Here's a fork from nathanpeck using jq. Hope it helps.

aws cloudformation describe-stacks \
    --stack-name YOURSTACK  \
    --query "Stacks[0].Outputs" \
    --output json | jq -rc '.[] | select(.OutputKey=="YOUR_OUTPUT_KEY") | .OutputValue '

@iDVB
Copy link

iDVB commented Jun 16, 2021

It would be super helpful if there were two additions to --outputs-file

  1. that you could pick the output type. We typically slurp these up as TOML files in .env
  2. that I could choose to flatten the file for that the top node is just the vars and not the stackname. At the very least that this would happen if you only have a single stack.

We're currently trying to get CDK outputs into a GatsbyJS build which uses dotenv (.env) to slurp them up.
We'd like to keep the build agnostic of the stack name so can have it nested in one.

@revmischa
Copy link
Contributor

Something like what Amplify does would be terrific

@solidsnack
Copy link

solidsnack commented Oct 9, 2021

Depending on what you're doing, JQ's test command is one way to get around the randomness in CDK-generated keys. For example, to get the Kubernetes config command for an EKS cluster:

:;  aws cloudformation describe-stacks --stack-name  "$name_of_stack" |
    jq -r '.Stacks | .[] | .Outputs[] | select(.OutputKey | test(".*ConfigCommand.*")) | .OutputValue' |
    bash
Updated context arn:aws:eks:aaa:bbb:cluster/ccc in /Users/nnn/.kube/config

@revmischa
Copy link
Contributor

// cloudformation stack output lookup
// output names are based on CDK node hierarchy + random suffix
// we could use exported names but then we have to worry about collisions
export enum StackOutput {
  MigrationScriptArn = "MigrationScriptMigrationScriptArn",
}
let _stackOutputs: Output[]

// retrieve a stack output value
// throws an exception if none found
export const getStackOutput = async ({
  cloudFormationClient,
  output,
  stackName,
}: {
  cloudFormationClient: CloudFormationClient
  output: StackOutput
  stackName: string
}): Promise<string> => {
  if (_stackOutputs) return lookupOutput(_stackOutputs, output)

  // describe stacks
  const descStackCmd = new DescribeStacksCommand({ StackName: stackName })
  const { Stacks } = await cloudFormationClient.send(descStackCmd)

  if (Stacks?.length !== 1) throw new Error(`Found ${Stacks?.length || 0} stacks named ${stackName}`)
  const stack = Stacks[0]!
  // get outputs
  _stackOutputs = stack.Outputs || []

  // find nested stacks of stack and get their outputs too
  if (stack.StackId) {
    const nestedStacks = await getNestedStacks(cloudFormationClient, stack.StackId)
    // push nested stack outputs
    _stackOutputs.push(...nestedStacks.flatMap((s) => s.Outputs).filter((o): o is Output => !!o))
  }

  // search
  return lookupOutput(_stackOutputs, output)
}

/**
 * Describe all stacks that are a descendent of rootStackId.
 */
const getNestedStacks = async (cloudFormationClient: CloudFormationClient, rootStackId: string): Promise<Stack[]> => {
  // get pages of stack summaries
  const descNestedStacksPaginator = paginateListStacks({ client: cloudFormationClient, pageSize: 50 }, {})

  // get a list of stack IDs that have rootStackId as the root
  const nestedStacksToDescribe: DescribeStacksCommand[] = []
  for await (const page of descNestedStacksPaginator) {
    const nestedStacks = page.StackSummaries?.filter((ss) => ss.RootId == rootStackId)
    if (!nestedStacks) continue

    nestedStacksToDescribe.push(
      ...nestedStacks.map(
        (ss) =>
          new DescribeStacksCommand({
            StackName: ss.StackName,
          })
      )
    )
  }

  // describe nested stacks (simultaneously)
  const stackDescribeRes = await Promise.all(nestedStacksToDescribe.map((desc) => cloudFormationClient.send(desc)))
  return stackDescribeRes.flatMap((sd) => sd.Stacks).filter((s): s is Stack => !!s)
}

// expect to find name in stackOutputs
// raise exception if not found
const lookupOutput = (stackOutputs: Output[] | undefined, name: StackOutput): string => {
  const value = findOutputIn(stackOutputs, name)
  if (value === undefined)
    throw new Error(`Failed to find stack output ${name} in ${stackOutputs?.map((o) => o.OutputKey)}`)
  return value
}

const findOutputIn = (stackOutputs: Output[] | undefined, name: StackOutput): string | undefined => {
  // look for an output that begins with this name
  return stackOutputs?.find((o) => o.OutputKey?.startsWith(name))?.OutputValue
}

@erhhung
Copy link

erhhung commented Nov 5, 2022

Building on previous answers and barring corner cases where you have multiple output values where the hashes do matter (e.g. "A/B/C" and "A/BC") or the logical IDs are too long so that CDK uses node addresses, you can just trim off the hash suffixes using some jq magic:

# eval $(
for stack in $(cdk ls 2> /dev/null); do
  aws cloudformation describe-stacks \
    --stack-name $stack \
    --query "Stacks[].Outputs[]" \
    --output json | \
  jq --arg stack $stack -r '.[] |
    "\($stack + .OutputKey[0:-8])=\(.OutputValue | @sh)"'
done
# )

If you're certain it's safe to eval the output, you could get those values as shell variables, or reformat the jq input to another JSON of your choosing.

@brettstack
Copy link
Contributor

You can now use key property, e.g.: new CfnOutput(this, 'UserPoolId', { key: 'UserPoolId', value: this.userPool.userPoolId })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. in-progress This issue is being actively worked on. package/tools Related to AWS CDK Tools or CLI
Projects
None yet
Development

Successfully merging a pull request may close this issue.