serverless-python-requirements
Version:
Serverless Python Requirements Plugin
181 lines (170 loc) • 5.4 kB
JavaScript
const BbPromise = require('bluebird');
const fse = require('fs-extra');
const glob = require('glob-all');
const get = require('lodash.get');
const set = require('set-value');
const path = require('path');
const JSZip = require('jszip');
const { writeZip, zipFile } = require('./zipTree');
BbPromise.promisifyAll(fse);
/**
* Inject requirements into packaged application.
* @param {string} requirementsPath requirements folder path
* @param {string} packagePath target package path
* @param {string} injectionRelativePath installation directory in target package
* @param {Object} options our options object
* @return {Promise} the JSZip object constructed.
*/
function injectRequirements(
requirementsPath,
packagePath,
injectionRelativePath,
options
) {
const noDeploy = new Set(options.noDeploy || []);
return fse
.readFileAsync(packagePath)
.then((buffer) => JSZip.loadAsync(buffer))
.then((zip) =>
BbPromise.resolve(
glob.sync([path.join(requirementsPath, '**')], {
mark: true,
dot: true,
})
)
.map((file) => [
file,
path.join(
injectionRelativePath,
path.relative(requirementsPath, file)
),
])
.filter(
([file, relativeFile]) =>
!file.endsWith('/') &&
!relativeFile.match(/^__pycache__[\\/]/) &&
!noDeploy.has(relativeFile.split(/([-\\/]|\.py$|\.pyc$)/, 1)[0])
)
.map(([file, relativeFile]) =>
Promise.all([file, relativeFile, fse.statAsync(file)])
)
.mapSeries(([file, relativeFile, fileStat]) =>
zipFile(zip, relativeFile, fse.readFileAsync(file), {
unixPermissions: fileStat.mode,
createFolders: false,
})
)
.then(() => writeZip(zip, packagePath))
);
}
/**
* Remove all modules but the selected module from a package.
* @param {string} source path to original package
* @param {string} target path to result package
* @param {string} module module to keep
* @return {Promise} the JSZip object written out.
*/
function moveModuleUp(source, target, module) {
const targetZip = new JSZip();
return fse
.readFileAsync(source)
.then((buffer) => JSZip.loadAsync(buffer))
.then((sourceZip) =>
sourceZip.filter(
(file) =>
file.startsWith(module + '/') ||
file.startsWith('serverless_sdk/') ||
file.match(/^s_.*\.py/) !== null
)
)
.map((srcZipObj) =>
zipFile(
targetZip,
srcZipObj.name.startsWith(module + '/')
? srcZipObj.name.replace(module + '/', '')
: srcZipObj.name,
srcZipObj.async('nodebuffer')
)
)
.then(() => writeZip(targetZip, target));
}
/**
* Inject requirements into packaged application.
* @return {Promise} the combined promise for requirements injection.
*/
async function injectAllRequirements(funcArtifact) {
if (this.options.layer) {
// The requirements will be placed in a Layer, so just resolve
return BbPromise.resolve();
}
let injectProgress;
if (this.progress && this.log) {
injectProgress = this.progress.get('python-inject-requirements');
injectProgress.update('Injecting required Python packages to package');
this.log.info('Injecting required Python packages to package');
} else {
this.serverless.cli.log('Injecting required Python packages to package...');
}
let injectionRelativePath = '.';
if (this.serverless.service.provider.name == 'scaleway') {
injectionRelativePath = 'package';
}
try {
if (this.serverless.service.package.individually) {
await BbPromise.resolve(this.targetFuncs)
.filter((func) =>
(func.runtime || this.serverless.service.provider.runtime).match(
/^python.*/
)
)
.map((func) => {
if (!get(func, 'module')) {
set(func, ['module'], '.');
}
return func;
})
.map((func) => {
if (func.module !== '.') {
const artifact = func.package
? func.package.artifact
: funcArtifact;
const newArtifact = path.join(
'.serverless',
`${func.module}-${func.name}.zip`
);
func.package.artifact = newArtifact;
return moveModuleUp(artifact, newArtifact, func.module).then(
() => func
);
} else {
return func;
}
})
.map((func) => {
return this.options.zip
? func
: injectRequirements(
path.join(
this.serverless.serviceDir,
'.serverless',
func.module,
'requirements'
),
func.package.artifact,
injectionRelativePath,
this.options
);
});
} else if (!this.options.zip) {
await injectRequirements(
path.join(this.serverless.serviceDir, '.serverless', 'requirements'),
this.serverless.service.package.artifact || funcArtifact,
injectionRelativePath,
this.options
);
}
} finally {
injectProgress && injectProgress.remove();
}
}
module.exports = { injectAllRequirements };