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 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 ); } diff --git a/test.js b/test.js index 673bf631..7a6b78a3 100644 --- a/test.js +++ b/test.js @@ -1715,3 +1715,20 @@ 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'); + 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( + 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..d54b88e0 --- /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-6.0.0.tgz", + "serverless-scaleway-functions": "^0.4.8" + } +} 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