diff --git a/README.md b/README.md index e11d842c..8d202e0e 100644 --- a/README.md +++ b/README.md @@ -69,18 +69,21 @@ custom: ``` ## Omitting Packages -You can omit a package from deployment by adding `#no-deploy` to the -requirement's line in `requirements.txt`. For example, this will not install -the AWS SDKs that are already installed on Lambda, but will install numpy: -``` -numpy -boto3 #no-deploy -botocore #no-deploy -docutils #no-deploy -jmespath #no-deploy -python-dateutil #no-deploy -s3transfer #no-deploy -six #no-deploy +You can omit a package from deployment with the `noDeploy` option. Note that +dependencies of omitted packages must explicitly be omitted too. +For example, this will not install the AWS SDKs that are already installed on +Lambda: +```yaml +custom: + pythonRequirements: + noDeploy: + - boto3 + - botocore + - docutils + - jmespath + - python-dateutil + - s3transfer + - six ``` ## extra pip arguments diff --git a/example/requirements.txt b/example/requirements.txt index f2293605..640e3d44 100644 --- a/example/requirements.txt +++ b/example/requirements.txt @@ -1 +1,2 @@ requests +six diff --git a/example/serverless.yml b/example/serverless.yml index 7a387436..58854698 100644 --- a/example/serverless.yml +++ b/example/serverless.yml @@ -11,6 +11,7 @@ custom: zip: false cleanupZipHelper: true dockerizePip: false + noDeploy: [] package: exclude: diff --git a/index.js b/index.js index 20d0a514..28c0d71f 100644 --- a/index.js +++ b/index.js @@ -41,41 +41,13 @@ class ServerlessPythonRequirements { } }; - /** - * parse requirements.txt into .requirements.txt, leaving out #no-deploy lines - * @return {true} - */ - parseRequirements() { - if (!fse.existsSync(path.join(this.serverless.config.servicePath, - 'requirements.txt'))) { - return true; - } - - this.serverless.cli.log( - `Parsing Python requirements.txt`); - - const reqs = fse.readFileSync('requirements.txt').toString().split('\n'); - - let newReqs = ''; - for (const req of reqs) { - if (req.indexOf('#no-deploy') === -1) { - newReqs += `${req}\n`; - } - } - if (!fse.existsSync('.serverless')) - fse.mkdirSync('.serverless'); - fse.writeFileSync('.serverless/requirements.txt', newReqs, 'utf8'); - - return true; - }; - /** * pip install the requirements to the .requirements directory * @return {Promise} */ installRequirements() { if (!fse.existsSync(path.join(this.serverless.config.servicePath, - '.serverless/requirements.txt'))) { + 'requirements.txt'))) { return BbPromise.resolve(); } @@ -88,7 +60,7 @@ class ServerlessPythonRequirements { let options; const pipCmd = [ runtime, '-m', 'pip', '--isolated', 'install', - '-t', '.requirements', '-r', '.serverless/requirements.txt', + '-t', '.requirements', '-r', 'requirements.txt', ]; if (this.custom().pipCmdExtraArgs) { pipCmd.push(...this.custom().pipCmdExtraArgs); @@ -148,9 +120,12 @@ class ServerlessPythonRequirements { linkRequirements() { if (!this.custom().zip) { this.serverless.cli.log('Linking required Python packages...'); + const noDeploy = new Set(this.custom().noDeploy || []); fse.readdirSync('.requirements').map((file) => { - this.serverless.service.package.include.push(file); - this.serverless.service.package.include.push(`${file}/**`); + if (noDeploy.has(file)) + return; + this.serverless.service.package.include.push(file); + this.serverless.service.package.include.push(`${file}/**`); try { fse.symlinkSync(`.requirements/${file}`, `./${file}`); } catch (exception) { @@ -159,11 +134,10 @@ class ServerlessPythonRequirements { linkDest = fse.readlinkSync(`./${file}`); } catch (e) {} if (linkDest !== `.requirements/${file}`) - throw new Error(`Unable to link dependency '${file}' because a file - by the same name exists in this service`); + throw new Error(`Unable to link dependency '${file}' because a file + by the same name exists in this service`); } - } - ); + }); } } @@ -244,7 +218,6 @@ class ServerlessPythonRequirements { let before = () => BbPromise.bind(this) .then(this.addVendorHelper) - .then(this.parseRequirements) .then(this.packRequirements) .then(this.linkRequirements); @@ -270,7 +243,6 @@ class ServerlessPythonRequirements { 'after:deploy:function:packageFunction': after, 'requirements:install:install': () => BbPromise.bind(this) .then(this.addVendorHelper) - .then(this.parseRequirements) .then(this.packRequirements), 'requirements:clean:clean': () => BbPromise.bind(this) .then(this.cleanup) diff --git a/test.bats b/test.bats index 579cfa49..8fe74f5d 100755 --- a/test.bats +++ b/test.bats @@ -29,6 +29,13 @@ teardown() { ls puck/.requirements.zip puck/unzip_requirements.py } +@test "py3.6 doesn't package six with noDeploy option" { + sed -i'.bak' -e 's/noDeploy:.*/noDeploy: [six]/' serverless.yml + sls package + unzip .serverless/sls-py-req-test.zip -d puck + ! ls puck/six +} + @test "py3.6 can package requests with zip & dockerizePip option" { [ -z "$CIRCLE_BRANCH" ] || skip "Volumes are weird in CircleCI https://circleci.com/docs/2.0/building-docker-images/#mounting-folders" ! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group" @@ -61,6 +68,13 @@ teardown() { ls puck/.requirements.zip puck/unzip_requirements.py } +@test "py2.7 doesn't package six with noDeploy option" { + sed -i'.bak' -e 's/runtime: *python3.6/runtime: python2.7/' -e 's/noDeploy:.*/noDeploy: [six]/' serverless.yml + sls package + unzip .serverless/sls-py-req-test.zip -d puck + ! ls puck/six +} + @test "py2.7 can package requests with zip & dockerizePip option" { [ -z "$CIRCLE_BRANCH" ] || skip "Volumes are weird in CircleCI https://circleci.com/docs/2.0/building-docker-images/#mounting-folders" ! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"