mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
Instead of specifying an IAM policy when creating the API Gateway method's integration, the individual lambda's access policy is updated. The reason for this is that the first method results in an extra ~500ms of latency. Now, JAWS REST APIs should be much faster. However, this might result in breaking changes (sorry).
988 lines
28 KiB
JavaScript
988 lines
28 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* JAWS Command: deploy endpoint <stage> <region>
|
|
* - Deploys project's API Gateway REST API to the specified stage and one or all regions
|
|
*/
|
|
|
|
// TODO: On completion, list API G routes not used within the project (all regions). Offer option to delete them.
|
|
|
|
var JawsError = require('../jaws-error'),
|
|
JawsCli = require('../utils/cli'),
|
|
Promise = require('bluebird'),
|
|
fs = require('fs'),
|
|
async = require('async'),
|
|
path = require('path'),
|
|
utils = require('../utils/index'),
|
|
AWSUtils = require('../utils/aws'),
|
|
CMDtag = require('./tag'),
|
|
JawsAPIClient = require('jaws-api-gateway-client');
|
|
|
|
Promise.promisifyAll(fs);
|
|
|
|
/**
|
|
* Run
|
|
* @param JAWS
|
|
* @param stage
|
|
* @param region
|
|
* @param allTagged
|
|
* @returns {*}
|
|
*/
|
|
|
|
module.exports.run = function(JAWS, stage, region, allTagged) {
|
|
var command = new CMD(JAWS, stage, region, allTagged);
|
|
return command.run();
|
|
};
|
|
|
|
/**
|
|
* CMD Class
|
|
* @param JAWS
|
|
* @param stage
|
|
* @param region
|
|
* @param allTagged
|
|
* @constructor
|
|
*/
|
|
|
|
function CMD(JAWS, stage, region, allTagged) {
|
|
var _this = this;
|
|
_this._stage = stage;
|
|
_this._allTagged = allTagged;
|
|
_this._JAWS = JAWS;
|
|
_this._prjJson = JAWS._meta.projectJson;
|
|
_this._prjRootPath = JAWS._meta.projectRootPath;
|
|
_this._prjCreds = JAWS._meta.credentials;
|
|
|
|
if (region && stage) {
|
|
_this._regions = _this._JAWS._meta.projectJson.stages[_this._stage].filter(function(r) {
|
|
return (r.region == region);
|
|
});
|
|
} else if (stage) {
|
|
_this._regions = _this._JAWS._meta.projectJson.stages[_this._stage];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CMD: Run
|
|
*/
|
|
|
|
CMD.prototype.run = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// Flow
|
|
return _this._JAWS.validateProject()
|
|
.bind(_this)
|
|
.then(function() {
|
|
// If !allTagged, tag current directory
|
|
if (!_this._allTagged) {
|
|
return CMDtag.tag('endpoint', null, false);
|
|
}
|
|
})
|
|
.then(_this._promptStage)
|
|
.then(_this._promptRegions)
|
|
.then(function() {
|
|
return _this._regions;
|
|
})
|
|
.each(function(regionJson) {
|
|
|
|
JawsCli.log('Endpoint Deployer: Deploying endpoint(s) to region "' + regionJson.region + '"...');
|
|
|
|
var deployer = new ApiDeployer(
|
|
_this._JAWS,
|
|
_this._stage,
|
|
regionJson,
|
|
_this._prjRootPath,
|
|
_this._prjJson,
|
|
_this._prjCreds
|
|
);
|
|
|
|
return deployer.deploy()
|
|
.then(function(url) {
|
|
JawsCli.log('Endpoint Deployer: Endpoints for stage "'
|
|
+ _this._stage
|
|
+ '" successfully deployed to API Gateway in the region "'
|
|
+ regionJson.region
|
|
+ '". Access them @ '
|
|
+ url);
|
|
});
|
|
})
|
|
.then(function() {
|
|
// Untag All tagged endpoints
|
|
return _this._allTagged ? CMDtag.tagAll(_this._JAWS, 'endpoint', true) : CMDtag.tag('endpoint', null, true);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* CMD: Prompt Stage
|
|
*/
|
|
|
|
CMD.prototype._promptStage = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// If stage, skip
|
|
if (_this._stage) return;
|
|
|
|
var stages = Object.keys(_this._prjJson.stages);
|
|
if (!stages.length) {
|
|
throw new JawsError('You have no stages in this project');
|
|
}
|
|
|
|
// If project has only one stage, skip select
|
|
if (stages.length === 1) {
|
|
_this._stage = stages[0];
|
|
return;
|
|
}
|
|
|
|
var choices = [];
|
|
for (var i = 0; i < stages.length; i++) {
|
|
choices.push({
|
|
key: '',
|
|
value: stages[i],
|
|
label: stages[i]
|
|
});
|
|
}
|
|
|
|
return JawsCli.select('Select a stage to deploy to: ', choices, false);
|
|
});
|
|
|
|
/**
|
|
* CMD: Prompt Regions
|
|
*/
|
|
|
|
CMD.prototype._promptRegions = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// If regions, skip
|
|
if (_this._regions && _this._regions.length) return;
|
|
|
|
var regions = _this._JAWS._meta.projectJson.stages[_this._stage];
|
|
|
|
// If stage has only one region, skip select
|
|
if (regions.length === 1) {
|
|
_this._regions = regions;
|
|
return;
|
|
}
|
|
|
|
var choices = [];
|
|
for (var i = 0; i < regions.length; i++) {
|
|
choices.push({
|
|
key: '',
|
|
value: regions[i].region,
|
|
label: regions[i].region,
|
|
});
|
|
}
|
|
|
|
return JawsCli.select('Select a region in this stage to deploy to: ', choices, false);
|
|
});
|
|
|
|
/**
|
|
* Api Deployer
|
|
* @param JAWS
|
|
* @param stage
|
|
* @param region
|
|
* @param prjRootPath
|
|
* @param prjJson
|
|
* @param prjCreds
|
|
* @constructor
|
|
*/
|
|
|
|
function ApiDeployer(JAWS, stage, region, prjRootPath, prjJson, prjCreds) {
|
|
|
|
var _this = this;
|
|
_this._JAWS = JAWS;
|
|
_this._stage = stage;
|
|
_this._regionJson = region;
|
|
_this._prjJson = prjJson;
|
|
_this._prjRootPath = prjRootPath;
|
|
_this._prjCreds = prjCreds;
|
|
_this._endpoints = [];
|
|
_this._resources = [];
|
|
|
|
_this._awsAccountNumber = _this._regionJson.iamRoleArnApiGateway.replace('arn:aws:iam::', '').split(':')[0];
|
|
_this._restApiId = _this._regionJson.restApiId ? _this._regionJson.restApiId : null;
|
|
|
|
// Instantiate API Gateway Client
|
|
this.ApiClient = new JawsAPIClient({
|
|
accessKeyId: prjCreds.aws_access_key_id,
|
|
secretAccessKey: prjCreds.aws_secret_access_key,
|
|
region: region.region,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* API Deployer: Deploy
|
|
*/
|
|
|
|
ApiDeployer.prototype.deploy = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
return _this._findTaggedEndpoints()
|
|
.bind(_this)
|
|
.then(_this._validateAndSantizeTaggedEndpoints)
|
|
.then(_this._fetchDeployedLambdas)
|
|
.then(_this._findOrCreateApi)
|
|
.then(_this._saveApiId)
|
|
.then(_this._listApiResources)
|
|
.then(_this._buildEndpoints)
|
|
.then(_this._createDeployment)
|
|
.then(function() {
|
|
return 'https://'
|
|
+ _this._restApiId
|
|
+ '.execute-api.'
|
|
+ _this._regionJson.region
|
|
+ '.amazonaws.com/'
|
|
+ _this._stage
|
|
+ '/';
|
|
});
|
|
});
|
|
|
|
/**
|
|
* ApiDeployer: Find Tagged Endpoints
|
|
*/
|
|
|
|
ApiDeployer.prototype._findTaggedEndpoints = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
return utils.findAllEndpoints(_this._prjRootPath)
|
|
.each(function(endpoint) {
|
|
|
|
var eJson = require(endpoint);
|
|
if (eJson.apiGateway.deploy) _this._endpoints.push(eJson);
|
|
|
|
}).then(function() {
|
|
|
|
if (!_this._endpoints.length) {
|
|
throw new JawsError(
|
|
'You have no tagged endpoints',
|
|
JawsError.errorCodes.UNKNOWN);
|
|
}
|
|
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ '": found '
|
|
+ _this._endpoints.length + ' endpoints to deploy');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* ApiDeployer: Fetch Deployed Lambdas In CF Stack
|
|
*/
|
|
|
|
ApiDeployer.prototype._fetchDeployedLambdas = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
_this._lambdas = [];
|
|
var moreResources = true;
|
|
var nextStackToken;
|
|
|
|
async.whilst(
|
|
function() {
|
|
return moreResources === true;
|
|
},
|
|
|
|
function(callback) {
|
|
AWSUtils.cfListStackResources(
|
|
_this._JAWS._meta.profile,
|
|
_this._regionJson.region,
|
|
_this._stage + '-' + _this._JAWS._meta.projectJson.name + '-l',
|
|
nextStackToken
|
|
)
|
|
.then(function(lambdaCfResources) {
|
|
|
|
// Add deployed lambdas
|
|
if (lambdaCfResources.StackResourceSummaries) {
|
|
_this._lambdas = _this._lambdas.concat(lambdaCfResources.StackResourceSummaries);
|
|
}
|
|
|
|
// Check if more resources are available
|
|
if (!lambdaCfResources.NextToken) {
|
|
moreResources = false;
|
|
}
|
|
|
|
return callback();
|
|
})
|
|
.catch(function(error) {
|
|
JawsCli.log('Warning: JAWS could not find a deployed Cloudformation '
|
|
+ 'template containing lambda functions.');
|
|
console.log(error);
|
|
moreResources = false;
|
|
return callback();
|
|
});
|
|
},
|
|
|
|
function() {
|
|
return;
|
|
}
|
|
);
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Validate & Sanitize Tagged Endpoints
|
|
*/
|
|
|
|
ApiDeployer.prototype._validateAndSantizeTaggedEndpoints = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// Loop through tagged endpoints
|
|
for (var i = 0; i < _this._endpoints.length; i++) {
|
|
|
|
var e = _this._endpoints[i].apiGateway.cloudFormation;
|
|
|
|
// Validate attributes
|
|
if (!e.Type
|
|
|| !e.Path
|
|
|| !e.Method
|
|
|| !e.AuthorizationType
|
|
|| typeof e.ApiKeyRequired === 'undefined') {
|
|
throw new JawsError(
|
|
'Missing one of many required endpoint attributes: type, path, method, authorizationType, apiKeyRequired',
|
|
JawsError.errorCodes.UNKNOWN);
|
|
}
|
|
|
|
// Sanitize path
|
|
if (e.Path.charAt(0) === '/') e.Path = e.Path.substring(1);
|
|
|
|
// Sanitize method
|
|
e.Method = e.Method.toUpperCase();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Save API ID
|
|
*/
|
|
|
|
ApiDeployer.prototype._saveApiId = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// Attach API Gateway REST API ID
|
|
for (var i = 0; i < _this._prjJson.stages[_this._stage].length; i++) {
|
|
if (_this._prjJson.stages[_this._stage][i].region === _this._regionJson.region) {
|
|
_this._prjJson.stages[_this._stage][i].restApiId = _this._restApiId;
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(path.join(_this._prjRootPath, 'jaws.json'), JSON.stringify(_this._prjJson, null, 2));
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Find Or Create API
|
|
*/
|
|
|
|
ApiDeployer.prototype._findOrCreateApi = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// Check Project's jaws.json for restApiId, otherwise create an api
|
|
if (_this._restApiId) {
|
|
|
|
// Show existing REST API
|
|
return _this.ApiClient.showRestApi(_this._restApiId)
|
|
.then(function(response) {
|
|
|
|
_this._restApiId = response.id;
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ '": found existing REST API on AWS API Gateway with ID: '
|
|
+ response.id);
|
|
});
|
|
} else {
|
|
|
|
// Create REST API
|
|
return _this.ApiClient.createRestApi({
|
|
name: _this._prjJson.name,
|
|
description: _this._prjJson.description ? _this._prjJson.description : 'A REST API for a JAWS project.',
|
|
}).then(function(response) {
|
|
|
|
_this._restApiId = response.id;
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ '": created a new REST API on AWS API Gateway with ID: '
|
|
+ response.id);
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* API Deployer: List API Resources
|
|
*/
|
|
|
|
ApiDeployer.prototype._listApiResources = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
// List all Resources for this REST API
|
|
return _this.ApiClient.listResources(_this._restApiId)
|
|
.then(function(response) {
|
|
|
|
// Parse API Gateway's HAL response
|
|
_this._resources = response._embedded.item;
|
|
if (!Array.isArray(_this._resources)) _this._resources = [_this._resources];
|
|
|
|
// Get Parent Resource ID
|
|
for (var i = 0; i < _this._resources.length; i++) {
|
|
if (_this._resources[i].path === '/') {
|
|
_this._parentResourceId = _this._resources[i].id;
|
|
}
|
|
}
|
|
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ '": found '
|
|
+ _this._resources.length
|
|
+ ' existing resources on API Gateway');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Build Endpoints
|
|
*/
|
|
|
|
ApiDeployer.prototype._buildEndpoints = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
return Promise.try(function() {
|
|
return _this._endpoints;
|
|
}).each(function(endpoint) {
|
|
|
|
return _this._createEndpointResources(endpoint)
|
|
.bind(_this)
|
|
.then(_this._createEndpointMethod)
|
|
.then(_this._createEndpointIntegration)
|
|
.then(_this._manageLambdaAccessPolicy)
|
|
.then(_this._createEndpointMethodResponses)
|
|
.then(_this._createEndpointMethodIntegResponses)
|
|
.then(function() {
|
|
|
|
// Clean-up hack
|
|
// TODO figure out how "apig" temp property is being written to the awsm's json and remove that
|
|
if (endpoint.apiGateway.apig) delete endpoint.apiGateway.apig;
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Create Endpoint Resources
|
|
*/
|
|
|
|
ApiDeployer.prototype._createEndpointResources = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
var eResources = endpoint.apiGateway.cloudFormation.Path.split('/');
|
|
|
|
/**
|
|
* Private Function to find resource
|
|
* @param resource
|
|
* @param parent
|
|
* @returns {*}
|
|
*/
|
|
|
|
var findEndpointResource = function(resource, parent) {
|
|
|
|
// Replace slashes in resource
|
|
resource = resource.replace(/\//g, '');
|
|
var index = eResources.indexOf(resource);
|
|
|
|
if (parent) {
|
|
index = index - 1;
|
|
resource = eResources[index];
|
|
}
|
|
|
|
if (index < 0) {
|
|
resourcePath = '/';
|
|
} else {
|
|
var resourceIndex = endpoint.apiGateway.cloudFormation.Path.indexOf(resource);
|
|
var resourcePath = '/' + endpoint.apiGateway.cloudFormation.Path.substring(0, resourceIndex + resource.length);
|
|
}
|
|
|
|
// If resource has already been created, skip it
|
|
for (var i = 0; i < _this._resources.length; i++) {
|
|
|
|
// Check if path matches, in case there are duplicate resources (users/list, org/list)
|
|
if (_this._resources[i].path === resourcePath) {
|
|
return _this._resources[i];
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create temp property for saving state information
|
|
endpoint.apiGateway.apig = {};
|
|
|
|
return Promise.try(function() {
|
|
|
|
return eResources;
|
|
|
|
}).each(function(eResource) {
|
|
|
|
// Remove slashes in resource
|
|
eResource = eResource.replace(/\//g, '');
|
|
|
|
// If resource exists, skip it
|
|
var resource = findEndpointResource(eResource);
|
|
if (resource) return resource;
|
|
|
|
// Get Parent Resource
|
|
endpoint.apiGateway.apig.parentResourceId = findEndpointResource(eResource, true).id;
|
|
|
|
// Create Resource
|
|
return _this.ApiClient.createResource(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.parentResourceId,
|
|
eResource)
|
|
.then(function(response) {
|
|
|
|
// Add resource to _this.resources and callback
|
|
_this._resources.push(response);
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "' +
|
|
_this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - ' + endpoint.apiGateway.cloudFormation.Path + '": '
|
|
+ 'created resource: '
|
|
+ response.pathPart);
|
|
});
|
|
|
|
}).then(function() {
|
|
|
|
// Attach the last resource to endpoint for later use
|
|
var endpointResource = endpoint.apiGateway.cloudFormation.Path.split('/').pop().replace(/\//g, '');
|
|
endpoint.apiGateway.apig.resource = findEndpointResource(endpointResource);
|
|
return endpoint;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Create Endpoint Method
|
|
*/
|
|
|
|
ApiDeployer.prototype._createEndpointMethod = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
// Create Method
|
|
var methodBody = {
|
|
authorizationType: endpoint.apiGateway.cloudFormation.AuthorizationType,
|
|
};
|
|
|
|
// If Request Params, add them
|
|
if (endpoint.apiGateway.cloudFormation.RequestParameters) {
|
|
|
|
methodBody.requestParameters = {};
|
|
|
|
// Format them per APIG API's Expectations
|
|
for (var prop in endpoint.apiGateway.cloudFormation.RequestParameters) {
|
|
var requestParam = endpoint.apiGateway.cloudFormation.RequestParameters[prop];
|
|
methodBody.requestParameters[requestParam] = true;
|
|
}
|
|
}
|
|
|
|
return _this.ApiClient.showMethod(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method)
|
|
.then(function() {
|
|
|
|
return _this.ApiClient.deleteMethod(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method)
|
|
.then(function() {
|
|
_this.ApiClient.putMethod(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method,
|
|
methodBody);
|
|
});
|
|
}, function() {
|
|
|
|
return _this.ApiClient.putMethod(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method,
|
|
methodBody);
|
|
})
|
|
.delay(250) // API Gateway takes time to delete Methods. Might have to increase this.
|
|
.then(function(response) {
|
|
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - ' + endpoint.apiGateway.cloudFormation.Path + '": '
|
|
+ 'created method: '
|
|
+ endpoint.apiGateway.cloudFormation.Method);
|
|
return endpoint;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Create Endpoint Integration
|
|
*/
|
|
|
|
ApiDeployer.prototype._createEndpointIntegration = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
// Create Integration
|
|
if (endpoint.type === 'lambda' || typeof endpoint.lambda !== 'undefined') {
|
|
|
|
// Find Deployed Lambda and its function name
|
|
var cfLogicalResourceId = utils.generateLambdaName(endpoint);
|
|
var lambda = null;
|
|
|
|
for (var i = 0; i < _this._lambdas.length; i++) {
|
|
if (_this._lambdas[i].LogicalResourceId === cfLogicalResourceId) {
|
|
lambda = _this._lambdas[i];
|
|
}
|
|
}
|
|
|
|
// If no deployed lambda found, throw error
|
|
if (!lambda) {
|
|
throw new JawsError('Could not find a lambda deployed in this stage/region with this function name: '
|
|
+ cfLogicalResourceId);
|
|
}
|
|
endpoint.apiGateway.apig.lambda = lambda;
|
|
|
|
// Create integration body
|
|
var integrationBody = {
|
|
type: 'AWS',
|
|
httpMethod: 'POST', // Must be post for lambda
|
|
authorizationType: 'none',
|
|
uri: 'arn:aws:apigateway:' // Make ARN for apigateway - lambda
|
|
+ _this._regionJson.region
|
|
+ ':lambda:path/2015-03-31/functions/arn:aws:lambda:'
|
|
+ _this._regionJson.region
|
|
+ ':'
|
|
+ _this._awsAccountNumber
|
|
+ ':function:'
|
|
+ lambda.PhysicalResourceId
|
|
+ '/invocations',
|
|
|
|
// Due to a bug in API Gateway reported here: https://github.com/awslabs/aws-apigateway-swagger-importer/issues/41
|
|
// Specifying credentials within API Gateway causes extra latency (~500ms)
|
|
// Until API Gateway is fixed, we need to make a seperate call to Lambda to add credentials to API Gateway
|
|
// Once API Gateway is fixed, we can use this in credentials:
|
|
// _this._regionJson.iamRoleArnApiGateway
|
|
credentials: null,
|
|
requestParameters: endpoint.apiGateway.cloudFormation.RequestParameters || {},
|
|
requestTemplates: endpoint.apiGateway.cloudFormation.RequestTemplates || {},
|
|
cacheNamespace: endpoint.apiGateway.cloudFormation.CacheNamespace || null,
|
|
cacheKeyParameters: endpoint.apiGateway.cloudFormation.CacheKeyParameters || [],
|
|
};
|
|
|
|
} else {
|
|
throw new JawsError('JAWS API Gateway integration currently works with Lambdas only.');
|
|
}
|
|
|
|
// Create Integration
|
|
return _this.ApiClient.putIntegration(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method,
|
|
integrationBody)
|
|
.then(function(response) {
|
|
|
|
// Save integration to apig property
|
|
endpoint.apiGateway.apig.integration = response;
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage + ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - ' + endpoint.apiGateway.cloudFormation.Path + '": '
|
|
+ 'created integration with the type: '
|
|
+ endpoint.apiGateway.cloudFormation.Type);
|
|
return endpoint;
|
|
})
|
|
.catch(function(error) {
|
|
throw new JawsError(
|
|
error.message,
|
|
JawsError.errorCodes.UNKNOWN);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Create Endpoint Method Responses
|
|
*/
|
|
|
|
ApiDeployer.prototype._createEndpointMethodResponses = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
return Promise.try(function() {
|
|
|
|
// Collect Response Keys
|
|
if (endpoint.apiGateway.cloudFormation.Responses) return Object.keys(endpoint.apiGateway.cloudFormation.Responses);
|
|
else return [];
|
|
})
|
|
.each(function(responseKey) {
|
|
|
|
var thisResponse = endpoint.apiGateway.cloudFormation.Responses[responseKey];
|
|
var methodResponseBody = {};
|
|
|
|
// If Request Params, add them
|
|
if (thisResponse.responseParameters) {
|
|
|
|
methodResponseBody.responseParameters = {};
|
|
|
|
// Format Response Parameters per APIG API's Expectations
|
|
for (var prop in thisResponse.responseParameters) {
|
|
methodResponseBody.responseParameters[prop] = true;
|
|
}
|
|
}
|
|
|
|
// Create Method Response
|
|
return _this.ApiClient.putMethodResponse(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method,
|
|
thisResponse.statusCode,
|
|
methodResponseBody)
|
|
.then(function() {
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage
|
|
+ ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - '
|
|
+ endpoint.apiGateway.cloudFormation.Path
|
|
+ '": '
|
|
+ 'created method response');
|
|
})
|
|
.catch(function(error) {
|
|
throw new JawsError(
|
|
error.message,
|
|
JawsError.errorCodes.UNKNOWN);
|
|
});
|
|
})
|
|
.then(function() {
|
|
return endpoint;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Create Endpoint Method Integration Responses
|
|
*/
|
|
|
|
ApiDeployer.prototype._createEndpointMethodIntegResponses = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
return Promise.try(function() {
|
|
|
|
// Collect Response Keys
|
|
if (endpoint.apiGateway.cloudFormation.Responses) return Object.keys(endpoint.apiGateway.cloudFormation.Responses);
|
|
else return [];
|
|
})
|
|
.each(function(responseKey) {
|
|
|
|
var thisResponse = endpoint.apiGateway.cloudFormation.Responses[responseKey];
|
|
var integrationResponseBody = {};
|
|
|
|
// Add Response Parameters
|
|
integrationResponseBody.responseParameters = thisResponse.responseParameters;
|
|
|
|
// Add Response Templates
|
|
integrationResponseBody.responseTemplates = thisResponse.responseTemplates;
|
|
|
|
// Add SelectionPattern
|
|
integrationResponseBody.selectionPattern = responseKey === 'default' ? null : responseKey;// null = default
|
|
|
|
// Create Integration Response
|
|
return _this.ApiClient.putIntegrationResponse(
|
|
_this._restApiId,
|
|
endpoint.apiGateway.apig.resource.id,
|
|
endpoint.apiGateway.cloudFormation.Method,
|
|
thisResponse.statusCode,
|
|
integrationResponseBody)
|
|
.then(function() {
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage
|
|
+ ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - '
|
|
+ endpoint.apiGateway.cloudFormation.Path
|
|
+ '": '
|
|
+ 'created method integration response');
|
|
}).catch(function(error) {
|
|
throw new JawsError(
|
|
error.message,
|
|
JawsError.errorCodes.UNKNOWN);
|
|
});
|
|
});
|
|
});
|
|
|
|
ApiDeployer.prototype._manageLambdaAccessPolicy = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
// If method integration is not for a lambda, skip
|
|
if (!endpoint.apiGateway.apig.lambda) return endpoint;
|
|
|
|
return _this._getLambdaAccessPolicy(endpoint)
|
|
.bind(_this)
|
|
.then(_this._removeLambdaAccessPolicy)
|
|
.then(_this._updateLambdaAccessPolicy);
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Get Lambda Access Policy
|
|
* - Since specifying credentials when creating the Method Integration results in ~500ms
|
|
* - of extra latency, this function updates the lambda's access policy instead
|
|
* - to grant API Gateway permission. This is how the API Gateway console does it.
|
|
* - But this is not finished and the "getPolicy" method in the SDK is broken, so this
|
|
* - is currently impossible to implement.
|
|
*/
|
|
|
|
ApiDeployer.prototype._getLambdaAccessPolicy = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
return AWSUtils.lambdaGetPolicy(
|
|
_this._JAWS._meta.profile,
|
|
_this._regionJson.region,
|
|
endpoint.apiGateway.apig.lambda.PhysicalResourceId)
|
|
.then(function(data) {
|
|
endpoint.apiGateway.apig.lambda.Policy = JSON.parse(data.Policy);
|
|
return endpoint;
|
|
})
|
|
.catch(function(error) {
|
|
return endpoint;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Remove Lambda Access Policy
|
|
*/
|
|
|
|
ApiDeployer.prototype._removeLambdaAccessPolicy = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
var statement;
|
|
|
|
if (endpoint.apiGateway.apig.lambda.Policy) {
|
|
|
|
var policy = endpoint.apiGateway.apig.lambda.Policy;
|
|
|
|
for (var i = 0; i < policy.Statement.length; i++) {
|
|
statement = policy.Statement[i];
|
|
if (statement.Sid && statement.Sid === 'jaws-apigateway-access') continue;
|
|
}
|
|
}
|
|
|
|
if (!statement) return endpoint;
|
|
|
|
return AWSUtils.lambdaRemovePermission(
|
|
_this._JAWS._meta.profile,
|
|
_this._regionJson.region,
|
|
endpoint.apiGateway.apig.lambda.PhysicalResourceId,
|
|
'jaws-apigateway-access')
|
|
.then(function(data) {
|
|
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage
|
|
+ ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - '
|
|
+ endpoint.apiGateway.cloudFormation.Path
|
|
+ '": removed existing lambda access policy statement');
|
|
|
|
return endpoint;
|
|
})
|
|
.catch(function(error) {
|
|
console.log(error);
|
|
return endpoint;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Update Lambda Access Policy
|
|
*/
|
|
|
|
ApiDeployer.prototype._updateLambdaAccessPolicy = Promise.method(function(endpoint) {
|
|
|
|
var _this = this;
|
|
|
|
// Sanitize Path - Remove first and last slashes, if any
|
|
endpoint.apiGateway.cloudFormation.Path = endpoint.apiGateway.cloudFormation.Path.split('/');
|
|
endpoint.apiGateway.cloudFormation.Path = endpoint.apiGateway.cloudFormation.Path.join('/');
|
|
|
|
// Create new access policy statement
|
|
var statement = {};
|
|
statement.Action = 'lambda:InvokeFunction';
|
|
statement.FunctionName = endpoint.apiGateway.apig.lambda.PhysicalResourceId;
|
|
statement.Principal = 'apigateway.amazonaws.com';
|
|
statement.StatementId = 'jaws-apigateway-access';
|
|
statement.SourceArn = 'arn:aws:execute-api:'
|
|
+ _this._regionJson.region
|
|
+ ':'
|
|
+ _this._awsAccountNumber
|
|
+ ':'
|
|
+ _this._restApiId
|
|
+ '/'
|
|
+ _this._stage
|
|
+ '/'
|
|
+ endpoint.apiGateway.cloudFormation.Method
|
|
+ '/'
|
|
+ endpoint.apiGateway.cloudFormation.Path;
|
|
|
|
return AWSUtils.lambdaAddPermission(
|
|
_this._JAWS._meta.profile,
|
|
_this._regionJson.region,
|
|
statement)
|
|
.then(function(data) {
|
|
JawsCli.log(
|
|
'Endpoint Deployer: "'
|
|
+ _this._stage
|
|
+ ' - '
|
|
+ _this._regionJson.region
|
|
+ ' - '
|
|
+ endpoint.apiGateway.cloudFormation.Path
|
|
+ '": created new lambda access policy statement');
|
|
return endpoint;
|
|
})
|
|
.catch(function(error) {
|
|
console.log(error);
|
|
return endpoint;
|
|
});
|
|
});
|
|
|
|
/**
|
|
* API Deployer: Create Deployment
|
|
*/
|
|
|
|
ApiDeployer.prototype._createDeployment = Promise.method(function() {
|
|
|
|
var _this = this;
|
|
|
|
var deployment = {
|
|
stageName: _this._stage,
|
|
stageDescription: _this._stage,
|
|
description: 'JAWS deployment',
|
|
};
|
|
|
|
return _this.ApiClient.createDeployment(_this._restApiId, deployment)
|
|
.then(function(response) {
|
|
return response;
|
|
})
|
|
.catch(function(error) {
|
|
throw new JawsError(
|
|
error.message,
|
|
JawsError.errorCodes.UNKNOWN);
|
|
});
|
|
}); |