-
-
Save dduvnjak/de00bc28918a2dc9906520b00dcc411e to your computer and use it in GitHub Desktop.
Get outputs from a stack by name in CloudFormation. Uses AWS SDK v3, compatible with Node.js 20.x runtime.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const { CloudFormationClient, DescribeStacksCommand } = require('@aws-sdk/client-cloudformation'); | |
const { HttpRequest } = require('@aws-sdk/protocol-http'); | |
const { NodeHttpHandler } = require('@aws-sdk/node-http-handler'); | |
const url = require('node:url'); | |
async function getStackOutputs(properties) { | |
if (typeof properties.StackName === 'undefined') { | |
throw new Error('The StackName property was not specified.'); | |
} | |
let filter = []; | |
if (typeof properties.Filter !== 'undefined') { | |
if (!Array.isArray(properties.Filter)) { | |
throw new Error('The Filter property must be an array.'); | |
} | |
filter = properties.Filter; | |
} | |
const cloudFormationClient = new CloudFormationClient({}); | |
const params = { | |
StackName: properties.StackName | |
}; | |
console.log('getStackOutputs', properties, params); | |
try { | |
const command = new DescribeStacksCommand(params); | |
const { Stacks } = await cloudFormationClient.send(command); | |
console.log('describeStacks', Stacks); | |
let stack = Stacks[0]; | |
let statuses = ['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_COMPLETE']; | |
if (!statuses.includes(stack.StackStatus)) { | |
throw new Error(`Unable to get outputs for a stack "${properties.StackName}" in state "${stack.StackStatus}", aborting.`); | |
} | |
if (filter.length === 0) { | |
console.log('No output filter was specified, will return all outputs.'); | |
} | |
let outputs = {}; | |
stack.Outputs.forEach(function(output) { | |
if (filter.length > 0 && filter.includes(output.OutputKey)) { | |
outputs[output.OutputKey] = output.OutputValue; | |
} | |
else if (filter.length === 0) { | |
outputs[output.OutputKey] = output.OutputValue; | |
} | |
}); | |
if (Object.keys(outputs).length === 0) { | |
if (filter.length > 0) { | |
throw new Error('No matching outputs were found.'); | |
} | |
throw new Error('Stack has no outputs.'); | |
} | |
return outputs; | |
} | |
catch (err) { | |
console.error('Error:', err); | |
throw err; | |
} | |
} | |
// Updated handler function to use async/await | |
exports.handler = async function(event, context) { | |
console.log(JSON.stringify(event, null, 2)); | |
if (event.RequestType === 'Delete') { | |
await sendResponse(event, context, 'SUCCESS'); | |
return; | |
} | |
try { | |
const data = await getStackOutputs(event.ResourceProperties); | |
await sendResponse(event, context, 'SUCCESS', data); | |
} | |
catch (err) { | |
await sendResponse(event, context, 'FAILED', null, err); | |
} | |
}; | |
async function sendResponse(event, context, status, data, err) { | |
let reason = err ? err.toString() + '; ' : ''; | |
let responseBody = { | |
StackId: event.StackId, | |
RequestId: event.RequestId, | |
LogicalResourceId: event.LogicalResourceId, | |
PhysicalResourceId: 'getStackOutputs-' + event.ResourceProperties.StackName, | |
Status: status, | |
Reason: reason + 'See details in CloudWatch Log: ' + context.logStreamName, | |
Data: data | |
}; | |
console.log("RESPONSE BODY:\n", JSON.stringify(responseBody, null, 2)); | |
const parsedUrl = url.parse(event.ResponseURL); | |
const httpRequest = new HttpRequest({ | |
hostname: parsedUrl.hostname, | |
port: 443, | |
path: parsedUrl.path, | |
method: 'PUT', | |
headers: { | |
'Content-Type': '', | |
'Content-Length': JSON.stringify(responseBody).length | |
}, | |
body: JSON.stringify(responseBody) | |
}); | |
try { | |
const httpHandler = new NodeHttpHandler(); | |
const { response } = await httpHandler.handle(httpRequest); | |
console.log('STATUS:', response.statusCode); | |
console.log('HEADERS:', JSON.stringify(response.headers)); | |
context.done(null, data); | |
} | |
catch (error) { | |
console.error("sendResponse Error:", error); | |
context.done(error); | |
} | |
} | |
if (require.main === module) { | |
const fs = require('fs'); | |
console.log('getStackOutputs called directly.'); | |
if (process.argv.length < 3) { | |
usageExit(); | |
} | |
let properties = null; | |
try { | |
properties = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); | |
} | |
catch (error) { | |
console.error('Invalid JSON:', error); | |
usageExit(); | |
} | |
getStackOutputs(properties).then(data => { | |
console.log('Result:', data); | |
}).catch(err => { | |
console.error('Error:', err); | |
}); | |
} | |
function usageExit() { | |
const path = require('path'); | |
console.log('Usage:', path.basename(process.argv[1]), 'JSON file.'); | |
process.exit(1); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"AWSTemplateFormatVersion": "2010-09-09", | |
"Parameters": { | |
"HelloMessage": { | |
"Default": "Hello World", | |
"Type": "String" | |
}, | |
"GoodbyeMessage": { | |
"Default": "Goodbye World", | |
"Type": "String" | |
}, | |
"Enable": { | |
"Default": "false", | |
"Type": "String" | |
} | |
}, | |
"Conditions": { | |
"Enable?": { | |
"Fn::Equals": [ | |
{ | |
"Ref": "Enable" | |
}, | |
"true" | |
] | |
} | |
}, | |
"Resources": { | |
"Ec2Instance": { | |
"Condition": "Enable?", | |
"Type": "AWS::EC2::Instance", | |
"Properties": { | |
"ImageId": "ami-f9dd458a" | |
} | |
} | |
}, | |
"Outputs": { | |
"HelloMessage": { | |
"Value": { | |
"Ref": "HelloMessage" | |
} | |
}, | |
"GoodbyeMessage": { | |
"Value": { | |
"Ref": "GoodbyeMessage" | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"AWSTemplateFormatVersion": "2010-09-09", | |
"Parameters": { | |
"ExternalStackName": { | |
"Type": "String" | |
} | |
}, | |
"Resources": { | |
"getStackOutputs": { | |
"Type": "Custom::getStackOutputs", | |
"Properties": { | |
"ServiceToken": { | |
"Fn::Join": [ | |
":", | |
[ | |
"arn:aws:lambda", | |
{ | |
"Ref": "AWS::Region" | |
}, | |
{ | |
"Ref": "AWS::AccountId" | |
}, | |
"function", | |
"getStackOutputs" | |
] | |
] | |
}, | |
"StackName": { | |
"Ref": "ExternalStackName" | |
} | |
} | |
} | |
}, | |
"Outputs": { | |
"Message": { | |
"Value": { | |
"Fn::GetAtt": [ | |
"getStackOutputs", | |
"HelloMessage" | |
] | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"AWSTemplateFormatVersion": "2010-09-09", | |
"Parameters": { | |
"ExternalStackName": { | |
"Type": "String" | |
}, | |
"OutputFilter": { | |
"Default": "GoodbyeMessage", | |
"Type": "String" | |
} | |
}, | |
"Resources": { | |
"getStackOutputs": { | |
"Type": "Custom::getStackOutputs", | |
"Properties": { | |
"ServiceToken": { | |
"Fn::Join": [ | |
":", | |
[ | |
"arn:aws:lambda", | |
{ | |
"Ref": "AWS::Region" | |
}, | |
{ | |
"Ref": "AWS::AccountId" | |
}, | |
"function", | |
"getStackOutputs" | |
] | |
] | |
}, | |
"StackName": { | |
"Ref": "ExternalStackName" | |
}, | |
"Filter": [ | |
{ | |
"Ref": "OutputFilter" | |
} | |
] | |
} | |
} | |
}, | |
"Outputs": { | |
"Message": { | |
"Value": { | |
"Fn::GetAtt": [ | |
"getStackOutputs", | |
"GoodbyeMessage" | |
] | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment