Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cfn-stack-event-stream

Package Overview
Dependencies
Maintainers
38
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cfn-stack-event-stream - npm Package Compare versions

Comparing version 0.0.7 to 1.0.0

LICENSE.txt

168

index.js

@@ -1,97 +0,111 @@

var Readable = require('stream').Readable;
'use strict';
const Readable = require('stream').Readable;
module.exports = function(cfn, stackName, options) {
options = options || {};
options = options || {};
var stream = new Readable({objectMode: true}),
pollInterval = options.pollInterval || 1000,
describing = false,
complete = false,
stackId = stackName,
seen = {},
events = [],
push = stream.push.bind(stream);
const stream = new Readable({ objectMode: true }),
pollInterval = options.pollInterval || 10000,
seen = {},
push = stream.push.bind(stream);
if (options.lastEventId) {
seen[options.lastEventId] = true;
}
let describing = false,
complete = false,
stackId = stackName,
events = [];
stream._read = function() {
if (describing || complete) return;
describeStack();
};
function describeEvents(nextToken) {
describing = true;
// Describe stacks using stackId (ARN) as CF stacks are actually
// not unique by name.
cfn.describeStackEvents({StackName: stackId, NextToken: nextToken}, function(err, data) {
describing = false;
if (options.lastEventId) {
seen[options.lastEventId] = true;
}
if (err) return stream.emit('error', err);
stream._read = function() {
if (describing || complete) return;
describeStack();
};
for (var i = 0; i < (data.StackEvents || []).length; i++) {
var event = data.StackEvents[i];
function describeEvents(nextToken) {
describing = true;
// Describe stacks using stackId (ARN) as CF stacks are actually
// not unique by name.
cfn.describeStackEvents({ StackName: stackId, NextToken: nextToken }, (err, data) => {
describing = false;
// Assuming StackEvents are in strictly reverse chronological order.
// If we get to what we've seen already we don't need to go on to the
// next page.
if (seen[event.EventId])
break;
if (err) return stream.emit('error', err);
events.push(event);
seen[event.EventId] = true;
let i;
for (i = 0; i < (data.StackEvents || []).length; i++) {
const event = data.StackEvents[i];
// If we reach a user initiated event assume this event is the
// initiating event the caller intends to monitor.
if (event.LogicalResourceId === stackName &&
event.ResourceType === 'AWS::CloudFormation::Stack' &&
event.ResourceStatusReason === 'User Initiated') {
break;
}
}
// Assuming StackEvents are in strictly reverse chronological order,
// stop reading events once we reach one we've seen already.
if (seen[event.EventId])
break;
if (i === (data.StackEvents || []).length && data.NextToken) {
describeEvents(data.NextToken);
// Collect new events in an array and mark them as "seen".
events.push(event);
seen[event.EventId] = true;
} else if (complete) {
events.reverse().forEach(push);
push(null);
// If we reach a user initiated event assume this event is the
// initiating event the caller intends to monitor.
if (event.LogicalResourceId === stackName &&
event.ResourceType === 'AWS::CloudFormation::Stack' &&
event.ResourceStatusReason === 'User Initiated') {
break;
}
}
} else {
describeStack();
events.reverse().forEach(push);
events = [];
}
}).on('retry', function(res) {
// Force a minimum 5s retry delay.
res.error.retryDelay = Math.max(5000, res.error.retryDelay||5000);
stream.emit('retry', res.error);
});
}
// If we did not find an event on this page we had already seen, paginate.
if (i === (data.StackEvents || []).length && data.NextToken) {
describeEvents(data.NextToken);
}
function describeStack() {
describing = true;
cfn.describeStacks({StackName: stackId}, function(err, data) {
describing = false;
// We know that the update is complete, whatever we have in the events
// array represents the last few events to stream.
else if (complete) {
events.reverse().forEach(push);
push(null);
if (err) return stream.emit('error', err);
if (!data.Stacks.length) return stream.emit('error', new Error('Could not describe stack: ' + stackName));
}
stackId = data.Stacks[0].StackId;
// The update is not complete, and there aren't any new events or more
// pages to scan. DescribeStack in order to check again to see if the
// update has completed.
else {
setTimeout(describeStack, pollInterval);
events.reverse().forEach(push);
events = [];
}
}).on('retry', (res) => {
// Force a minimum 5s retry delay.
res.error.retryDelay = Math.max(5000, res.error.retryDelay || 5000);
stream.emit('retry', res.error);
});
}
if (/COMPLETE$/.test(data.Stacks[0].StackStatus)) {
complete = true;
describeEvents();
} else {
setTimeout(describeEvents, pollInterval);
}
}).on('retry', function(res) {
// Force a minimum 5s retry delay.
res.error.retryDelay = Math.max(5000, res.error.retryDelay||5000);
stream.emit('retry', res.error);
});
}
function describeStack() {
describing = true;
cfn.describeStacks({ StackName: stackId }, (err, data) => {
describing = false;
return stream;
if (err) return stream.emit('error', err);
if (!data.Stacks.length) return stream.emit('error', new Error('Could not describe stack: ' + stackName));
stackId = data.Stacks[0].StackId;
if (/COMPLETE$/.test(data.Stacks[0].StackStatus)) {
complete = true;
describeEvents();
} else {
setTimeout(describeEvents, pollInterval);
}
}).on('retry', (res) => {
// Force a minimum 5s retry delay.
res.error.retryDelay = Math.max(5000, res.error.retryDelay || 5000);
stream.emit('retry', res.error);
});
}
return stream;
};
{
"name": "cfn-stack-event-stream",
"version": "0.0.7",
"version": "1.0.0",
"description": "A readable stream of CloudFormation stack events",
"main": "index.js",
"scripts": {
"test": "tape test/*.test.js"
"lint": "eslint test index.js",
"test": "nyc tape test/*.test.js | tap-spec && npm run lint",
"coverage": "nyc --reporter html tape test/*.test.js && opener coverage/index.html"
},

@@ -12,7 +14,16 @@ "author": "John Firebaugh <john@mapbox.com>",

"dependencies": {
"aws-sdk": "^2.2.0"
"aws-sdk": "^2.335.0"
},
"devDependencies": {
"tape": "~4.4.0"
"@mapbox/eslint-config-mapbox": "^1.2.1",
"eslint": "^5.7.0",
"eslint-plugin-node": "^7.0.1",
"nyc": "^13.1.0",
"opener": "^1.5.1",
"tap-spec": "^5.0.0",
"tape": "^4.9.1"
},
"eslintConfig": {
"extends": "@mapbox/eslint-config-mapbox"
}
}

