mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
* fix: remove bluebird from zip-service * fix: remove bluebird and set concurrency limits for packaging
244 lines
7.7 KiB
JavaScript
244 lines
7.7 KiB
JavaScript
import crypto from 'crypto'
|
|
import _ from 'lodash'
|
|
import path from 'path'
|
|
import fsAsync from 'fs/promises'
|
|
import getLambdaLayerArtifactPath from '../../utils/get-lambda-layer-artifact-path.js'
|
|
import utils from '@serverlessinc/sf-core/src/utils.js'
|
|
|
|
const { log } = utils
|
|
|
|
class AwsCompileLayers {
|
|
constructor(serverless, options) {
|
|
this.serverless = serverless
|
|
this.options = options
|
|
const serviceDir = this.serverless.serviceDir || ''
|
|
this.packagePath =
|
|
this.serverless.service.package.path ||
|
|
path.join(serviceDir || '.', '.serverless')
|
|
|
|
this.provider = this.serverless.getProvider('aws')
|
|
|
|
this.hooks = {
|
|
'package:compileLayers': async () => this.compileLayers(),
|
|
}
|
|
}
|
|
|
|
async 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
|
|
}
|
|
if (layerObject.compatibleArchitectures) {
|
|
newLayer.Properties.CompatibleArchitectures =
|
|
layerObject.compatibleArchitectures
|
|
}
|
|
|
|
let layerLogicalId = this.provider.naming.getLambdaLayerLogicalId(layerName)
|
|
const layerArtifactPath = getLambdaLayerArtifactPath(
|
|
this.packagePath,
|
|
layerName,
|
|
this.provider.serverless.service,
|
|
this.provider.naming,
|
|
)
|
|
return fsAsync.readFile(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,
|
|
},
|
|
)
|
|
})
|
|
}
|
|
|
|
async 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
|
|
log.info(`Layer ${layerName} is already uploaded.`)
|
|
},
|
|
(e) => {
|
|
if (e.message.includes('does not exist')) {
|
|
return
|
|
}
|
|
throw e
|
|
},
|
|
)
|
|
}
|
|
|
|
async compileLayers() {
|
|
const allLayers = this.serverless.service.getAllLayers()
|
|
await 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',
|
|
}
|
|
}
|
|
}
|
|
|
|
export default AwsCompileLayers
|