mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
223 lines
7.5 KiB
JavaScript
223 lines
7.5 KiB
JavaScript
'use strict';
|
|
|
|
const _ = require('lodash');
|
|
const fsp = require('fs').promises;
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const limit = require('ext/promise/limit').bind(Promise);
|
|
const filesize = require('filesize');
|
|
const normalizeFiles = require('../../lib/normalize-files');
|
|
const getLambdaLayerArtifactPath = require('../../utils/get-lambda-layer-artifact-path');
|
|
const ServerlessError = require('../../../../serverless-error');
|
|
const setS3UploadEncryptionOptions = require('../../../../aws/set-s3-upload-encryption-options');
|
|
const { progress, log } = require('@serverless/utils/log');
|
|
|
|
const MAX_CONCURRENT_ARTIFACTS_UPLOADS =
|
|
Number(process.env.SLS_MAX_CONCURRENT_ARTIFACTS_UPLOADS) || 3;
|
|
|
|
module.exports = {
|
|
async getFileStats(filepath) {
|
|
try {
|
|
return await fsp.stat(filepath);
|
|
} catch (error) {
|
|
throw new ServerlessError(
|
|
`Cannot read file artifact "${filepath}": ${error.message}`,
|
|
'INACCESSIBLE_FILE_ARTIFACT'
|
|
);
|
|
}
|
|
},
|
|
|
|
async uploadArtifacts() {
|
|
const artifactFilePaths = [
|
|
...(await this.getFunctionArtifactFilePaths()),
|
|
...this.getLayerArtifactFilePaths(),
|
|
];
|
|
if (artifactFilePaths.length === 1) {
|
|
const stats = await this.getFileStats(artifactFilePaths[0]);
|
|
progress.get('main').notice(`Uploading (${filesize(stats.size)})`);
|
|
} else {
|
|
progress.get('main').notice(`Uploading (0/${artifactFilePaths.length})`);
|
|
}
|
|
|
|
await this.uploadCloudFormationFile();
|
|
await this.uploadStateFile();
|
|
await this.uploadFunctionsAndLayers();
|
|
await this.uploadCustomResources();
|
|
},
|
|
|
|
async uploadCloudFormationFile() {
|
|
log.info('Uploading CloudFormation file to S3');
|
|
|
|
const compiledTemplateFileName = this.provider.naming.getCompiledTemplateS3Suffix();
|
|
|
|
const compiledCfTemplate = this.serverless.service.provider.compiledCloudFormationTemplate;
|
|
const normCfTemplate = normalizeFiles.normalizeCloudFormationTemplate(compiledCfTemplate);
|
|
const fileHash = crypto
|
|
.createHash('sha256')
|
|
.update(JSON.stringify(normCfTemplate))
|
|
.digest('base64');
|
|
|
|
let params = {
|
|
Bucket: this.bucketName,
|
|
Key: `${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`,
|
|
Body: JSON.stringify(compiledCfTemplate),
|
|
ContentType: 'application/json',
|
|
Metadata: {
|
|
filesha256: fileHash,
|
|
},
|
|
};
|
|
|
|
const deploymentBucketObject = this.serverless.service.provider.deploymentBucketObject;
|
|
if (deploymentBucketObject) {
|
|
params = setS3UploadEncryptionOptions(params, deploymentBucketObject);
|
|
}
|
|
|
|
return this.provider.request('S3', 'upload', params);
|
|
},
|
|
async uploadStateFile() {
|
|
log.info('Uploading State file to S3');
|
|
|
|
const basename = this.provider.naming.getServiceStateFileName();
|
|
const content = await fsp.readFile(
|
|
path.join(this.serverless.serviceDir, '.serverless', basename),
|
|
'utf-8'
|
|
);
|
|
|
|
const fileHash = crypto.createHash('sha256').update(content).digest('base64');
|
|
|
|
let params = {
|
|
Bucket: this.bucketName,
|
|
Key: `${this.serverless.service.package.artifactDirectoryName}/${basename}`,
|
|
Body: content,
|
|
ContentType: 'application/json',
|
|
Metadata: { filesha256: fileHash },
|
|
};
|
|
|
|
const deploymentBucketObject = this.serverless.service.provider.deploymentBucketObject;
|
|
if (deploymentBucketObject) {
|
|
params = setS3UploadEncryptionOptions(params, deploymentBucketObject);
|
|
}
|
|
|
|
return this.provider.request('S3', 'upload', params);
|
|
},
|
|
|
|
async getFunctionArtifactFilePaths() {
|
|
const functionNames = this.serverless.service.getAllFunctions();
|
|
return _.uniq(
|
|
(
|
|
await Promise.all(
|
|
functionNames.map(async (name) => {
|
|
const functionObject = this.serverless.service.getFunction(name);
|
|
if (functionObject.image) return null;
|
|
const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name);
|
|
functionObject.package = functionObject.package || {};
|
|
let artifactFilePath =
|
|
functionObject.package.artifact || this.serverless.service.package.artifact;
|
|
|
|
if (
|
|
!artifactFilePath ||
|
|
(this.serverless.service.artifact && !functionObject.package.artifact)
|
|
) {
|
|
if (
|
|
this.serverless.service.package.individually ||
|
|
functionObject.package.individually
|
|
) {
|
|
const artifactFileName = functionArtifactFileName;
|
|
artifactFilePath = path.join(this.packagePath, artifactFileName);
|
|
} else {
|
|
artifactFilePath = path.join(
|
|
this.packagePath,
|
|
this.provider.naming.getServiceArtifactName()
|
|
);
|
|
}
|
|
}
|
|
functionObject.artifactSize = (await this.getFileStats(artifactFilePath)).size;
|
|
return artifactFilePath;
|
|
})
|
|
)
|
|
).filter(Boolean)
|
|
);
|
|
},
|
|
|
|
getLayerArtifactFilePaths() {
|
|
const layerNames = this.serverless.service.getAllLayers();
|
|
return layerNames
|
|
.map((name) => {
|
|
const layerObject = this.serverless.service.getLayer(name);
|
|
if (layerObject.artifactAlreadyUploaded) {
|
|
log.info(`Skipped uploading ${name}`);
|
|
return null;
|
|
}
|
|
return getLambdaLayerArtifactPath(
|
|
this.packagePath,
|
|
name,
|
|
this.provider.serverless.service,
|
|
this.provider.naming
|
|
);
|
|
})
|
|
.filter(Boolean);
|
|
},
|
|
|
|
async uploadFunctionsAndLayers() {
|
|
const artifactFilePaths = [
|
|
...(await this.getFunctionArtifactFilePaths()),
|
|
...this.getLayerArtifactFilePaths(),
|
|
];
|
|
|
|
const shouldReportDetailedProgress = artifactFilePaths.length > 1;
|
|
let alreadyUploadedCount = 0;
|
|
|
|
const limitedUpload = limit(
|
|
MAX_CONCURRENT_ARTIFACTS_UPLOADS,
|
|
async ({ filename, s3KeyDirname }) => {
|
|
const stats = await this.getFileStats(filename);
|
|
const fileName = path.basename(filename);
|
|
log.info(`Uploading service ${fileName} file to S3 (${filesize(stats.size)})`);
|
|
if (shouldReportDetailedProgress) {
|
|
progress
|
|
.get(`upload:${fileName}`)
|
|
.notice(`Uploading service ${fileName} file to S3 (${filesize(stats.size)})`);
|
|
}
|
|
const result = await this.uploadZipFile({
|
|
filename,
|
|
s3KeyDirname,
|
|
});
|
|
alreadyUploadedCount += 1;
|
|
if (shouldReportDetailedProgress) {
|
|
progress
|
|
.get('main')
|
|
.notice(`Uploading (${alreadyUploadedCount}/${artifactFilePaths.length})`);
|
|
progress.get(`upload:${fileName}`).remove();
|
|
}
|
|
return result;
|
|
}
|
|
);
|
|
const uploadPromises = artifactFilePaths.map((filename) =>
|
|
limitedUpload({
|
|
filename,
|
|
s3KeyDirname: this.serverless.service.package.artifactDirectoryName,
|
|
})
|
|
);
|
|
if (this.console.isEnabled) {
|
|
uploadPromises.push(this.console.uploadOtelExtensionLayer());
|
|
}
|
|
await Promise.all(uploadPromises);
|
|
},
|
|
|
|
async uploadCustomResources() {
|
|
const artifactFilePath = path.join(
|
|
this.serverless.serviceDir,
|
|
'.serverless',
|
|
this.provider.naming.getCustomResourcesArtifactName()
|
|
);
|
|
|
|
if (this.serverless.utils.fileExistsSync(artifactFilePath)) {
|
|
log.info('Uploading custom CloudFormation resources');
|
|
await this.uploadZipFile({
|
|
filename: artifactFilePath,
|
|
s3KeyDirname: this.serverless.service.package.artifactDirectoryName,
|
|
});
|
|
}
|
|
},
|
|
};
|