From cf29464a57f695ae663dc3a1c3a1f7c25f8d6bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Thu, 15 Dec 2022 17:48:56 +0100 Subject: [PATCH 1/8] feat: Support Scaleway provider --- index.js | 5 ++++- lib/inject.js | 23 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 246b121e..25cc34cd 100644 --- a/index.js +++ b/index.js @@ -72,7 +72,10 @@ class ServerlessPythonRequirements { ) { options.pythonBin = 'python'; } - + if (/python3[0-9]+/.test(options.pythonBin)) { + // "google" and "scaleway" providers' runtimes uses python3XX + options.pythonBin = options.pythonBin.replace(/3([0-9]+)/, '3.$1'); + } if (options.dockerizePip === 'non-linux') { options.dockerizePip = process.platform !== 'linux'; } diff --git a/lib/inject.js b/lib/inject.js index ea20e58d..12267376 100644 --- a/lib/inject.js +++ b/lib/inject.js @@ -13,10 +13,16 @@ 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, options) { +function injectRequirements( + requirementsPath, + packagePath, + injectionRelativePath, + options +) { const noDeploy = new Set(options.noDeploy || []); return fse @@ -29,7 +35,13 @@ function injectRequirements(requirementsPath, packagePath, options) { dot: true, }) ) - .map((file) => [file, path.relative(requirementsPath, file)]) + .map((file) => [ + file, + path.join( + injectionRelativePath, + path.relative(requirementsPath, file) + ), + ]) .filter( ([file, relativeFile]) => !file.endsWith('/') && @@ -101,6 +113,11 @@ async function injectAllRequirements(funcArtifact) { 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) @@ -138,6 +155,7 @@ async function injectAllRequirements(funcArtifact) { : injectRequirements( path.join('.serverless', func.module, 'requirements'), func.package.artifact, + injectionRelativePath, this.options ); }); @@ -145,6 +163,7 @@ async function injectAllRequirements(funcArtifact) { await injectRequirements( path.join('.serverless', 'requirements'), this.serverless.service.package.artifact || funcArtifact, + injectionRelativePath, this.options ); } From 57fc0f648bad97b6f17440c2cbf1e196ad385f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Mon, 9 Jan 2023 14:26:13 +0100 Subject: [PATCH 2/8] test: add test for injection path with scaleway provider --- test.js | 59 ++++++++++++++--------- tests/scaleway_provider/_slimPatterns.yml | 2 + tests/scaleway_provider/handler.py | 5 ++ tests/scaleway_provider/package.json | 15 ++++++ tests/scaleway_provider/requirements.txt | 3 ++ tests/scaleway_provider/serverless.yml | 34 +++++++++++++ 6 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 tests/scaleway_provider/_slimPatterns.yml create mode 100644 tests/scaleway_provider/handler.py create mode 100644 tests/scaleway_provider/package.json create mode 100644 tests/scaleway_provider/requirements.txt create mode 100644 tests/scaleway_provider/serverless.yml diff --git a/test.js b/test.js index 673bf631..5c5b015b 100644 --- a/test.js +++ b/test.js @@ -22,30 +22,30 @@ const initialWorkingDir = process.cwd(); const mkCommand = (cmd) => - (args, options = {}) => { - options['env'] = Object.assign( - { SLS_DEBUG: 'true' }, - process.env, - options['env'] - ); - const { error, stdout, stderr, status } = crossSpawn.sync( - cmd, - args, - options - ); - if (error && !options['noThrow']) { - console.error(`Error running: ${quote([cmd, ...args])}`); // eslint-disable-line no-console - throw error; - } - if (status && !options['noThrow']) { - console.error('STDOUT: ', stdout.toString()); // eslint-disable-line no-console - console.error('STDERR: ', stderr.toString()); // eslint-disable-line no-console - throw new Error( - `${quote([cmd, ...args])} failed with status code ${status}` + (args, options = {}) => { + options['env'] = Object.assign( + { SLS_DEBUG: 'true' }, + process.env, + options['env'] ); - } - return stdout && stdout.toString().trim(); - }; + const { error, stdout, stderr, status } = crossSpawn.sync( + cmd, + args, + options + ); + if (error && !options['noThrow']) { + console.error(`Error running: ${quote([cmd, ...args])}`); // eslint-disable-line no-console + throw error; + } + if (status && !options['noThrow']) { + console.error('STDOUT: ', stdout.toString()); // eslint-disable-line no-console + console.error('STDERR: ', stderr.toString()); // eslint-disable-line no-console + throw new Error( + `${quote([cmd, ...args])} failed with status code ${status}` + ); + } + return stdout && stdout.toString().trim(); + }; const sls = mkCommand('sls'); const git = mkCommand('git'); @@ -421,7 +421,7 @@ test( ); t.true( zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, + 0, '__main__.py files are packaged' ); t.end(); @@ -1715,3 +1715,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('py3.7 injects dependencies into `package` folder when using scaleway provider', async (t) => { + process.chdir('tests/scaleway_provider'); + const path = npm(['pack', '../..']); + npm(['i', path]); + sls(['package'], { env: {} }); + const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); + t.true(zipfiles.includes(`package${sep}flask${sep}__init__.py`), 'flask is packaged'); + t.true(zipfiles.includes(`package${sep}boto3${sep}__init__.py`), 'boto3 is packaged'); + t.end(); +}); diff --git a/tests/scaleway_provider/_slimPatterns.yml b/tests/scaleway_provider/_slimPatterns.yml new file mode 100644 index 00000000..443af9a0 --- /dev/null +++ b/tests/scaleway_provider/_slimPatterns.yml @@ -0,0 +1,2 @@ +slimPatterns: + - '**/__main__.py' diff --git a/tests/scaleway_provider/handler.py b/tests/scaleway_provider/handler.py new file mode 100644 index 00000000..5e2e67ff --- /dev/null +++ b/tests/scaleway_provider/handler.py @@ -0,0 +1,5 @@ +import requests + + +def hello(event, context): + return requests.get('https://httpbin.org/get').json() diff --git a/tests/scaleway_provider/package.json b/tests/scaleway_provider/package.json new file mode 100644 index 00000000..b81b0b71 --- /dev/null +++ b/tests/scaleway_provider/package.json @@ -0,0 +1,15 @@ +{ + "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-5.3.1.tgz", + "serverless-scaleway-functions": "^0.4.5" + } +} diff --git a/tests/scaleway_provider/requirements.txt b/tests/scaleway_provider/requirements.txt new file mode 100644 index 00000000..23bfb7a6 --- /dev/null +++ b/tests/scaleway_provider/requirements.txt @@ -0,0 +1,3 @@ +flask==0.12.5 +bottle +boto3 diff --git a/tests/scaleway_provider/serverless.yml b/tests/scaleway_provider/serverless.yml new file mode 100644 index 00000000..a781f30b --- /dev/null +++ b/tests/scaleway_provider/serverless.yml @@ -0,0 +1,34 @@ +service: sls-py-req-test + +configValidationMode: off + +provider: + name: scaleway + runtime: python37 + +plugins: + - serverless-python-requirements + - serverless-scaleway-functions + +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} + defaults: + zip: false + slimPatterns: false + slimPatternsAppendDefaults: true + slim: false + dockerizePip: false + +package: + patterns: + - '!**/*' + - 'handler.py' + +functions: + hello: + handler: handler.hello From 44936a7f725e18e2cf9290f336d71b6a0e1a700b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Mon, 9 Jan 2023 14:46:10 +0100 Subject: [PATCH 3/8] chore: format with prettify --- test.js | 58 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/test.js b/test.js index 5c5b015b..9d63f510 100644 --- a/test.js +++ b/test.js @@ -22,30 +22,30 @@ const initialWorkingDir = process.cwd(); const mkCommand = (cmd) => - (args, options = {}) => { - options['env'] = Object.assign( - { SLS_DEBUG: 'true' }, - process.env, - options['env'] - ); - const { error, stdout, stderr, status } = crossSpawn.sync( - cmd, - args, - options + (args, options = {}) => { + options['env'] = Object.assign( + { SLS_DEBUG: 'true' }, + process.env, + options['env'] + ); + const { error, stdout, stderr, status } = crossSpawn.sync( + cmd, + args, + options + ); + if (error && !options['noThrow']) { + console.error(`Error running: ${quote([cmd, ...args])}`); // eslint-disable-line no-console + throw error; + } + if (status && !options['noThrow']) { + console.error('STDOUT: ', stdout.toString()); // eslint-disable-line no-console + console.error('STDERR: ', stderr.toString()); // eslint-disable-line no-console + throw new Error( + `${quote([cmd, ...args])} failed with status code ${status}` ); - if (error && !options['noThrow']) { - console.error(`Error running: ${quote([cmd, ...args])}`); // eslint-disable-line no-console - throw error; - } - if (status && !options['noThrow']) { - console.error('STDOUT: ', stdout.toString()); // eslint-disable-line no-console - console.error('STDERR: ', stderr.toString()); // eslint-disable-line no-console - throw new Error( - `${quote([cmd, ...args])} failed with status code ${status}` - ); - } - return stdout && stdout.toString().trim(); - }; + } + return stdout && stdout.toString().trim(); + }; const sls = mkCommand('sls'); const git = mkCommand('git'); @@ -421,7 +421,7 @@ test( ); t.true( zipfiles.filter((filename) => filename.endsWith('__main__.py')).length > - 0, + 0, '__main__.py files are packaged' ); t.end(); @@ -1722,7 +1722,13 @@ test('py3.7 injects dependencies into `package` folder when using scaleway provi npm(['i', path]); sls(['package'], { env: {} }); const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); - t.true(zipfiles.includes(`package${sep}flask${sep}__init__.py`), 'flask is packaged'); - t.true(zipfiles.includes(`package${sep}boto3${sep}__init__.py`), 'boto3 is packaged'); + t.true( + zipfiles.includes(`package${sep}flask${sep}__init__.py`), + 'flask is packaged' + ); + t.true( + zipfiles.includes(`package${sep}boto3${sep}__init__.py`), + 'boto3 is packaged' + ); t.end(); }); From 3af90c37756d73833ec7d6936fb35c3ea320d731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Thu, 3 Aug 2023 14:38:52 +0200 Subject: [PATCH 4/8] test: bump scaleway plugin version to 0.4.7 --- tests/scaleway_provider/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scaleway_provider/package.json b/tests/scaleway_provider/package.json index b81b0b71..34fb2bff 100644 --- a/tests/scaleway_provider/package.json +++ b/tests/scaleway_provider/package.json @@ -10,6 +10,6 @@ "license": "ISC", "dependencies": { "serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz", - "serverless-scaleway-functions": "^0.4.5" + "serverless-scaleway-functions": "^0.4.7" } } From 56c1d6a99fe0141e3ece37da018c2659f1146547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Thu, 3 Aug 2023 14:44:06 +0200 Subject: [PATCH 5/8] docs: add custom provider section to README --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 6032725a..1531bc88 100644 --- a/README.md +++ b/README.md @@ -565,6 +565,31 @@ package: - '**' ``` +## Custom Provider Support + +### Scaleway + +This plugin is compatible with the [Scaleway Serverless Framework Plugin](https://github.com/scaleway/serverless-scaleway-functions) to package dependencies for Python functions deployed on [Scaleway](https://www.scaleway.com/en/serverless-functions/). To use it, add the following to your `serverless.yml`: + +```yaml +provider: + name: scaleway + runtime: python311 + +plugins: + - serverless-python-requirements + - serverless-scaleway-functions +``` + +To handle native dependencies, it's recommended to use the Docker builder with the image provided by Scaleway: + +```yaml +custom: + pythonRequirements: + # Can use any Python version supported by Scaleway + dockerImage: rg.fr-par.scw.cloud/scwfunctionsruntimes-public/python-dep:3.11 +``` + ## Contributors - [@dschep](https://github.com/dschep) - Lead developer & original maintainer From ed84993e0963afdf2edecf06d06f66e3c872215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Mon, 21 Aug 2023 11:39:55 +0200 Subject: [PATCH 6/8] fix(tests): bump provider requirements version --- tests/scaleway_provider/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/scaleway_provider/package.json b/tests/scaleway_provider/package.json index 34fb2bff..d54b88e0 100644 --- a/tests/scaleway_provider/package.json +++ b/tests/scaleway_provider/package.json @@ -9,7 +9,7 @@ "author": "", "license": "ISC", "dependencies": { - "serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz", - "serverless-scaleway-functions": "^0.4.7" + "serverless-python-requirements": "file:serverless-python-requirements-6.0.0.tgz", + "serverless-scaleway-functions": "^0.4.8" } } From 67d229c2486208e2f61c1a01e01c0bc86f31c0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Mon, 21 Aug 2023 11:48:34 +0200 Subject: [PATCH 7/8] test: empty commit to run CI from fork From 541a655bc73afa5e6e4ae9c0e83705d8860fdf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= <amery@scaleway.com> Date: Tue, 22 Aug 2023 10:13:36 +0200 Subject: [PATCH 8/8] fix(test): run npm i on the test package.json to install the scaleway plugin --- test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test.js b/test.js index 9d63f510..7a6b78a3 100644 --- a/test.js +++ b/test.js @@ -1718,8 +1718,8 @@ test('poetry py3.7 only installs optional packages specified in onlyGroups', asy test('py3.7 injects dependencies into `package` folder when using scaleway provider', async (t) => { process.chdir('tests/scaleway_provider'); - const path = npm(['pack', '../..']); - npm(['i', path]); + npm(['pack', '../..']); + npm(['i']); // install the serverless-scaleway-functions plugin sls(['package'], { env: {} }); const zipfiles = await listZipFiles('.serverless/sls-py-req-test.zip'); t.true(