Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce size of installed dependencies by slimming #191

Merged
merged 27 commits into from Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91d80c3
replace all double backslashes
dee-me-tree-or-love May 14, 2018
76a92b0
add the slimmen logic
dee-me-tree-or-love May 14, 2018
7b7c429
adding test
dee-me-tree-or-love May 14, 2018
4362153
added a few more tests
dee-me-tree-or-love May 14, 2018
8389e2b
fix useless-escape eslint error
dee-me-tree-or-love May 14, 2018
b2d50fa
triple equal and one time folder path preparation
dee-me-tree-or-love May 15, 2018
f500910
prettier --write formatting
dee-me-tree-or-love May 15, 2018
82c1088
remove 1. from readme
dee-me-tree-or-love May 16, 2018
780af23
improved tests and a few bug fixes
dee-me-tree-or-love May 16, 2018
81e2663
better evaluate slim option in js
dee-me-tree-or-love May 16, 2018
c406b5f
add self to readme
dee-me-tree-or-love May 16, 2018
4302c17
prettier code without doublequotes
dee-me-tree-or-love May 16, 2018
1f9ae75
added a default in index.js + a dockerizePip disclaimer in README
dee-me-tree-or-love May 17, 2018
eeee343
moved slim option handling to apply globally + added failing pipenv test
dee-me-tree-or-love May 18, 2018
e121d17
split code to a separate file + unix-compatibility check + passing te…
dee-me-tree-or-love May 18, 2018
f12d600
fix the prettier issus
dee-me-tree-or-love May 22, 2018
474b823
added handling slim as array of removal patterns
dee-me-tree-or-love May 22, 2018
d3a6dc8
prettier fix
dee-me-tree-or-love May 22, 2018
26216d8
adding custom slim patterns tests
dee-me-tree-or-love May 22, 2018
83b170c
adjusted custom removed directories, added tests
dee-me-tree-or-love May 22, 2018
671cc72
update README and remove tests
dee-me-tree-or-love May 22, 2018
2230ae4
prettier code fix
dee-me-tree-or-love May 22, 2018
97212b2
README line fix
dee-me-tree-or-love May 22, 2018
89c46ba
remove forgotten log
dee-me-tree-or-love May 22, 2018
c71be73
Merge branch 'master' into master
dee-me-tree-or-love May 23, 2018
33433aa
Merge branch 'master' into master
dee-me-tree-or-love Jun 9, 2018
e6a4273
Merge branch 'master' into master
dschep Jun 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Expand Up @@ -95,7 +95,28 @@ try:
except ImportError:
pass
```

### Slim Package
_Works on non 'win32' environments: Docker, WSL are included_
To remove the tests, information and caches from the installed packages,
enable the `slim` option. This will: `strip` the `.so` files, remove `__pycache__`
directories and `dist-info` directories.
```yaml
custom:
pythonRequirements:
slim: true
```
#### Custom Removal Patterns
To specify additional directories to remove from the installed packages,
define the patterns using regex as a `slimPatterns` option in serverless config:
```yaml
custom:
pythonRequirements:
slim: true
slimPatterns:
- "*.egg-info*"
```
This will remove all folders within the installed requirements that match
the names in `slimPatterns`
## Omitting Packages
You can omit a package from deployment with the `noDeploy` option. Note that
dependencies of omitted packages must explicitly be omitted too.
Expand Down Expand Up @@ -266,3 +287,5 @@ For usage of `dockerizePip` on Windows do Step 1 only if running serverless on w
* [@kichik](https://github.com/kichik) - Imposed windows & `noDeploy` support,
switched to adding files straight to zip instead of creating symlinks, and
improved pip chache support when using docker.
* [@dee-me-tree-or-love](https://github.com/dee-me-tree-or-love) - the `slim` package option

1 change: 1 addition & 0 deletions index.js
Expand Up @@ -26,6 +26,7 @@ class ServerlessPythonRequirements {
get options() {
const options = Object.assign(
{
slim: false,
zip: false,
cleanupZipHelper: true,
invalidateCaches: false,
Expand Down
9 changes: 9 additions & 0 deletions lib/pip.js
Expand Up @@ -6,6 +6,7 @@ const set = require('lodash.set');
const { spawnSync } = require('child_process');
const values = require('lodash.values');
const { buildImage, getBindPath, getDockerUid } = require('./docker');
const { getSlimPackageCommands } = require('./slim');

/**
* Install requirements described in requirementsPath to targetFolder
Expand Down Expand Up @@ -122,6 +123,14 @@ function installRequirements(
cmd = pipCmd[0];
cmdOptions = pipCmd.slice(1);
}

// If enabled slimming, strip out the caches, tests and dist-infos
if (options.slim === true || options.slim === 'true') {
const preparedPath = dockerPathForWin(options, targetRequirementsFolder);
const slimCmd = getSlimPackageCommands(options, preparedPath);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the preparing of the strip command happens inside of this method - in case when the environment does not meet expectations (win32 systems) returns an empty array.

To be honest, I am slightly worried about this - theoretically running these commands inside of CYGWIN or Git Bash (or other bash emulators) should be just fine - but I couldn't find a way yet to check for this... would appreciate any advice! :)

cmdOptions.push(...slimCmd);
}

const res = spawnSync(cmd, cmdOptions, { cwd: servicePath, shell: true });
if (res.error) {
if (res.error.code === 'ENOENT') {
Expand Down
59 changes: 59 additions & 0 deletions lib/slim.js
@@ -0,0 +1,59 @@
const isWsl = require('is-wsl');

/**
* Get commands to slim the installed requirements
* only for non-windows platforms:
* works for docker builds and when run on UNIX platforms (wsl included)
* @param {Object} options
* @param {string} folderPath
* @return {Array.<String>}
*/
function getSlimPackageCommands(options, folderPath) {
let stripCmd = [];

// Default stripping is done for non-windows environments
if (process.platform !== 'win32' || isWsl) {
stripCmd = getDefaultSLimOptions(folderPath);

// If specified any custom patterns to remove
if (options.slimPatterns instanceof Array) {
// Add the custom specified patterns to remove to the default commands
const customPatterns = options.slimPatterns.map(pattern => {
return getRemovalCommand(folderPath, pattern);
});
stripCmd = stripCmd.concat(customPatterns);
}
}
return stripCmd;
}

/**
* Gets the commands to slim the default (safe) files:
* including removing caches, stripping compiled files, removing dist-infos
* @param {String} folderPath
* @return {Array}
*/
function getDefaultSLimOptions(folderPath) {
return [
`&& find ${folderPath} -name "*.so" -exec strip {} \\;`,
`&& find ${folderPath} -name "*.py[c|o]" -exec rm -rf {} +`,
`&& find ${folderPath} -type d -name "__pycache__*" -exec rm -rf {} +`,
`&& find ${folderPath} -type d -name "*.dist-info*" -exec rm -rf {} +`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe removing egg-info would also be fine within default slim options? 🤔

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dee-me-tree-or-love test folders?

 11M	./pandas/tests/io/sas/data
 11M	./pandas/tests/io/sas
2.0M	./pandas/tests/io/data/legacy_pickle
4.8M	./pandas/tests/io/data
 17M	./pandas/tests/io
1.2M	./pandas/tests/indexes
 24M	./pandas/tests

];
}

/**
* Get the command created fromt he find and remove template:
* returns a string in form `&& find <folder> -name "<match>" -exec rm -rf {} +`
* @param {String} folderPath
* @param {String} removalMatch
* @return {String}
*/
function getRemovalCommand(folderPath, removalMatch) {
return `&& find ${folderPath} -type d -name "${removalMatch}" -exec rm -rf {} +`;
}

module.exports = {
getSlimPackageCommands,
getDefaultSLimOptions
};
166 changes: 166 additions & 0 deletions test.bats
Expand Up @@ -32,6 +32,27 @@ teardown() {
! ls puck/flask
}

@test "py3.6 can package flask with slim options" {
cd tests/base
npm i $(npm pack ../..)
sls --slim=true package
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
}

@test "py3.6 can package flask with slim & slimPatterns options" {
cd tests/base
mv _slimPatterns.yml slimPatterns.yml
npm i $(npm pack ../..)
sls --slim=true package
mv slimPatterns.yml _slimPatterns.yml
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
}

@test "py3.6 doesn't package boto3 by default" {
cd tests/base
npm i $(npm pack ../..)
Expand Down Expand Up @@ -59,6 +80,15 @@ teardown() {
ls puck/.requirements.zip puck/unzip_requirements.py
}

@test "py3.6 can package flask with zip & slim & dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
sls --dockerizePip=true --zip=true --slim=true package
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/.requirements.zip puck/unzip_requirements.py
}

@test "py3.6 can package flask with dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -68,6 +98,29 @@ teardown() {
ls puck/flask
}

@test "py3.6 can package flask with slim & dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
sls --dockerizePip=true --slim=true package
unzip .serverless/sls-py-req-test.zip -d puck
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌 Thanks for including tests too!!

Could you add this to also check that the unzipped services doesn't contain any pyc files?

test $(find . -name *.pyc | wc -l) -eq 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's an awesome one, thanks, didn't know of this, will do now! ✌️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should I put test $(find puck -name *.pyc | wc -l) -eq 0 maybe? so it runs inside of the puck folder?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. My mistake.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dee-me-tree-or-love, @dschep Wouldn't you want to dump the .py and not .pyc?

ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
}

@test "py3.6 can package flask with slim & dockerizePip & slimPatterns options" {
cd tests/base
mv _slimPatterns.yml slimPatterns.yml
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
sls --dockerizePip=true --slim=true package
mv slimPatterns.yml _slimPatterns.yml
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
}

@test "py3.6 uses cache with dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -77,6 +130,17 @@ teardown() {
ls .requirements-cache/http
}

@test "py3.6 uses cache with dockerizePip & slim option" {
cd tests/base
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
perl -p -i'.bak' -e 's/(pythonRequirements:$)/\1\n pipCmdExtraArgs: ["--cache-dir", ".requirements-cache"]/' serverless.yml
sls --dockerizePip=true --slim=true package
ls .requirements-cache/http
test $(find puck -name "*.pyc" | wc -l) -eq 0
}


@test "py2.7 can package flask with default options" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -85,6 +149,15 @@ teardown() {
ls puck/flask
}

@test "py2.7 can package flask with slim option" {
cd tests/base
npm i $(npm pack ../..)
sls --runtime=python2.7 --slim=true package
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
}

@test "py2.7 can package flask with zip option" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -93,6 +166,18 @@ teardown() {
ls puck/.requirements.zip puck/unzip_requirements.py
}

@test "py2.7 can package flask with slim & dockerizePip & slimPatterns options" {
cd tests/base
mv _slimPatterns.yml slimPatterns.yml
npm i $(npm pack ../..)
sls --runtime=python2.7 --slim=true packag
mv slimPatterns.yml _slimPatterns.yml
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
}

@test "py2.7 doesn't package boto3 by default" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -119,6 +204,15 @@ teardown() {
ls puck/.requirements.zip puck/unzip_requirements.py
}

@test "py2.7 can package flask with zip & slim & dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
sls --dockerizePip=true --runtime=python2.7 --zip=true --slim=true package
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/.requirements.zip puck/unzip_requirements.py
}

@test "py2.7 can package flask with dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -128,6 +222,29 @@ teardown() {
ls puck/flask
}

@test "py2.7 can package flask with slim & dockerizePip option" {
cd tests/base
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
sls --dockerizePip=true --slim=true --runtime=python2.7 package
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
}

@test "py2.7 can package flask with slim & dockerizePip & slimPatterns options" {
cd tests/base
mv _slimPatterns.yml slimPatterns.yml
npm i $(npm pack ../..)
! uname -sm|grep Linux || groups|grep docker || id -u|egrep '^0$' || skip "can't dockerize on linux if not root & not in docker group"
sls --dockerizePip=true --slim=true --runtime=python2.7 package
mv slimPatterns.yml _slimPatterns.yml
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
}

@test "pipenv py3.6 can package flask with default options" {
cd tests/pipenv
npm i $(npm pack ../..)
Expand All @@ -136,6 +253,27 @@ teardown() {
ls puck/flask
}

@test "pipenv py3.6 can package flask with slim option" {
cd tests/pipenv
npm i $(npm pack ../..)
sls --slim=true package
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
}

@test "pipenv py3.6 can package flask with slim & slimPatterns option" {
cd tests/pipenv
npm i $(npm pack ../..)
mv _slimPatterns.yml slimPatterns.yml
sls --slim=true package
mv slimPatterns.yml _slimPatterns.yml
unzip .serverless/sls-py-req-test.zip -d puck
ls puck/flask
test $(find puck -name "*.pyc" | wc -l) -eq 0
test $(find puck -type d -name "*.egg-info*" | wc -l) -eq 0
}

@test "pipenv py3.6 can package flask with zip option" {
cd tests/pipenv
npm i $(npm pack ../..)
Expand Down Expand Up @@ -182,6 +320,20 @@ teardown() {
! ls puck3/flask
}

@test "py3.6 can package flask with package individually & slim option" {
cd tests/base
npm i $(npm pack ../..)
sls --individually=true --slim=true package
unzip .serverless/hello.zip -d puck
unzip .serverless/hello2.zip -d puck2
unzip .serverless/hello3.zip -d puck3
ls puck/flask
ls puck2/flask
! ls puck3/flask
test $(find "puck*" -name "*.pyc" | wc -l) -eq 0
}


@test "py2.7 can package flask with package individually option" {
cd tests/base
npm i $(npm pack ../..)
Expand All @@ -194,6 +346,20 @@ teardown() {
! ls puck3/flask
}

@test "py2.7 can package flask with package individually & slim option" {
cd tests/base
npm i $(npm pack ../..)
sls --individually=true --slim=true --runtime=python2.7 package
unzip .serverless/hello.zip -d puck
unzip .serverless/hello2.zip -d puck2
unzip .serverless/hello3.zip -d puck3
ls puck/flask
ls puck2/flask
! ls puck3/flask
test $(find puck* -name "*.pyc" | wc -l) -eq 0
}


@test "py3.6 can package only requirements of module" {
cd tests/individually
npm i $(npm pack ../..)
Expand Down
2 changes: 2 additions & 0 deletions tests/base/_slimPatterns.yml
@@ -0,0 +1,2 @@
slimPatterns:
- "*.egg-info*"
4 changes: 4 additions & 0 deletions tests/base/serverless.yml
Expand Up @@ -10,8 +10,12 @@ custom:
pythonRequirements:
zip: ${opt:zip, self:custom.defaults.zip}
dockerizePip: ${opt:dockerizePip, self:custom.defaults.dockerizePip}
slim: ${opt:slim, self:custom.defaults.slim}
slimPatterns: ${file(./slimPatterns.yml):slimPatterns, self:custom.defaults.slimPatterns}
vendor: ${opt:vendor, ''}
defaults:
slim: false
slimPatterns: false
zip: false
dockerizePip: false
individually: false
Expand Down
2 changes: 2 additions & 0 deletions tests/pipenv/_slimPatterns.yml
@@ -0,0 +1,2 @@
slimPatterns:
- "*.egg-info*"