Skip to content

Commit

Permalink
feat(AWS ALB): Recognize path as optional condition (#8571)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinhong- committed Dec 11, 2020
1 parent 105fbfe commit 3632e0e
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 18 deletions.
1 change: 0 additions & 1 deletion lib/plugins/aws/package/compile/events/alb/index.js
Expand Up @@ -71,7 +71,6 @@ class AwsCompileAlbEvents {
propertyNames: { type: 'string', maxLength: 128 },
},
},
required: ['path'],
additionalProperties: false,
},
healthCheck: {
Expand Down
Expand Up @@ -75,12 +75,13 @@ module.exports = {
}
Actions.push(forwardAction);

const Conditions = [
{
const Conditions = [];
if (event.conditions.path) {
Conditions.push({
Field: 'path-pattern',
Values: event.conditions.path,
},
];
});
}
if (event.conditions.host) {
Conditions.push({
Field: 'host-header',
Expand Down
26 changes: 22 additions & 4 deletions lib/plugins/aws/package/compile/events/alb/lib/validate.js
Expand Up @@ -34,13 +34,14 @@ module.exports = {
listenerId,
listenerArn: event.alb.listenerArn,
priority: event.alb.priority,
conditions: {
// concat usage allows the user to provide value as a string or an array
path: [].concat(event.alb.conditions.path),
},
conditions: {},
// the following is data which is not defined on the event-level
functionName,
};

if (event.alb.conditions.path) {
albObj.conditions.path = [].concat(event.alb.conditions.path);
}
if (event.alb.conditions.host) {
albObj.conditions.host = [].concat(event.alb.conditions.host);
}
Expand All @@ -56,9 +57,12 @@ module.exports = {
if (event.alb.conditions.ip) {
albObj.conditions.ip = [].concat(event.alb.conditions.ip);
}
this.validateConditions(event.alb.conditions, functionName);

if (event.alb.multiValueHeaders) {
albObj.multiValueHeaders = event.alb.multiValueHeaders;
}

if (event.alb.authorizer) {
albObj.authorizers = this.validateEventAuthorizers(event, authorizers, functionName);
}
Expand All @@ -78,6 +82,20 @@ module.exports = {
};
},

validateConditions(conditions, functionName) {
// At least one condition must be set
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listenerrule.html#cfn-elasticloadbalancingv2-listenerrule-conditions

if (Object.keys(conditions).length > 0) {
return;
}

throw new this.serverless.classes.Error(
`Cannot setup "alb" event in "${functionName}" function: At least one condition must be defined`,
'ALB_NO_CONDITIONS'
);
},

validateListenerArn(listenerArn, functionName) {
// If the ARN is a ref, use the logical ID instead of the ALB ID
if (_.isObject(listenerArn)) {
Expand Down
94 changes: 85 additions & 9 deletions test/unit/lib/plugins/aws/package/compile/events/alb/index.test.js
@@ -1,18 +1,26 @@
'use strict';

const runServerless = require('../../../../../../../../utils/run-serverless');
const { expect } = require('chai');
const { ServerlessError } = require('../../../../../../../../../lib/classes/Error');
const { use: chaiUse, expect } = require('chai');
const chaiAsPromised = require('chai-as-promised');

chaiUse(chaiAsPromised);

describe('lib/plugins/aws/package/compile/eents/alb/index.test.js', () => {
let cfResources;
let naming;

const baseEventConfig = {
listenerArn:
'arn:aws:elasticloadbalancing:' +
'us-east-1:123456789012:listener/app/my-load-balancer/' +
'50dc6c495c0c9188/f2f7dc8efc522ab2',
};

before(async () => {
const baseEventConfig = {
listenerArn:
'arn:aws:elasticloadbalancing:' +
'us-east-1:123456789012:listener/app/my-load-balancer/' +
'50dc6c495c0c9188/f2f7dc8efc522ab2',
const validBaseEventConfig = {
...baseEventConfig,
conditions: {
path: '/',
},
Expand Down Expand Up @@ -43,15 +51,25 @@ describe('lib/plugins/aws/package/compile/eents/alb/index.test.js', () => {
functions: {
fnAuthorizerOnUnauthenticatedRequestDeny: {
handler: 'index.handler',
events: [{ alb: { ...baseEventConfig, priority: 1, authorizer: 'deny' } }],
events: [{ alb: { ...validBaseEventConfig, priority: 1, authorizer: 'deny' } }],
},
fnAuthorizerOnUnauthenticatedRequestAllow: {
handler: 'index.handler',
events: [{ alb: { ...baseEventConfig, priority: 2, authorizer: 'allow' } }],
events: [{ alb: { ...validBaseEventConfig, priority: 2, authorizer: 'allow' } }],
},
fnAuthorizerOnUnauthenticatedRequestAuthenticate: {
handler: 'index.handler',
events: [{ alb: { ...baseEventConfig, priority: 3, authorizer: 'authenticate' } }],
events: [{ alb: { ...validBaseEventConfig, priority: 3, authorizer: 'authenticate' } }],
},
fnConditionsHostOnly: {
handler: 'index.handler',
events: [
{ alb: { ...baseEventConfig, priority: 4, conditions: { host: 'example.com' } } },
],
},
fnConditionsPathOnly: {
handler: 'index.handler',
events: [{ alb: { ...baseEventConfig, priority: 5, conditions: { path: '/' } } }],
},
},
},
Expand Down Expand Up @@ -116,4 +134,62 @@ describe('lib/plugins/aws/package/compile/eents/alb/index.test.js', () => {
);
});
});

describe('alb rule conditions', () => {
it('should support rule without path', () => {
const albListenerRuleLogicalId = naming.getAlbListenerRuleLogicalId(
'fnConditionsHostOnly',
4
);
const rule = cfResources[albListenerRuleLogicalId];

expect(rule.Type).to.equal('AWS::ElasticLoadBalancingV2::ListenerRule');
expect(rule.Properties.Conditions).to.have.length(1);
expect(rule.Properties.Conditions[0].Field).to.equal('host-header');
expect(rule.Properties.Conditions[0].Values).to.have.length(1);
expect(rule.Properties.Conditions[0].Values[0]).to.equal('example.com');
});

it('should should support rule with path', () => {
const albListenerRuleLogicalId = naming.getAlbListenerRuleLogicalId(
'fnConditionsPathOnly',
5
);
const rule = cfResources[albListenerRuleLogicalId];

expect(rule.Type).to.equal('AWS::ElasticLoadBalancingV2::ListenerRule');
expect(rule.Properties.Conditions).to.have.length(1);
expect(rule.Properties.Conditions[0].Field).to.equal('path-pattern');
expect(rule.Properties.Conditions[0].Values).to.have.length(1);
expect(rule.Properties.Conditions[0].Values[0]).to.equal('/');
});

it('should fail validation if no conditions are set', async () => {
const runServerlessAction = () =>
runServerless({
fixture: 'function',
cliArgs: ['package'],
configExt: {
functions: {
fnConditionsHostOnly: {
handler: 'index.handler',
events: [
{
alb: {
...baseEventConfig,
priority: 1,
conditions: {},
},
},
],
},
},
},
});

await expect(runServerlessAction())
.to.eventually.be.rejectedWith(ServerlessError)
.and.have.property('code', 'ALB_NO_CONDITIONS');
});
});
});

0 comments on commit 3632e0e

Please sign in to comment.