Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit a442fb0

Browse files
authored
Merge pull request #3 from daveshepherd/master
Fix issue where instance-terminator-group tag values were ignored
2 parents ca2f333 + 4ebe3a4 commit a442fb0

File tree

2 files changed

+202
-2
lines changed

2 files changed

+202
-2
lines changed

src/instance_terminator.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function terminateOldestInstanceFromEachGrouped(autoscalingGroups) {
7777
instanceTerminatorGroupNames.forEach(function(instanceTerminatorGroupName) {
7878
promises.push(new Promise(function(resolve) {
7979

80-
const matchingAutoscalingGroups = autoscalingGroups.filter(item => containsTag(item['Tags'], 'instance-terminator-group', instanceTerminatorGroupName));
80+
const matchingAutoscalingGroups = autoscalingGroups.filter(item => containsTagValue(item['Tags'], 'instance-terminator-group', instanceTerminatorGroupName));
8181
console.log('Attempting to terminate instance from: ' + instanceTerminatorGroupName);
8282

8383
if (matchingAutoscalingGroups.length < 2) {
@@ -118,7 +118,7 @@ function terminateOldestInstanceFrom(matchingAutoscalingGroups) {
118118
const instances = autoscalingGroup['Instances'];
119119
const healthyInstances = instances.filter(instance => instance['LifecycleState'] == 'InService' && instance['HealthStatus'] == 'Healthy');
120120

121-
if (healthyInstances.length < desiredCapacity) {
121+
if (healthyInstances.length < desiredCapacity || desiredCapacity < 1) {
122122
console.log('Too few healthy instances, ignoring.')
123123
var response = {
124124
result: 'not enough healthy instances in group'
@@ -177,4 +177,11 @@ function containsTag(tags, tagKey) {
177177
if (tag['Key'] == tagKey)
178178
return true;
179179
})
180+
}
181+
182+
function containsTagValue(tags, tagKey, tagValue) {
183+
return tags.some(function (tag) {
184+
if (tag['Key'] == tagKey && tag['Value'] == tagValue)
185+
return true;
186+
})
180187
}

test/unit/instance_terminator.test.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,114 @@ describe( 'instance-terminator', function() {
307307
expect(terminateInstance.notCalled, 'terminate instance should not be called').to.be.true;
308308
});
309309
});
310+
311+
it( `should not terminate an instance across grouped asgs if the group has no instances`, function() {
312+
const terminateInstance = sinon.spy();
313+
const lookupOldestInstance = sinon.spy();
314+
const myLambda = proxyquire( '../../src/instance_terminator', {
315+
'./aws/autoscaling_handler': {
316+
findAutoscalingGroupsByTag: (tagKey, tagValue) => {
317+
expect(tagKey).to.equal('can-be-terminated');
318+
expect(tagValue).to.equal('true');
319+
return new Promise(function (resolve) {
320+
resolve(describeAutoscalingGroupsResponse_withGroupedAsgsWithNoInstances);
321+
});
322+
},
323+
terminateInstance: terminateInstance
324+
},
325+
'./aws/ec2_handler': {
326+
lookupOldestInstance: lookupOldestInstance
327+
},
328+
});
329+
330+
return LambdaTester(myLambda.handler)
331+
.event()
332+
.expectResult((result) => {
333+
expect(result).to.have.lengthOf(1);
334+
expect(result).to.have.deep.members([{instanceTerminatorGroupName: 'my-test-group', result: 'not enough healthy instances in group'}]);
335+
expect(lookupOldestInstance.notCalled, 'lookupOldestInstance should not be called').to.be.true;
336+
expect(terminateInstance.notCalled, 'terminate instance should not be called').to.be.true;
337+
});
338+
});
339+
340+
it( `should terminate one instance when there is more than one grouped asgs`, function() {
341+
const terminateInstance = sinon.spy();
342+
const myLambda = proxyquire( '../../src/instance_terminator', {
343+
'./aws/autoscaling_handler': {
344+
findAutoscalingGroupsByTag: (tagKey, tagValue) => {
345+
expect(tagKey).to.equal('can-be-terminated');
346+
expect(tagValue).to.equal('true');
347+
return new Promise(function (resolve) {
348+
resolve(describeAutoscalingGroupsResponse_withMultpleGroupedAsgs);
349+
});
350+
},
351+
terminateInstance: terminateInstance
352+
},
353+
'./aws/ec2_handler': {
354+
lookupOldestInstance: (instances) => {
355+
if (instances[0]['InstanceId'] == 'i-grouped-asgs-1') {
356+
expect(instances).to.deep.equal([{
357+
InstanceId: 'i-grouped-asgs-1',
358+
LifecycleState: 'InService',
359+
HealthStatus: 'Healthy'
360+
}, {
361+
InstanceId: 'i-grouped-asgs-2',
362+
LifecycleState: 'InService',
363+
HealthStatus: 'Healthy'
364+
}]);
365+
return new Promise(function (resolve) {
366+
resolve({InstanceId: 'i-grouped-asgs-2',LaunchTime: new Date('2018-01-11T09:56:50.000Z')});
367+
});
368+
}
369+
if (instances[0]['InstanceId'] == 'i-grouped-asgs-3') {
370+
expect(instances).to.deep.equal([{
371+
InstanceId: 'i-grouped-asgs-3',
372+
LifecycleState: 'InService',
373+
HealthStatus: 'Healthy'
374+
}, {
375+
InstanceId: 'i-grouped-asgs-4',
376+
LifecycleState: 'InService',
377+
HealthStatus: 'Healthy'
378+
}]);
379+
return new Promise(function (resolve) {
380+
resolve({InstanceId: 'i-grouped-asgs-4',LaunchTime: new Date('2018-01-11T09:56:50.000Z')});
381+
});
382+
}
383+
if (instances[0]['InstanceId'] == 'i-grouped-asgs-2') {
384+
expect(instances).to.deep.equal([{
385+
InstanceId: 'i-grouped-asgs-2',
386+
LaunchTime: new Date('2018-01-11T09:56:50.000Z')
387+
}, {
388+
InstanceId: 'i-grouped-asgs-4',
389+
LaunchTime: new Date('2018-01-11T09:56:50.000Z')
390+
}]);
391+
return new Promise(function (resolve) {
392+
resolve({InstanceId: 'i-grouped-asgs-4', LaunchTime: new Date('2018-01-11T09:56:50.000Z')});
393+
});
394+
}
395+
}
396+
},
397+
});
398+
399+
return LambdaTester(myLambda.handler)
400+
.event()
401+
.expectResult((result) => {
402+
expect(result).to.have.lengthOf(2);
403+
expect(result).to.have.deep.members([
404+
{
405+
instanceTerminatorGroupName: "my-test-group1",
406+
result: 'instance terminated',
407+
instanceId: 'i-grouped-asgs-4'
408+
},{
409+
instanceTerminatorGroupName: "my-test-group2",
410+
result: 'instance-terminator-group tag only attached to one autoscaling group'
411+
}
412+
]);
413+
414+
expect(terminateInstance.callCount, 'terminate instance called once').to.equal(1);
415+
expect(terminateInstance.calledWith('i-grouped-asgs-4'), 'terminate instance parameters').to.be.true;
416+
});
417+
});
310418
});
311419

312420
const describeAutoscalingGroupsResponse_withTooFewInstances = [
@@ -451,4 +559,89 @@ const describeAutoscalingGroupsResponse_withBadlyGroupedAsgs = [
451559
Value: 'my-test-group'
452560
}]
453561
}
562+
]
563+
564+
const describeAutoscalingGroupsResponse_withGroupedAsgsWithNoInstances = [
565+
{
566+
AutoScalingGroupName: 'my-asg-grouped-asgs',
567+
MinSize: 0,
568+
MaxSize: 0,
569+
DesiredCapacity: 0,
570+
Instances: [],
571+
Tags: [{
572+
Key: 'instance-terminator-group',
573+
Value: 'my-test-group'
574+
}]
575+
},
576+
{
577+
AutoScalingGroupName: 'another-asg-grouped-asgs',
578+
MinSize: 0,
579+
MaxSize: 0,
580+
DesiredCapacity: 0,
581+
Instances: [],
582+
Tags: [{
583+
Key: 'instance-terminator-group',
584+
Value: 'my-test-group'
585+
}]
586+
}
587+
]
588+
589+
const describeAutoscalingGroupsResponse_withMultpleGroupedAsgs = [
590+
{
591+
AutoScalingGroupName: 'my-asg-grouped-asgs',
592+
MinSize: 2,
593+
MaxSize: 2,
594+
DesiredCapacity: 2,
595+
Instances: [{
596+
InstanceId: 'i-grouped-asgs-1',
597+
LifecycleState: 'InService',
598+
HealthStatus: 'Healthy'
599+
}, {
600+
InstanceId: 'i-grouped-asgs-2',
601+
LifecycleState: 'InService',
602+
HealthStatus: 'Healthy'
603+
}],
604+
Tags: [{
605+
Key: 'instance-terminator-group',
606+
Value: 'my-test-group1'
607+
}]
608+
},
609+
{
610+
AutoScalingGroupName: 'another-asg-grouped-asgs',
611+
MinSize: 2,
612+
MaxSize: 2,
613+
DesiredCapacity: 2,
614+
Instances: [{
615+
InstanceId: 'i-grouped-asgs-3',
616+
LifecycleState: 'InService',
617+
HealthStatus: 'Healthy'
618+
}, {
619+
InstanceId: 'i-grouped-asgs-4',
620+
LifecycleState: 'InService',
621+
HealthStatus: 'Healthy'
622+
}],
623+
Tags: [{
624+
Key: 'instance-terminator-group',
625+
Value: 'my-test-group1'
626+
}]
627+
},
628+
{
629+
AutoScalingGroupName: 'third-grouped-asgs',
630+
MinSize: 2,
631+
MaxSize: 2,
632+
DesiredCapacity: 2,
633+
Instances: [{
634+
InstanceId: 'i-grouped-asgs-5',
635+
LifecycleState: 'InService',
636+
HealthStatus: 'Healthy'
637+
}, {
638+
InstanceId: 'i-grouped-asgs-6',
639+
LifecycleState: 'InService',
640+
HealthStatus: 'Healthy'
641+
}],
642+
Tags: [{
643+
Key: 'instance-terminator-group',
644+
Value: 'my-test-group2'
645+
}]
646+
},
454647
]

0 commit comments

Comments
 (0)