New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS: Fix stage name validation timing and allow hyphen #5686

Merged
merged 8 commits into from Jan 14, 2019
Copy path View file
@@ -10,6 +10,8 @@ const fse = require('../utils/fs/fse');
const logWarning = require('./Error').logWarning; const logWarning = require('./Error').logWarning;
const PromiseTracker = require('./PromiseTracker'); const PromiseTracker = require('./PromiseTracker');


const validAPIGatewayStageNamePattern = /^[-_a-zA-Z0-9]+$/;

/** /**
* Maintainer's notes: * Maintainer's notes:
* *
@@ -108,6 +110,19 @@ class Variables {
this.populateValue(config.value, true) // populate this.populateValue(config.value, true) // populate
.then(populated => _.assign(config, { populated }))); .then(populated => _.assign(config, { populated })));
return this.assignProperties(provider, prepopulations); return this.assignProperties(provider, prepopulations);
}).then(() => {
const stage = provider.getStage();
this.serverless.service.getAllFunctions().forEach(funcName => {
_.forEach(this.serverless.service.getAllEventsInFunction(funcName), event => {
if (_.has(event, 'http') && !validAPIGatewayStageNamePattern.test(stage)) {
throw new this.serverless.classes.Error([
`Invalid stage name ${stage}:`,
'it should contains only [-_a-zA-Z0-9] for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
}
});
});
}); });
} }
return BbPromise.resolve(); return BbPromise.resolve();
Copy path View file
@@ -207,6 +207,113 @@ describe('Variables', () => {
}); });
}); });
}); });
describe('stage name validation', () => {
it(`should not throw an error if http event is absent and
stage contains only alphanumeric, underscore and hyphen`, () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: 'xyz-101_abc-123',
},
functions: {
first: {
events: [],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be.fullfilled;
});

it(`should not throw an error after variable population if http event is present and
the populated stage contains only alphanumeric, underscore and hyphen`, () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: '${opt:stage, "default-stage"}',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be.fullfilled;
});

it('should throw an error if http event is present and stage contains invalid chars', () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: 'my@stage',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be
.rejectedWith(serverless.classes.Error, [
'Invalid stage name my@stage: it should contains only [-_a-zA-Z0-9]',
'for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
});

it(`should throw an error after variable population
if http event is present and stage contains hyphen`, () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: '${opt:stage, "default:stage"}',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be
.rejectedWith(serverless.classes.Error, [
'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]',
'for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
});
});
}); });


describe('#getProperties', () => { describe('#getProperties', () => {
@@ -19,8 +19,6 @@ const constants = {
providerName: 'aws', providerName: 'aws',
}; };


const validAPIGatewayStageNamePattern = /^[a-zA-Z0-9_]+$/;

PromiseQueue.configure(BbPromise.Promise); PromiseQueue.configure(BbPromise.Promise);


const impl = { const impl = {
@@ -201,19 +199,6 @@ class AwsProvider {
} }
} }
} }

const stage = this.getStage();
this.serverless.service.getAllFunctions().forEach(funcName => {
_.forEach(this.serverless.service.getAllEventsInFunction(funcName), event => {
if (_.has(event, 'http') && !validAPIGatewayStageNamePattern.test(stage)) {
throw new Error([
`Invalid stage name ${stage}: `,
'it should contains only [a-zA-Z0-9] for AWS provider if http event are present ',
'since API Gateway stage name cannot contains hyphens.',
].join(''));
}
});
});
} }


/** /**
@@ -90,51 +90,43 @@ describe('AwsProvider', () => {
delete process.env.AWS_CLIENT_TIMEOUT; delete process.env.AWS_CLIENT_TIMEOUT;
}); });


describe('validation on construction', () => { describe('stage name validation', () => {
it('should not throw an error if stage name contains only alphanumeric', () => { const stages = [
const config = { 'myStage',
stage: 'configStage', 'my-stage',
}; 'my_stage',
serverless = new Serverless(config); '${opt:stage, \'prod\'}',
expect(() => new AwsProvider(serverless, config)).to.not.throw(Error); ];
}); stages.forEach(stage => {

it(`should not throw an error before variable population
it('should not throw an error if stage contains hyphen but http events are absent', () => { even if http event is present and stage is ${stage}`, () => {
const config = { const config = {
stage: 'config-stage', stage,
}; };
serverless = new Serverless(config); serverless = new Serverless(config);
expect(() => new AwsProvider(serverless, config)).to.not.throw(Error);
});

it('should throw an error if stage contains hyphen and http events are present', () => {
const config = {
stage: 'config-stage',
};
serverless = new Serverless(config);


const serverlessYml = { const serverlessYml = {
service: 'new-service', service: 'new-service',
provider: { provider: {
name: 'aws', name: 'aws',
stage: 'config-stage', stage,
}, },
functions: { functions: {
first: { first: {
events: [ events: [
{ {
http: { http: {
path: 'foo', path: 'foo',
method: 'GET', method: 'GET',
},
}, },
}, ],
], },
}, },
}, };
}; serverless.service = new serverless.classes.Service(serverless, serverlessYml);
serverless.service = new serverless.classes.Service(serverless, serverlessYml); expect(() => new AwsProvider(serverless, config)).to.not.throw(Error);

});
expect(() => new AwsProvider(serverless, config)).to.throw(Error);
}); });
}); });


ProTip! Use n and p to navigate between commits in a pull request.