aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.56k stars 3.87k forks source link

CLI: way to get outputs from deployed stack #1773

Closed clareliguori closed 4 years ago

clareliguori commented 5 years ago

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 commented 5 years ago

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
clareliguori commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

+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 commented 5 years ago

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

cristianrat commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

Another significant dimension to the problem is that the output keys are auto generated so I don’t have a good way to know what they will be because of the random portions. The CDK format=JSON needs some way to locate outputs by construct name and attribute name because the auto generated key name can not be known ahead of time

On Wed, Jul 10, 2019 at 7:59 AM Cristian Raț notifications@github.com wrote:

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

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/awslabs/aws-cdk/issues/1773?email_source=notifications&email_token=AA2VN6T63QIOW77XA4ZSDPTP6XFJPA5CNFSM4GXSHCH2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZTHLGY#issuecomment-510031259, or mute the thread https://github.com/notifications/unsubscribe-auth/AA2VN6U3MF2RZGFUGSWCMVTP6XFJPANCNFSM4GXSHCHQ .

cristianrat commented 5 years ago

For me simply selecting one of the outputs is enough. For example I need the VPC id to for the next step of the Jenkins pipeline. So if I could do cdk deploy...... --get-output MYID Where MYID is what I specified as CfnOutput in the stack. Hope this makes sense. Writing from phone

On Wed, 10 Jul 2019, 20:44 Nathan Peck, notifications@github.com wrote:

Another significant dimension to the problem is that the output keys are auto generated so I don’t have a good way to know what they will be because of the random portions. The CDK format=JSON needs some way to locate outputs by construct name and attribute name because the auto generated key name can not be known ahead of time

On Wed, Jul 10, 2019 at 7:59 AM Cristian Raț notifications@github.com wrote:

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

— You are receiving this because you commented. Reply to this email directly, view it on GitHub < https://github.com/awslabs/aws-cdk/issues/1773?email_source=notifications&email_token=AA2VN6T63QIOW77XA4ZSDPTP6XFJPA5CNFSM4GXSHCH2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZTHLGY#issuecomment-510031259 , or mute the thread < https://github.com/notifications/unsubscribe-auth/AA2VN6U3MF2RZGFUGSWCMVTP6XFJPANCNFSM4GXSHCHQ

.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/awslabs/aws-cdk/issues/1773?email_source=notifications&email_token=ALFYCRFSZ3HJPAIHFA34F4DP6Y3Z5A5CNFSM4GXSHCH2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZUQZUI#issuecomment-510201041, or mute the thread https://github.com/notifications/unsubscribe-auth/ALFYCRB5WYYWTNGPAWGRA3TP6Y3Z5ANCNFSM4GXSHCHQ .

nathanpeck commented 5 years ago

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 commented 5 years ago

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

brettswift commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 :+1: - 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 commented 4 years ago

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

eladb commented 4 years ago

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

evcatalyst commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

Just ran into this myself. :+1:

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

duarten commented 4 years ago

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.

Dzhuneyt commented 4 years ago

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 commented 4 years ago

picking this task up!

duarten commented 4 years ago

@shivlaks is there an rfc for the feature?

jsdtaylor commented 4 years ago

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 commented 4 years ago

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

acomagu commented 4 years ago

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

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 commented 4 years ago

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.

oscarnevarezleal commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

Something like what Amplify does would be terrific

solidsnack commented 2 years ago

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 commented 2 years ago
// 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 commented 1 year ago

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 commented 8 months ago

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