Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.git
.git
tester/
test/
11 changes: 7 additions & 4 deletions factory.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict';

const config = require('leo-config');
const AWS = require('aws-sdk');
const { fromIni } = require("@aws-sdk/credential-providers");
const merge = require('lodash.merge');
const requireFn = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;


const leoaws = {
cloudformation: require('./lib/cloudformation'),
Expand All @@ -12,10 +14,10 @@ const leoaws = {
sqs: require('./lib/sqs'),
};

function factory (service, options) {
function factory(service, options) {
const configuration = config.leoaws;
if (configuration && configuration.profile && !configuration.credentials) {
configuration.credentials = new AWS.SharedIniFileCredentials({
configuration.credentials = fromIni({
profile: configuration.profile,
role: configuration.role,
});
Expand All @@ -30,8 +32,9 @@ function factory (service, options) {
return leoaws[lowerService](configuration);
} else {
// return a configured AWS service
let serviceLib = requireFn("@aws-sdk/client-" + service.replace(/[A-Z]/g, (a) => "-" + a.toLowerCase()).replace(/^-/, ""));
return {
_service: new AWS[service](configuration),
_service: new serviceLib[service](configuration),
};
}
}
Expand Down
6 changes: 3 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const dynamodb = require("./lib/dynamodb");
const kms = require("./lib/kms");
const secrets = require("./lib/secretsmanager");
const sqs = require("./lib/sqs");
const AWS = require('aws-sdk');
const { fromIni } = require("@aws-sdk/credential-providers");

/**
*
Expand All @@ -15,12 +15,12 @@ const AWS = require('aws-sdk');
*/
function build(configuration) {
if (configuration.profile && !configuration.credentials) {
configuration.credentials = new AWS.SharedIniFileCredentials({
configuration.credentials = fromIni({
profile: configuration.profile,
role: configuration.role
});
}

return Object.assign((config) => {
return new build(config)
}, {
Expand Down
175 changes: 116 additions & 59 deletions lib/cloudformation.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
"use strict";
const aws = require('aws-sdk');
const { CloudFormation, waitUntilChangeSetCreateComplete, GetTemplateCommand, ListStackResourcesCommand, DescribeStackEventsCommand, DescribeStacksCommand, DescribeChangeSetCommand, waitUntilStackCreateComplete, waitUntilStackUpdateComplete, ExecuteChangeSetCommand } = require('@aws-sdk/client-cloudformation');
const readline = require('readline');

module.exports = function(configuration) {
let service = new aws.CloudFormation(configuration);
let service = new CloudFormation(configuration);
return {
_service: service,
getStackResources: function(stack) {
return service.listStackResources({
const command = new ListStackResourcesCommand({
StackName: stack
}).promise().then((data) => {
});
return service.send(command)
.then((data) => {
if (data.NextToken) {
console.log("We need to deal with next token");
}
var resources = {};
data.StackResourceSummaries.map((resource) => {
resources[resource.LogicalResourceId] = {
type: resource.ResourceType,
id: resource.PhysicalResourceId,
name: resource.LogicalResourceId
Type: resource.ResourceType,
Id: resource.PhysicalResourceId,
Name: resource.LogicalResourceId
};
});

return resources;
});
},
get: function(stack, opts) {
return service.getTemplate(Object.assign({
const command = new GetTemplateCommand(Object.assign({
StackName: stack,
}, opts)).promise().then(data => JSON.parse(data.TemplateBody));
}, opts));

return service.send(command)
.then((data) => {
return JSON.parse(data.TemplateBody);
})
},
/**
* Create a run a changeset
Expand All @@ -38,7 +45,7 @@ module.exports = function(configuration) {
* @param options (Options for this function)
* @returns {Promise<PromiseResult<CloudFormation.CreateChangeSetOutput, AWSError>>}
*/
runChangeSet: function(stack, file, opts, options = {}) {
runChangeSet: async function(stack, file, opts, options = {}) {
let updateOpts = Object.assign({}, opts);
if (updateOpts.Parameters) {
updateOpts.Parameters = updateOpts.Parameters.map(param => ({
Expand All @@ -58,23 +65,49 @@ module.exports = function(configuration) {
"CAPABILITY_IAM",
]
}, updateOpts);
return service.createChangeSet(params).promise().then(data => {
changeSetId = data.Id;
service.api.waiters["changeSetCreateComplete"].delay = 5;
return this.waitFor("changeSetCreateComplete", {
ChangeSetName: changeSetId
});
return service.createChangeSet(params)
.then(async data => {
const changeSetId = data.Id;
const stackId = data.StackId;

const waitConfig = {
minDelay: 5, // Minimum delay in seconds between checks
maxDelay: 30, // Maximum delay in seconds between checks
maxWaitTime: 300 // Maximum time in seconds to wait for the operation to complete
};

try {
const result = await waitUntilChangeSetCreateComplete(
{
client: service, // Pass the CloudFormation client here
maxWaitTime: waitConfig.maxWaitTime,
minDelay: waitConfig.minDelay,
maxDelay: waitConfig.maxDelay
},
{
ChangeSetName: changeSetId, // Use the ChangeSet ID
StackName: stackId, // Use the Stack ID
IncludeNestedStacks: true // Optional, if you want nested stack details
}
);

console.log("--- ChangeSet creation complete ---");
return result;
} catch (err) {
console.error("Error waiting for ChangeSet creation to complete", err);
throw err; // Re-throw the error to handle it later if needed
}
}).then(data => {
data = data.reason
changeSetId = data.ChangeSetId;
console.log(changeSetId);

function rightPad(val, count) {
return (val + " ".repeat(count)).slice(0, count) + " ";
}
console.log(`${rightPad("Action",30)}${rightPad("Logical ID",30)}${rightPad("Physical ID",30)}${rightPad("Resource Type",30)}${rightPad("Replacement",30)}`);
console.log(`${rightPad("Action", 30)}${rightPad("Logical ID", 30)}${rightPad("Physical ID", 30)}${rightPad("Resource Type", 30)}${rightPad("Replacement", 30)}`);
data.Changes.map(change => {
change = change.ResourceChange;
console.log(`${rightPad(change.Action,30)}${rightPad(change.LogicalResourceId,30)}${rightPad(change.PhysicalResourceId,30)}${rightPad(change.ResourceType,30)}${rightPad(change.Replacement,30)}`);
console.log(`${rightPad(change.Action, 30)}${rightPad(change.LogicalResourceId, 30)}${rightPad(change.PhysicalResourceId, 30)}${rightPad(change.ResourceType, 30)}${rightPad(change.Replacement, 30)}`);
});
const rl = readline.createInterface({
input: process.stdin,
Expand Down Expand Up @@ -105,10 +138,12 @@ module.exports = function(configuration) {
} else if (err.StatusReason) {
throw new Error(err.StatusReason);
} else {
return service.describeChangeSet({
const command = new DescribeChangeSetCommand({
ChangeSetName: changeSetId,
StackName: stack
}).promise().then(cs => {
});
return service.send(command)
.then(cs => {
throw cs;
}).catch((err2) => {
if (err2.StatusReason) {
Expand All @@ -133,17 +168,27 @@ module.exports = function(configuration) {
rl.close();
let start = Date.now();
console.log("Executing Change Set");
resolve(service.executeChangeSet({
ChangeSetName: changeSetId
}).promise().then(data => {
service.api.waiters["stackUpdateComplete"].delay = 10;
return this.waitFor("stackUpdateComplete", {
StackName: stack
});
const input = {
ChangeSetName: changeSetId,
StackName: stack
};
const command = new ExecuteChangeSetCommand(input);
resolve(service.send(command)
.then(data => {
const waitConfig = {
minDelay: 5, // Minimum delay in seconds between checks
maxDelay: 30, // Maximum delay in seconds between checks
maxWaitTime: 900 // Maximum time in seconds to wait for the operation to complete
};
return waitUntilStackUpdateComplete(
{ client: service, maxWaitTime: waitConfig.maxWaitTime }, // Waiter configuration
{ StackName: stack } // Command input
);

}).catch(err => {
return service.describeStackEvents({
StackName: stack
}).promise().then(data => {
}).then(data => {
let messages = [];
let addLinkMessage = false;
for (let i = 0; i < data.StackEvents.length; i++) {
Expand All @@ -156,9 +201,6 @@ module.exports = function(configuration) {
if (addLinkMessage) {
messages.push("Linked Stack(s) are missing export values. Are your stack names correct?")
}
if (messages.length == 0) {
message.push("Unknown Error")
}

throw {
StatusReason: messages.join(". ")
Expand All @@ -170,7 +212,7 @@ module.exports = function(configuration) {
getStackErrorStatus: function(stack, start) {
return service.describeStackEvents({
StackName: stack
}).promise().then(data => {
}).then(data => {
let messages = [];
let addLinkMessage = false;
for (let i = 0; i < data.StackEvents.length; i++) {
Expand All @@ -183,9 +225,6 @@ module.exports = function(configuration) {
if (addLinkMessage) {
messages.push("Linked Stack(s) are missing export values. Are your stack names correct?")
}
if (messages.length == 0) {
messages.push("Unknown Error")
}

throw {
StatusReason: messages.join(". ")
Expand All @@ -201,26 +240,33 @@ module.exports = function(configuration) {
ParameterValue: param.ParameterValue
}));
}
console.log(updateOpts);
// console.log(updateOpts);
let start = Date.now();
return service.updateStack(Object.assign({
StackName: stack,
TemplateURL: file,
Capabilities: [
"CAPABILITY_IAM",
]
}, updateOpts)).promise().then(data => {
service.api.waiters["stackUpdateComplete"].delay = 10;
return this.waitFor("stackUpdateComplete", {
StackName: stack
});
}, updateOpts))
.then(data => {
const waitConfig = {
minDelay: 5, // Minimum delay in seconds between checks
maxDelay: 30, // Maximum delay in seconds between checks
maxWaitTime: 900 // Maximum time in seconds to wait for the operation to complete
};
return waitUntilStackUpdateComplete(
{ client: service, maxWaitTime: waitConfig.maxWaitTime }, // Waiter configuration
{ StackName: stack } // Command input
);

}).catch(err => {
if (err.message.match(/^Stack.*does not exist/)) {
return this.createStack(stack, file, updateOpts.Parameters, opts, true, true);
} else {
return service.describeStackEvents({
StackName: stack
}).promise().then(data => {
}).then(data => {
let messages = [];
let addLinkMessage = false;
for (let i = 0; i < data.StackEvents.length; i++) {
Expand All @@ -233,9 +279,7 @@ module.exports = function(configuration) {
if (addLinkMessage) {
messages.push("Linked Stack(s) are missing export values. Are your stack names correct?")
}
if (messages.length == 0) {
messages.push("Unknown Error")
}

throw {
StatusReason: messages.join(", ")
};
Expand All @@ -252,10 +296,10 @@ module.exports = function(configuration) {
describeStackResources: function(stack) {
return service.describeStackResources({
StackName: stack
}).promise().then(data => data.StackResources);
}).then(data => data.StackResources);
},
waitFor: function(action, params) {
return service.waitFor(action, params).promise();
return service.waitFor(action, params);
},
createStack: async function createStack(name, template, paramaters = [], waitFor = true, describeStack = true) {
let templateBody;
Expand All @@ -276,21 +320,33 @@ module.exports = function(configuration) {
OnFailure: "DELETE",
[templateBody]: template,
Parameters: paramaters
}).promise().then(stackInfo => {
}).then(stackInfo => {
createInfo = stackInfo;
return stackInfo;
});
if (waitFor || describeStack) {
service.api.waiters["stackCreateComplete"].delay = 10;
promise = promise.then(() => this.waitFor("stackCreateComplete", {
StackName: name
})).then(waitdata => ({
stack: name,
region: configuration.region,
details: waitdata.Stacks[0]
}));
promise = promise
.then(() => {
const waitConfig = {
minDelay: 5, // Minimum delay in seconds between checks
maxDelay: 30, // Maximum delay in seconds between checks
maxWaitTime: 300 // Maximum time in seconds to wait for the operation to complete
};
return waitUntilStackCreateComplete(
{ client: service, maxWaitTime: waitConfig.maxWaitTime }, // Waiter configuration
{ StackName: name } // Command input
);

}).then(waitdata => {
return {
stack: name,
region: configuration.region,
details: waitdata.reason.Stacks[0]
}
});
} else if (describeStack) {
promise = promise.then(() => this.describeStackResources(name)).then(data => ({
promise = promise.then(() => this.describeStackResources(name))
.then(data => ({
stack: name,
region: configuration.region,
details: {
Expand All @@ -299,11 +355,12 @@ module.exports = function(configuration) {
}));
}
return promise.catch((waitErr) => this.getStackErrorStatus(createInfo.StackId || name, createStart).catch(err => {
console.log("Error creating stack", err);
if (err.StatusReason)
throw new Error(err.StatusReason);
else
throw waitErr;
}));
}
};
};
};
Loading