diff --git a/lib/layer.js b/lib/layer.js index 6fe9ca4c..f9b7f3d2 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -39,9 +39,10 @@ function zipRequirements() { } else { const rootZip = new JSZip(); const runtimepath = 'python'; + const noDeploy = new Set(this.options.noDeploy || []); promises.push( - addTree(rootZip.folder(runtimepath), src).then(() => + addTree(rootZip.folder(runtimepath), src, noDeploy).then(() => writeZip(rootZip, zipCachePath) ) ); diff --git a/lib/zip.js b/lib/zip.js index 4b652f98..cf03fce1 100644 --- a/lib/zip.js +++ b/lib/zip.js @@ -112,6 +112,7 @@ function removeVendorHelper() { */ function packRequirements() { if (this.options.zip) { + const noDeploy = new Set(this.options.noDeploy || []); if (this.serverless.service.package.individually) { return BbPromise.resolve(this.targetFuncs) .map((f) => { @@ -137,7 +138,11 @@ function packRequirements() { ); } f.package.patterns.push(`${f.module}/.requirements.zip`); - return addTree(new JSZip(), `.serverless/${f.module}/requirements`) + return addTree( + new JSZip(), + `.serverless/${f.module}/requirements`, + noDeploy + ) .then((zip) => writeZip(zip, `${f.module}/.requirements.zip`)) .finally(() => packProgress && packProgress.remove()); }); @@ -149,7 +154,7 @@ function packRequirements() { this.serverless.cli.log('Zipping required Python packages...'); } this.serverless.service.package.patterns.push('.requirements.zip'); - return addTree(new JSZip(), '.serverless/requirements') + return addTree(new JSZip(), '.serverless/requirements', noDeploy) .then((zip) => writeZip(zip, path.join(this.servicePath, '.requirements.zip')) ) diff --git a/lib/zipTree.js b/lib/zipTree.js index 1654f665..32414c31 100644 --- a/lib/zipTree.js +++ b/lib/zipTree.js @@ -8,19 +8,21 @@ BbPromise.promisifyAll(fse); * Add a directory recursively to a zip file. Files in src will be added to the top folder of zip. * @param {JSZip} zip a zip object in the folder you want to add files to. * @param {string} src the source folder. + * @param {object} noDeploy the package to be omitted * @return {Promise} a promise offering the original JSZip object. */ -function addTree(zip, src) { +function addTree(zip, src, noDeploy) { const srcN = path.normalize(src); return fse .readdirAsync(srcN) + .filter((name) => !noDeploy.has(name)) .map((name) => { const srcPath = path.join(srcN, name); return fse.statAsync(srcPath).then((stat) => { if (stat.isDirectory()) { - return addTree(zip.folder(name), srcPath); + return addTree(zip.folder(name), srcPath, noDeploy); } else { const opts = { date: stat.mtime, unixPermissions: stat.mode }; return fse diff --git a/test.js b/test.js index c7232a6e..cb438db1 100644 --- a/test.js +++ b/test.js @@ -1703,3 +1703,14 @@ test('poetry py3.7 only installs optional packages specified in onlyGroups', asy t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged'); t.end(); }); + +test("enable layer option doesn't package bottle with noDeploy option", async (t) => { + process.chdir('tests/layer_nodeploy'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: { noDeploy: ['bottle'] } }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged'); + t.false(zipfiles.includes(`bottle.py`), 'bottle is NOT packaged'); + t.end(); +}); diff --git a/tests/layer_nodeploy/_slimPatterns.yml b/tests/layer_nodeploy/_slimPatterns.yml new file mode 100644 index 00000000..443af9a0 --- /dev/null +++ b/tests/layer_nodeploy/_slimPatterns.yml @@ -0,0 +1,2 @@ +slimPatterns: + - '**/__main__.py' diff --git a/tests/layer_nodeploy/handler.py b/tests/layer_nodeploy/handler.py new file mode 100644 index 00000000..5e2e67ff --- /dev/null +++ b/tests/layer_nodeploy/handler.py @@ -0,0 +1,5 @@ +import requests + + +def hello(event, context): + return requests.get('https://httpbin.org/get').json() diff --git a/tests/layer_nodeploy/package.json b/tests/layer_nodeploy/package.json new file mode 100644 index 00000000..781a4259 --- /dev/null +++ b/tests/layer_nodeploy/package.json @@ -0,0 +1,14 @@ +{ + "name": "example", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "serverless-python-requirements": "file:serverless-python-requirements-6.0.0.tgz" + } +} diff --git a/tests/layer_nodeploy/pyproject.toml b/tests/layer_nodeploy/pyproject.toml new file mode 100644 index 00000000..b813968a --- /dev/null +++ b/tests/layer_nodeploy/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "poetry" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" +Flask = "^1.0" +bottle = {git = "https://git@github.com/bottlepy/bottle.git", tag = "0.12.16"} +boto3 = "^1.9" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/tests/layer_nodeploy/serverless.yml b/tests/layer_nodeploy/serverless.yml new file mode 100644 index 00000000..a2c3648f --- /dev/null +++ b/tests/layer_nodeploy/serverless.yml @@ -0,0 +1,34 @@ +service: sls-py-req-test + +provider: + name: aws + runtime: python3.7 + +plugins: + - serverless-python-requirements +custom: + pythonRequirements: + zip: ${env:zip, self:custom.defaults.zip} + slim: ${env:slim, self:custom.defaults.slim} + slimPatterns: ${file(./slimPatterns.yml):slimPatterns, self:custom.defaults.slimPatterns} + slimPatternsAppendDefaults: ${env:slimPatternsAppendDefaults, self:custom.defaults.slimPatternsAppendDefaults} + dockerizePip: ${env:dockerizePip, self:custom.defaults.dockerizePip} + requirePoetryLockFile: ${env:requirePoetryLockFile, false} + layer: true + noDeploy: + - 'bottle' + defaults: + zip: false + slimPatterns: false + slimPatternsAppendDefaults: true + slim: false + dockerizePip: false + +package: + patterns: + - '!**/*' + - 'handler.py' + +functions: + hello: + handler: handler.hello