Max Marze e0d6a8acbb
fix: remove bluebird and set concurrency limits for packaging (#12658)
* fix: remove bluebird from zip-service

* fix: remove bluebird and set concurrency limits for packaging
2024-07-02 14:26:28 -04:00

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