Skip to content

Instantly share code, notes, and snippets.

@dduvnjak
Forked from kwilczynski/getStackOutputs.js
Last active April 1, 2024 09:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dduvnjak/de00bc28918a2dc9906520b00dcc411e to your computer and use it in GitHub Desktop.
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.
'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);
}
{
"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"
}
}
}
}
{
"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"
]
}
}
}
}
{
"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