serverless
Version:
Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more
224 lines (196 loc) • 7.62 kB
JavaScript
'use strict';
const crypto = require('crypto');
const BbPromise = require('bluebird');
const _ = require('lodash');
const path = require('path');
const fsAsync = BbPromise.promisifyAll(require('fs'));
const getLambdaLayerArtifactPath = require('../../utils/getLambdaLayerArtifactPath');
class AwsCompileLayers {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
const servicePath = this.serverless.config.servicePath || '';
this.packagePath =
this.serverless.service.package.path || path.join(servicePath || '.', '.serverless');
this.provider = this.serverless.getProvider('aws');
this.hooks = {
'package:compileLayers': () => BbPromise.bind(this).then(this.compileLayers),
};
}
compileLayer(layerName) {
const newLayer = this.cfLambdaLayerTemplate();
const layerObject = this.serverless.service.getLayer(layerName);
layerObject.package = layerObject.package || {};
Object.defineProperty(newLayer, '_serverlessLayerName', { value: layerName });
const artifactFilePath = this.provider.resolveLayerArtifactName(layerName);
if (this.serverless.service.package.deploymentBucket) {
newLayer.Properties.Content.S3Bucket = this.serverless.service.package.deploymentBucket;
}
const s3Folder = this.serverless.service.package.artifactDirectoryName;
const s3FileName = artifactFilePath.split(path.sep).pop();
newLayer.Properties.Content.S3Key = `${s3Folder}/${s3FileName}`;
newLayer.Properties.LayerName = layerObject.name || layerName;
if (layerObject.description) {
newLayer.Properties.Description = layerObject.description;
}
if (layerObject.licenseInfo) {
newLayer.Properties.LicenseInfo = layerObject.licenseInfo;
}
if (layerObject.compatibleRuntimes) {
newLayer.Properties.CompatibleRuntimes = layerObject.compatibleRuntimes;
}
let layerLogicalId = this.provider.naming.getLambdaLayerLogicalId(layerName);
const layerArtifactPath = getLambdaLayerArtifactPath(
this.packagePath,
layerName,
this.provider.serverless.service,
this.provider.naming
);
return fsAsync.readFileAsync(layerArtifactPath).then((layerArtifactBinary) => {
const sha = crypto
.createHash('sha1')
.update(JSON.stringify(_.omit(newLayer, ['Properties.Content.S3Key'])))
.update(layerArtifactBinary)
.digest('hex');
if (layerObject.retain) {
layerLogicalId = `${layerLogicalId}${sha}`;
newLayer.DeletionPolicy = 'Retain';
}
const newLayerObject = {
[layerLogicalId]: newLayer,
};
if (layerObject.allowedAccounts) {
layerObject.allowedAccounts.map((account) => {
const newPermission = this.cfLambdaLayerPermissionTemplate();
newPermission.Properties.LayerVersionArn = { Ref: layerLogicalId };
newPermission.Properties.Principal = account;
let layerPermLogicalId = this.provider.naming.getLambdaLayerPermissionLogicalId(
layerName,
account
);
if (layerObject.retain) {
layerPermLogicalId = `${layerPermLogicalId}${sha}`;
newPermission.DeletionPolicy = 'Retain';
}
newLayerObject[layerPermLogicalId] = newPermission;
return newPermission;
});
}
Object.assign(
this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
newLayerObject
);
// Add layer to Outputs section
const layerOutputLogicalId = this.provider.naming.getLambdaLayerOutputLogicalId(layerName);
const newLayerOutput = this.cfOutputLayerTemplate();
newLayerOutput.Value = { Ref: layerLogicalId };
const layerHashOutputLogicalId = this.provider.naming.getLambdaLayerHashOutputLogicalId(
layerName
);
const newLayerHashOutput = this.cfOutputLayerHashTemplate();
newLayerHashOutput.Value = sha;
const layerS3KeyOutputLogicalId = this.provider.naming.getLambdaLayerS3KeyOutputLogicalId(
layerName
);
const newLayerS3KeyOutput = this.cfOutputLayerS3KeyTemplate();
newLayerS3KeyOutput.Value = newLayer.Properties.Content.S3Key;
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Outputs, {
[layerOutputLogicalId]: newLayerOutput,
[layerHashOutputLogicalId]: newLayerHashOutput,
[layerS3KeyOutputLogicalId]: newLayerS3KeyOutput,
});
});
}
compareWithLastLayer(layerName) {
const stackName = this.provider.naming.getStackName();
const layerHashOutputLogicalId = this.provider.naming.getLambdaLayerHashOutputLogicalId(
layerName
);
return this.provider.request('CloudFormation', 'describeStacks', { StackName: stackName }).then(
(data) => {
const lastHash = data.Stacks[0].Outputs.find(
(output) => output.OutputKey === layerHashOutputLogicalId
);
const compiledCloudFormationTemplate = this.serverless.service.provider
.compiledCloudFormationTemplate;
const newSha = compiledCloudFormationTemplate.Outputs[layerHashOutputLogicalId].Value;
if (lastHash == null || lastHash.OutputValue !== newSha) {
return;
}
const layerS3keyOutputLogicalId = this.provider.naming.getLambdaLayerS3KeyOutputLogicalId(
layerName
);
const lastS3Key = data.Stacks[0].Outputs.find(
(output) => output.OutputKey === layerS3keyOutputLogicalId
);
compiledCloudFormationTemplate.Outputs[layerS3keyOutputLogicalId].Value =
lastS3Key.OutputValue;
const layerLogicalId = this.provider.naming.getLambdaLayerLogicalId(layerName);
const layerResource =
compiledCloudFormationTemplate.Resources[layerLogicalId] ||
compiledCloudFormationTemplate.Resources[`${layerLogicalId}${lastHash.OutputValue}`];
layerResource.Properties.Content.S3Key = lastS3Key.OutputValue;
const layerObject = this.serverless.service.getLayer(layerName);
layerObject.artifactAlreadyUploaded = true;
this.serverless.cli.log(`Layer ${layerName} is already uploaded.`);
},
(e) => {
if (e.message.includes('does not exist')) {
return;
}
throw e;
}
);
}
compileLayers() {
const allLayers = this.serverless.service.getAllLayers();
return Promise.all(
allLayers.map((layerName) =>
this.compileLayer(layerName).then(() => this.compareWithLastLayer(layerName))
)
);
}
cfLambdaLayerTemplate() {
return {
Type: 'AWS::Lambda::LayerVersion',
Properties: {
Content: {
S3Bucket: {
Ref: 'ServerlessDeploymentBucket',
},
S3Key: 'S3Key',
},
LayerName: 'LayerName',
},
};
}
cfLambdaLayerPermissionTemplate() {
return {
Type: 'AWS::Lambda::LayerVersionPermission',
Properties: {
Action: 'lambda:GetLayerVersion',
LayerVersionArn: 'LayerVersionArn',
Principal: 'Principal',
},
};
}
cfOutputLayerTemplate() {
return {
Description: 'Current Lambda layer version',
Value: 'Value',
};
}
cfOutputLayerHashTemplate() {
return {
Description: 'Current Lambda layer hash',
Value: 'Value',
};
}
cfOutputLayerS3KeyTemplate() {
return {
Description: 'Current Lambda layer S3Key',
Value: 'Value',
};
}
}
module.exports = AwsCompileLayers;