@@ -1,153 +0,155 @@

var test = require('tape');
var Stream = require('../.');
var AWS = require('aws-sdk');
'use strict';
var cfn = new AWS.CloudFormation({region: 'us-east-1'});
const test = require('tape');
const Stream = require('../.');
const AWS = require('aws-sdk');
test('emits an error for a non-existent stack', function (assert) {
Stream(cfn, 'cfn-stack-event-stream-test')
.on('data', function (e) {})
.on('error', function (err) {
assert.ok(err);
assert.end();
});
});
const cfn = new AWS.CloudFormation({ region: 'us-east-1' });
test('streams events until stack is complete', {timeout: 120000}, function (assert) {
var stackName = 'cfn-stack-event-stream-test-create';
const template = {
'AWSTemplateFormatVersion': '2010-09-09',
'Description': 'cfn-stack-event-stream-test',
'Parameters': {
'TestParameter': {
'Description': 'A parameter for testing',
'Type': 'String',
'Default': 'TestParameterValue'
}
},
'Resources': {
'TestTopic': {
'Type': 'AWS::SNS::Topic',
'Properties': {
'DisplayName': { 'Ref': 'TestParameter' }
}
}
}
};
cfn.createStack({
StackName: stackName,
TemplateBody: JSON.stringify(template)
}, function (err) {
if (err) {
assert.ifError(err);
assert.end();
return;
}
var events = [
'CREATE_IN_PROGRESS AWS::CloudFormation::Stack',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_COMPLETE AWS::SNS::Topic',
'CREATE_COMPLETE AWS::CloudFormation::Stack'
];
Stream(cfn, stackName)
.on('data', function (e) {
assert.equal(events[0], e.ResourceStatus + ' ' + e.ResourceType, e.ResourceStatus + ' ' + e.ResourceType);
events.shift();
})
.on('end', function () {
updateStack();
});
test('emits an error for a non-existent stack', (assert) => {
Stream(cfn, 'cfn-stack-event-stream-test')
.on('data', () => {})
.on('error', (err) => {
assert.ok(err);
assert.end();
});
});
function updateStack() {
// Modify template for update.
var templateUpdated = JSON.parse(JSON.stringify(template));
templateUpdated.Resources.NewTopic = {
"Type": "AWS::SNS::Topic",
"Properties" : {
"DisplayName": "Topic2"
}
};
test('streams events until stack is complete', { timeout: 120000 }, (assert) => {
const stackName = 'cfn-stack-event-stream-test-create';
cfn.updateStack({
StackName: stackName,
TemplateBody: JSON.stringify(templateUpdated)
}, function (err) {
if (err) {
assert.ifError(err);
assert.end();
return;
}
var events = [
'UPDATE_IN_PROGRESS AWS::CloudFormation::Stack',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_COMPLETE AWS::SNS::Topic',
'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack',
'UPDATE_COMPLETE AWS::CloudFormation::Stack'
];
Stream(cfn, stackName)
.on('data', function (e) {
assert.equal(events[0], e.ResourceStatus + ' ' + e.ResourceType, e.ResourceStatus + ' ' + e.ResourceType);
events.shift();
})
.on('end', function () {
deleteStack();
});
});
cfn.createStack({
StackName: stackName,
TemplateBody: JSON.stringify(template)
}, (err) => {
if (err) {
assert.ifError(err);
assert.end();
return;
}
const events = [
'CREATE_IN_PROGRESS AWS::CloudFormation::Stack',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_COMPLETE AWS::SNS::Topic',
'CREATE_COMPLETE AWS::CloudFormation::Stack'
];
Stream(cfn, stackName)
.on('data', (e) => {
assert.equal(events[0], e.ResourceStatus + ' ' + e.ResourceType, e.ResourceStatus + ' ' + e.ResourceType);
events.shift();
})
.on('end', () => {
updateStack();
});
});
function deleteStack() {
cfn.deleteStack({StackName: stackName}, function(err) {
assert.ifError(err);
assert.end();
function updateStack() {
// Modify template for update.
const templateUpdated = JSON.parse(JSON.stringify(template));
templateUpdated.Resources.NewTopic = {
'Type': 'AWS::SNS::Topic',
'Properties' : {
'DisplayName': 'Topic2'
}
};
cfn.updateStack({
StackName: stackName,
TemplateBody: JSON.stringify(templateUpdated)
}, (err) => {
if (err) {
assert.ifError(err);
assert.end();
return;
}
const events = [
'UPDATE_IN_PROGRESS AWS::CloudFormation::Stack',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_IN_PROGRESS AWS::SNS::Topic',
'CREATE_COMPLETE AWS::SNS::Topic',
'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack',
'UPDATE_COMPLETE AWS::CloudFormation::Stack'
];
Stream(cfn, stackName)
.on('data', (e) => {
assert.equal(events[0], e.ResourceStatus + ' ' + e.ResourceType, e.ResourceStatus + ' ' + e.ResourceType);
events.shift();
})
.on('end', () => {
deleteStack();
});
}
});
}
function deleteStack() {
cfn.deleteStack({ StackName: stackName }, (err) => {
assert.ifError(err);
assert.end();
});
}
});
test('streams events during stack deletion', {timeout: 60000}, function (assert) {
var stackName = 'cfn-stack-event-stream-test-delete',
lastEventId;
test('streams events during stack deletion', { timeout: 60000 }, (assert) => {
const stackName = 'cfn-stack-event-stream-test-delete';
let lastEventId;
cfn.createStack({
StackName: stackName,
TemplateBody: JSON.stringify(template)
}, function (err, stack) {
if (err) {
cfn.createStack({
StackName: stackName,
TemplateBody: JSON.stringify(template)
}, (err, stack) => {
if (err) {
assert.ifError(err);
assert.end();
return;
}
Stream(cfn, stackName)
.on('data', (e) => {
lastEventId = e.EventId;
})
.on('end', () => {
cfn.deleteStack({ StackName: stackName }, (err) => {
if (err) {
assert.ifError(err);
assert.end();
return;
}
Stream(cfn, stackName)
.on('data', function (e) {
lastEventId = e.EventId;
}
const events = [
'DELETE_IN_PROGRESS AWS::CloudFormation::Stack',
'DELETE_IN_PROGRESS AWS::SNS::Topic',
'DELETE_COMPLETE AWS::SNS::Topic',
'DELETE_COMPLETE AWS::CloudFormation::Stack'
];
Stream(cfn, stack.StackId, { lastEventId: lastEventId })
.on('data', (e) => {
assert.equal(events[0], e.ResourceStatus + ' ' + e.ResourceType, e.ResourceStatus + ' ' + e.ResourceType);
events.shift();
})
.on('end', function () {
cfn.deleteStack({StackName: stackName}, function(err) {
if (err) {
assert.ifError(err);
assert.end();
return;
}
var events = [
'DELETE_IN_PROGRESS AWS::CloudFormation::Stack',
'DELETE_IN_PROGRESS AWS::SNS::Topic',
'DELETE_COMPLETE AWS::SNS::Topic',
'DELETE_COMPLETE AWS::CloudFormation::Stack'
];
Stream(cfn, stack.StackId, {lastEventId: lastEventId})
.on('data', function (e) {
assert.equal(events[0], e.ResourceStatus + ' ' + e.ResourceType, e.ResourceStatus + ' ' + e.ResourceType);
events.shift();
})
.on('end', function () {
assert.end();
});
});
.on('end', () => {
assert.end();
});
});
});
});
});
});
var template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "cfn-stack-event-stream-test",
"Parameters": {
"TestParameter": {
"Description": "A parameter for testing",
"Type": "String",
"Default": "TestParameterValue"
}
},
"Resources": {
"TestTopic" : {
"Type": "AWS::SNS::Topic",
"Properties" : {
"DisplayName": { "Ref": "TestParameter" }
}
}
}
};

@@ -1,61 +0,64 @@

var test = require('tape');
var Stream = require('../.');
var AWS = require('aws-sdk');
'use strict';
var cfn = new AWS.CloudFormation({region: 'us-east-1'});
const test = require('tape');
const Stream = require('../.');
const AWS = require('aws-sdk');
test('handles throttle events', {timeout: 60000}, function (assert) {
var events = [],
managed = [],
stackName = 'cfn-stack-event-stream-test-throttle';
const cfn = new AWS.CloudFormation({ region: 'us-east-1' });
cfn.createStack({
StackName: stackName,
TemplateBody: JSON.stringify(template)
}, function (err) {
assert.ifError(err);
const template = {
'AWSTemplateFormatVersion': '2010-09-09',
'Description': 'cfn-stack-event-stream-test',
'Resources': {
'Test': {
'Type': 'AWS::AutoScaling::LaunchConfiguration',
'Properties': {
// Hammer CF API to trigger throttling.
var interval = setInterval(function() {
cfn.describeStacks({StackName: stackName}, function(err, data) {});
}, 100);
}
}
}
};
Stream(cfn, stackName)
.on('retry', function(err) {
assert.ok(err, 'retry');
managed.push(err);
clearInterval(interval);
})
.on('data', function (e) {
assert.ok(e, 'data');
events.push(e);
})
.on('end', function () {
cfn.deleteStack({StackName: stackName}, function(err) {
assert.ifError(err);
assert.deepEqual(events.map(function (e) { return e.ResourceStatus; }), [
'CREATE_IN_PROGRESS',
'CREATE_FAILED',
'ROLLBACK_IN_PROGRESS',
'DELETE_COMPLETE',
'ROLLBACK_COMPLETE'
]);
assert.equal(managed.length > 0, true);
assert.end();
});
});
});
});
var template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "cfn-stack-event-stream-test",
"Resources": {
"Test": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
test('handles throttle events', { timeout: 60000 }, (assert) => {
const events = [],
managed = [],
stackName = 'cfn-stack-event-stream-test-throttle';
}
}
}
};
cfn.createStack({
StackName: stackName,
TemplateBody: JSON.stringify(template)
}, (err) => {
assert.ifError(err);
// Hammer CF API to trigger throttling.
const interval = setInterval(() => {
cfn.describeStacks({ StackName: stackName }, () => {});
}, 100);
Stream(cfn, stackName)
.on('retry', (err) => {
assert.ok(err, 'retry');
managed.push(err);
clearInterval(interval);
})
.on('data', (e) => {
assert.ok(e, 'data');
events.push(e);
})
.on('end', () => {
cfn.deleteStack({ StackName: stackName }, (err) => {
assert.ifError(err);
assert.deepEqual(events.map((e) => { return e.ResourceStatus; }), [
'CREATE_IN_PROGRESS',
'CREATE_FAILED',
'ROLLBACK_IN_PROGRESS',
'DELETE_COMPLETE',
'ROLLBACK_COMPLETE'
]);
assert.equal(managed.length > 0, true);
assert.end();
});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc