From fb7a75732091c970a567e027992c916c3c30b9e0 Mon Sep 17 00:00:00 2001 From: amottier Date: Fri, 5 Jul 2019 13:12:41 +0200 Subject: [PATCH 1/2] add container --- deploy/lib/createContainers.js | 75 +++++ deploy/lib/createNamespace.js | 1 + deploy/lib/deployContainers.js | 54 ++++ deploy/lib/pushContainers.js | 52 ++++ deploy/scalewayDeploy.js | 32 +- docs/README.md | 40 ++- examples/container/my-container/Dockerfile | 11 + .../container/my-container/requirements.txt | 1 + examples/container/my-container/server.py | 17 ++ examples/container/package.json | 17 ++ examples/container/serverless.yml | 32 ++ package-lock.json | 285 +++++++++++++++--- package.json | 10 +- provider/scalewayProvider.js | 2 +- shared/validate.js | 52 ++-- 15 files changed, 609 insertions(+), 72 deletions(-) create mode 100644 deploy/lib/createContainers.js create mode 100644 deploy/lib/deployContainers.js create mode 100644 deploy/lib/pushContainers.js create mode 100644 examples/container/my-container/Dockerfile create mode 100644 examples/container/my-container/requirements.txt create mode 100644 examples/container/my-container/server.py create mode 100644 examples/container/package.json create mode 100644 examples/container/serverless.yml diff --git a/deploy/lib/createContainers.js b/deploy/lib/createContainers.js new file mode 100644 index 00000000..a64fc260 --- /dev/null +++ b/deploy/lib/createContainers.js @@ -0,0 +1,75 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const constants = require('./constants'); + +module.exports = { + createContainers() { + return BbPromise.bind(this) + .then(this.getContainers) + .then(this.createOrUpdateContainers); + }, + + getContainers() { + const containersUrl = `namespaces/${this.namespace.id}/containers`; + return this.provider.apiManager.get(containersUrl) + .then(response => response.data.containers) + .catch((err) => { + throw new Error(err.response.data.message) + }) + }, + + createOrUpdateContainers(foundContainers) { + const containers = this.provider.serverless.service.custom.containers; + const containerNames = Object.keys(containers); + const promises = containerNames.map((containerName) => { + const container = Object.assign(containers[containerName], { name: containerName }); + const foundContainer = foundContainers.find(c => c.name === container.name); + return foundContainer ? this.updateContainer(container, foundContainer) : this.createSingleContainer(container); + }); + + return Promise.all(promises) + .then((updatedContainers) => { + this.containers = updatedContainers; + }); + }, + + createSingleContainer(container) { + const params = { + name: container.name, + environment_variables: container.env, + namespace_id: this.namespace.id, + memory_limit: container.memoryLimit, + min_scale: container.minScale, + max_scale: container.maxScale, + timeout: container.timeout + }; + + this.serverless.cli.log(`Creating container ${container.name}...`); + + return this.provider.apiManager.post('containers', params) + .then(response => Object.assign(response.data, { directory: container.directory })) + .catch((err) => { + throw new Error(err.response.data.message) + }) + }, + + updateContainer(container, foundContainer) { + const params = { + redeploy: false, + environment_variables: container.env, + memory_limit: container.memoryLimit, + min_scale: container.minScale, + max_scale: container.maxScale, + timeout: container.timeout + } + + const updateUrl = `containers/${foundContainer.id}`; + this.serverless.cli.log(`Updating container ${container.name}...`); + return this.provider.apiManager.patch(updateUrl, params) + .then(response => Object.assign(response.data, { directory: container.directory })) + .catch((err) => { + throw new Error(err.response.data.message) + }) + }, +}; diff --git a/deploy/lib/createNamespace.js b/deploy/lib/createNamespace.js index 38bcfc32..d2d7be63 100644 --- a/deploy/lib/createNamespace.js +++ b/deploy/lib/createNamespace.js @@ -59,6 +59,7 @@ module.exports = { setTimeout(() => resolve(this.waitNamespaceIsReady()), 1000); }); } + this.saveNamespaceToProvider(response.data); return true; }); }, diff --git a/deploy/lib/deployContainers.js b/deploy/lib/deployContainers.js new file mode 100644 index 00000000..a88e254d --- /dev/null +++ b/deploy/lib/deployContainers.js @@ -0,0 +1,54 @@ +'use strict'; + +const BbPromise = require('bluebird'); + +module.exports = { + deployContainers() { + this.serverless.cli.log('Deploying Containers...'); + return BbPromise.bind(this) + .then(this.deployEachContainer) + .then(() => this.serverless.cli.log('Waiting for container deployments, this may take multiple minutes...')) + .then(this.waitContainersAreDeployed) + .catch((err) => { + throw new Error(err.response.data.message) + }); + }, + + deployEachContainer() { + const promises = this.containers.map( + container => this.provider.apiManager.post(`containers/${container.id}/deploy`, {}) + .then(response => response.data) + .catch((err) => { + throw new Error(err.response.data.message) + }), + ); + + return Promise.all(promises); + }, + + waitContainersAreDeployed() { + return this.provider.apiManager.get(`namespaces/${this.namespace.id}/containers`) + .then((response) => { + const containers = response.data.containers || []; + let containersAreReady = true; + for (let i = 0; i < containers.length; i += 1) { + const container = response.data.containers[i]; + if (container.status === 'error') { + throw new Error(container.error_message) + } + if (container.status !== 'ready') { + containersAreReady = false; + break; + } + } + if (!containersAreReady) { + return new Promise((resolve) => { + setTimeout(() => resolve(this.waitContainersAreDeployed()), 5000); + }); + } + + // Print every containers endpoint + return containers.forEach(container => this.serverless.cli.log(`Container ${container.name} has been deployed to: ${container.endpoint}`)); + }); + }, +}; diff --git a/deploy/lib/pushContainers.js b/deploy/lib/pushContainers.js new file mode 100644 index 00000000..df033d08 --- /dev/null +++ b/deploy/lib/pushContainers.js @@ -0,0 +1,52 @@ +'use strict'; + +const BbPromise = require('bluebird'); + +const Docker = require('dockerode'); +const tar = require('tar-fs'); +const docker = new Docker(); + +const promisifyStream = (stream, verbose) => new BbPromise((resolve, reject) => { + stream.on('data', data => { + if (verbose) { + console.log(data.toString().replace('\n','')) + } + }) + stream.on('end', resolve) + stream.on('error', reject) +}); + +module.exports = { + pushContainers() { + return BbPromise.bind(this) + .then(this.buildAndPushContainer) + }, + + + buildAndPushContainer() { + const auth = { + username: 'any', + password: this.provider.scwToken + } + + const containerNames = Object.keys(this.containers); + const promises = containerNames.map((containerName) => { + + const container = this.containers[containerName] + const tarStream = tar.pack(`./${container.directory}`) + const imageName = `${this.namespace.registry_endpoint}/${container.name}:latest` + this.serverless.cli.log(`Building and pushing container ${container.name} to: ${imageName} ...`); + return new BbPromise((resolve) => { + docker.buildImage(tarStream, {t: imageName}) + .then(stream => promisifyStream(stream, this.provider.options.verbose)) + .then(() => docker.getImage(imageName)) + .then((image) => image.push(auth)) + .then(stream => promisifyStream(stream, this.provider.options.verbose)) + .then(() => resolve()) + }); + }); + + return Promise.all(promises) + }, + +}; diff --git a/deploy/scalewayDeploy.js b/deploy/scalewayDeploy.js index b882561d..a8f74676 100644 --- a/deploy/scalewayDeploy.js +++ b/deploy/scalewayDeploy.js @@ -3,8 +3,11 @@ const validate = require('../shared/validate'); const setUpDeployment = require('../shared/setUpDeployment'); const createNamespace = require('./lib/createNamespace'); const createFunctions = require('./lib/createFunctions'); +const createContainers = require('./lib/createContainers'); +const pushContainers = require('./lib/pushContainers'); const uploadCode = require('./lib/uploadCode'); const deployFunctions = require('./lib/deployFunctions'); +const deployContainers = require('./lib/deployContainers'); const namespaceUtils = require('../shared/namespace'); class ScalewayDeploy { @@ -19,11 +22,33 @@ class ScalewayDeploy { setUpDeployment, createNamespace, createFunctions, + createContainers, + pushContainers, uploadCode, deployFunctions, + deployContainers, namespaceUtils, ); + function chainContainers() { + if (this.provider.serverless.service.custom && + this.provider.serverless.service.custom.containers && + Object.keys(this.provider.serverless.service.custom.containers).length !== 0) { + return this.createContainers() + .then(this.pushContainers) + .then(this.deployContainers) + } + }; + + function chainFunctions() { + if (this.provider.serverless.service.functions && + Object.keys(this.provider.serverless.service.functions).length !== 0) { + return this.createFunctions() + .then(this.uploadCode) + .then(this.deployFunctions) + } + }; + this.hooks = { // Validate serverless.yml, set up default values, configure deployment... 'before:deploy:deploy': () => BbPromise.bind(this) @@ -35,12 +60,11 @@ class ScalewayDeploy { // - Create each functions in API if it does not exist // - Zip code - zip each function // - Get Presigned URL and Push code for each function to S3 - // - Deploy each function + // - Deploy each function / container 'deploy:deploy': () => BbPromise.bind(this) .then(this.createNamespace) - .then(this.createFunctions) - .then(this.uploadCode) - .then(this.deployFunctions), + .then(chainContainers) + .then(chainFunctions) }; } } diff --git a/docs/README.md b/docs/README.md index dd27e649..f9373a5c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -90,6 +90,7 @@ provider: # node8, node10 for JavaScript # python (2.7), python3 (3.7) for Python # golang + # You don't need to specify a runtime if you deploy only containers runtime: # See documentation below for environment env: @@ -110,10 +111,47 @@ functions: env: MY_VARIABLE: "my-value" ``` +### Managing containers + +**Requirements:** You need to have Docker installed to be able to build and push your image to your Scaleway registry. + +You must define your containers inside the `custom.containers` field in your serverless.yml manifest. +Each container must specify the relative path of its application directory (containing the Dockerfile, and all files related to the application to deploy): + +```yml +custom: + containers: + myContainer: + directory: my-container-directory + # Environment only available in this function + env: + MY_VARIABLE: "my-value" +``` + +Here is an example of the files you should have, the `directory` containing your Dockerfile ans scripts is `my-container-directory`. + +``` +. +├── my-container-directory +│   ├── Dockerfile +│   ├── requirements.txt +│ ├── server.py +│   └── (...) +├── node_modules +│   ├── serverless-scaleway-functions +│ └── (...) +├── package-lock.json +├── package.json +└── serverless.yml +``` + +Scaleway's platform will automatically inject a PORT environment variable on which your server should be listening for incoming traffic. By default, this PORT is 8080. + +You may use the [container example](../examples/container) to getting started. ### Runtime and Functions Handler -You must specify your functions runtime inside `provider.runtime` key inside your serverless.yml file. +You must specify your functions runtime inside `provider.runtime` key inside your serverless.yml file. It is not necessary if you wish to deploy containers only. #### Runtimes diff --git a/examples/container/my-container/Dockerfile b/examples/container/my-container/Dockerfile new file mode 100644 index 00000000..9a726018 --- /dev/null +++ b/examples/container/my-container/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3-alpine +WORKDIR /usr/src/app + +ENV PORT 8080 +EXPOSE 8080 + +COPY requirements.txt . +RUN pip install -qr requirements.txt +COPY server.py . + +CMD ["python3", "./server.py"] \ No newline at end of file diff --git a/examples/container/my-container/requirements.txt b/examples/container/my-container/requirements.txt new file mode 100644 index 00000000..f2e1e506 --- /dev/null +++ b/examples/container/my-container/requirements.txt @@ -0,0 +1 @@ +Flask==1.0.2 diff --git a/examples/container/my-container/server.py b/examples/container/my-container/server.py new file mode 100644 index 00000000..0403cb9f --- /dev/null +++ b/examples/container/my-container/server.py @@ -0,0 +1,17 @@ +from flask import Flask + +DEFAULT_PORT = "8080" +MESSAGE = "Hello, World from Scaleway Container !\n" + +app = Flask(__name__) + +@app.route("/") +def root(): + result = MESSAGE.encode("utf-8") + return result + +if __name__ == "__main__": + # Scaleway's system will inject a PORT environment variable on which your application should start the server. + port_env = os.getenv("PORT", DEFAULT_PORT) + port = int(port_env) + app.run(debug=True, host="0.0.0.0", port=port) \ No newline at end of file diff --git a/examples/container/package.json b/examples/container/package.json new file mode 100644 index 00000000..33ed4202 --- /dev/null +++ b/examples/container/package.json @@ -0,0 +1,17 @@ +{ + "name": "container-scaleway-starter", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": {}, + "devDependencies": { + "serverless-scaleway-functions": "^0.1.6" + }, + "description": "" + } + \ No newline at end of file diff --git a/examples/container/serverless.yml b/examples/container/serverless.yml new file mode 100644 index 00000000..079b4ec2 --- /dev/null +++ b/examples/container/serverless.yml @@ -0,0 +1,32 @@ +service: + name: scaleway-container + +provider: + name: scaleway + # Global Environment variables - used in every functions + env: + test: test + # the path to the credentials file needs to be absolute + scwToken: + scwOrganization: + +plugins: + - serverless-scaleway-functions + +package: + exclude: + - node_modules/** + - .gitignore + - .git/** + +custom: + containers: + first: + directory: my-container + # minScale: 1 + # memoryLimit: 256 + # maxScale: 2 + # timeout: 20000 + # Local environment variables - used only in given function + env: + local: local diff --git a/package-lock.json b/package-lock.json index 61abf2b2..33401216 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless-scaleway-functions", - "version": "0.1.0", + "version": "0.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -486,6 +486,15 @@ "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", "dev": true }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", @@ -838,6 +847,15 @@ "tweetnacl": "^0.14.3" } }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "bluebird": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", @@ -914,11 +932,29 @@ "node-int64": "^0.4.0" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "cache-base": { "version": "1.0.1", @@ -981,6 +1017,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -1101,6 +1142,23 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "confusing-browser-globals": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz", + "integrity": "sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==", + "dev": true + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -1125,8 +1183,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cross-spawn": { "version": "6.0.5", @@ -1283,6 +1340,85 @@ "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", "dev": true }, + "docker-modem": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.9.tgz", + "integrity": "sha512-lVjqCSCIAUDZPAZIeyM125HXfNvOmYYInciphNrLrylUtKyW66meAjSPXWchKVzoIYZx69TPnAepVSSkeawoIw==", + "requires": { + "JSONStream": "1.3.2", + "debug": "^3.2.6", + "readable-stream": "~1.0.26-4", + "split-ca": "^1.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "dockerode": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.8.tgz", + "integrity": "sha512-+7iOUYBeDTScmOmQqpUYQaE7F4vvIt6+gIZNHWhqAQEI887tiPFB9OvXI/HzQYqfUNvukMK+9myLW63oTJPZpw==", + "requires": { + "concat-stream": "~1.6.2", + "docker-modem": "^1.0.8", + "tar-fs": "~1.16.3" + }, + "dependencies": { + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "tar-fs": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "requires": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + } + } + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1321,7 +1457,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -1449,14 +1584,14 @@ } }, "eslint-config-airbnb-base": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", - "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz", + "integrity": "sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==", "dev": true, "requires": { - "eslint-restricted-globals": "^0.1.1", + "confusing-browser-globals": "^1.0.5", "object.assign": "^4.1.0", - "object.entries": "^1.0.4" + "object.entries": "^1.1.0" } }, "eslint-import-resolver-node": { @@ -1502,9 +1637,9 @@ } }, "eslint-plugin-import": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz", - "integrity": "sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.0.tgz", + "integrity": "sha512-PZpAEC4gj/6DEMMoU2Df01C5c50r7zdGIN52Yfi7CvvWaYssG7Jt5R9nFG5gmqodxNOz9vQS87xk6Izdtpdrig==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -1541,12 +1676,6 @@ } } }, - "eslint-restricted-globals": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", - "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", - "dev": true - }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -1944,6 +2073,11 @@ "map-cache": "^0.2.2" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2774,8 +2908,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "inquirer": { "version": "6.3.1", @@ -3037,8 +3170,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -3681,6 +3813,11 @@ } } }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3912,8 +4049,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mixin-deep": { "version": "1.3.1", @@ -3940,7 +4076,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" } @@ -4172,7 +4307,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -4432,8 +4566,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.3", @@ -4461,7 +4594,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -4510,7 +4642,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4732,8 +4863,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -5061,6 +5191,11 @@ "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", "dev": true }, + "split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -5150,7 +5285,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -5237,6 +5371,63 @@ } } }, + "tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "requires": { + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + }, + "dependencies": { + "bl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", + "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "requires": { + "readable-stream": "^3.0.1" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-stream": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz", + "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==", + "requires": { + "bl": "^3.0.0", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + } + }, "test-exclude": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", @@ -5367,8 +5558,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tmp": { "version": "0.0.33", @@ -5385,6 +5575,11 @@ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5494,6 +5689,11 @@ "prelude-ls": "~1.1.2" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "uglify-js": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", @@ -5604,8 +5804,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -5765,8 +5964,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -5803,6 +6001,11 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 5e206a25..daa065cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-scaleway-functions", - "version": "0.1.5", + "version": "0.1.6", "description": "Provider plugin for the Serverless Framework v1.x which adds support for Scaleway Functions.", "main": "index.js", "author": "scaleway.com", @@ -27,12 +27,14 @@ ], "dependencies": { "axios": "^0.19.0", - "bluebird": "^3.5.5" + "bluebird": "^3.5.5", + "dockerode": "^2.5.8", + "tar-fs": "^2.0.0" }, "devDependencies": { "eslint": "^5.16.0", - "eslint-config-airbnb-base": "^13.1.0", - "eslint-plugin-import": "^2.17.3", + "eslint-config-airbnb-base": "^13.2.0", + "eslint-plugin-import": "^2.18.0", "jest": "^24.8.0" } } diff --git a/provider/scalewayProvider.js b/provider/scalewayProvider.js index f42daf9a..e98e5c61 100644 --- a/provider/scalewayProvider.js +++ b/provider/scalewayProvider.js @@ -43,7 +43,7 @@ class ScalewayProvider { return new BbPromise((resolve) => { this.setCredentials(options); - + this.apiUrl = 'https://api.scaleway.com/functions/v1alpha2/regions/fr-par'; this.apiManager = axios.create({ baseURL: this.apiUrl, diff --git a/shared/validate.js b/shared/validate.js index 3cc08850..c5b88484 100644 --- a/shared/validate.js +++ b/shared/validate.js @@ -10,7 +10,7 @@ module.exports = { .then(this.validateServicePath) .then(this.validateCredentials) .then(this.validateNamespace) - .then(this.validateFunctions) + .then(this.validateApplications) .then(this.checkErrors); }, @@ -50,32 +50,42 @@ module.exports = { return BbPromise.resolve(currentErrors.concat(namespaceErrors)); }, - validateFunctions(errors) { + validateApplications(errors) { + let functionNames = []; + let containerNames = []; + const currentErrors = Array.isArray(errors) ? errors : []; - const functions = this.serverless.service.functions; - const functionNames = Object.keys(functions); let functionErrors = []; + let containers = []; - if (!functionNames.length) { - functionErrors.push('You must define at least one function to deploy under the functions: key.'); + const functions = this.serverless.service.functions; + if (functions && Object.keys(functions).length !== 0) { + functionNames = Object.keys(functions); + + functionNames.forEach((functionName) => { + const func = functions[functionName]; + // Check if function handler exists + try { + if (!fs.existsSync(path.resolve('./', func.handler))) { + throw new Error('File does not exists'); + } + } catch (error) { + const message = `Handler defined for function ${functionName} does not exist.`; + functionErrors.push(message); + } + }); } - functionNames.forEach((functionName) => { - const func = functions[functionName]; - - // Check function env vars - functionErrors = functionErrors.concat(this.validateEnv(func.env)); + if (this.serverless.service.custom) { + containers = this.serverless.service.custom.containers; + } + if (containers && Object.keys(containers).length !== 0) { + containerNames = Object.keys(containers); + } - // Check if function handler exists - try { - if (!fs.existsSync(path.resolve('./', func.handler))) { - throw new Error('File does not exists'); - } - } catch (error) { - const message = `Handler defined for function ${functionName} does not exist.`; - functionErrors.push(message); - } - }); + if (!functionNames.length && !containerNames.length) { + functionErrors.push('You must define at least one function or container to deploy under the functions or custom key.'); + } return BbPromise.resolve(currentErrors.concat(functionErrors)); }, From 47f96facae3d2079587af35783fd7318d02df103 Mon Sep 17 00:00:00 2001 From: amottier Date: Fri, 12 Jul 2019 13:31:12 +0200 Subject: [PATCH 2/2] fix container example --- examples/container/my-container/server.py | 3 +- examples/container/package.json | 4 +- .../scaleway/scaleway-functions-go/LICENSE | 21 ++++ .../scaleway-functions-go/events/http.go | 39 ++++++ .../scaleway-functions-go/lambda/entry.go | 112 ++++++++++++++++++ package.json | 2 +- 6 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 examples/golang/vendor/github.com/scaleway/scaleway-functions-go/LICENSE create mode 100644 examples/golang/vendor/github.com/scaleway/scaleway-functions-go/events/http.go create mode 100644 examples/golang/vendor/github.com/scaleway/scaleway-functions-go/lambda/entry.go diff --git a/examples/container/my-container/server.py b/examples/container/my-container/server.py index 0403cb9f..2009e569 100644 --- a/examples/container/my-container/server.py +++ b/examples/container/my-container/server.py @@ -1,4 +1,5 @@ from flask import Flask +import os DEFAULT_PORT = "8080" MESSAGE = "Hello, World from Scaleway Container !\n" @@ -14,4 +15,4 @@ def root(): # Scaleway's system will inject a PORT environment variable on which your application should start the server. port_env = os.getenv("PORT", DEFAULT_PORT) port = int(port_env) - app.run(debug=True, host="0.0.0.0", port=port) \ No newline at end of file + app.run(debug=True, host="0.0.0.0", port=port) diff --git a/examples/container/package.json b/examples/container/package.json index 33ed4202..8399e1be 100644 --- a/examples/container/package.json +++ b/examples/container/package.json @@ -10,8 +10,8 @@ "license": "ISC", "dependencies": {}, "devDependencies": { - "serverless-scaleway-functions": "^0.1.6" + "serverless-scaleway-functions": "^0.1.7" }, "description": "" } - \ No newline at end of file + diff --git a/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/LICENSE b/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/LICENSE new file mode 100644 index 00000000..f3a907ee --- /dev/null +++ b/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Scaleway + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/events/http.go b/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/events/http.go new file mode 100644 index 00000000..0296f2eb --- /dev/null +++ b/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/events/http.go @@ -0,0 +1,39 @@ +package events + +// APIGatewayProxyRequest contains data coming from the API Gateway proxy +type APIGatewayProxyRequest struct { + Resource string `json:"resource"` // The resource path defined in API Gateway + Path string `json:"path"` // The url path for the caller + HTTPMethod string `json:"httpMethod"` + Headers map[string]string `json:"headers"` + MultiValueHeaders map[string][]string `json:"multiValueHeaders"` + QueryStringParameters map[string]string `json:"queryStringParameters"` + MultiValueQueryStringParameters map[string][]string `json:"multiValueQueryStringParameters"` + PathParameters map[string]string `json:"pathParameters"` + StageVariables map[string]string `json:"stageVariables"` + RequestContext APIGatewayProxyRequestContext `json:"requestContext"` + Body string `json:"body"` + IsBase64Encoded bool `json:"isBase64Encoded,omitempty"` +} + +// APIGatewayProxyResponse configures the response to be returned by API Gateway for the request +type APIGatewayProxyResponse struct { + StatusCode int `json:"statusCode"` + Headers map[string]string `json:"headers"` + MultiValueHeaders map[string][]string `json:"multiValueHeaders"` + Body string `json:"body"` + IsBase64Encoded bool `json:"isBase64Encoded,omitempty"` +} + +// APIGatewayProxyRequestContext contains the information to identify the AWS account and resources invoking the +// Lambda function. It also includes Cognito identity information for the caller. +type APIGatewayProxyRequestContext struct { + AccountID string `json:"accountId"` + ResourceID string `json:"resourceId"` + Stage string `json:"stage"` + RequestID string `json:"requestId"` + ResourcePath string `json:"resourcePath"` + Authorizer map[string]interface{} `json:"authorizer"` + HTTPMethod string `json:"httpMethod"` + APIID string `json:"apiId"` // The API Gateway rest API Id +} diff --git a/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/lambda/entry.go b/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/lambda/entry.go new file mode 100644 index 00000000..6a460da8 --- /dev/null +++ b/examples/golang/vendor/github.com/scaleway/scaleway-functions-go/lambda/entry.go @@ -0,0 +1,112 @@ +package lambda + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "time" + + "github.com/scaleway/scaleway-functions-go/events" +) + +const defaultPort = 8080 + +// FunctionHandler - Handler for Event +type FunctionHandler func(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) + +// Start takes the function Handler, at the moment only supporting HTTP Triggers (Api Gateway Proxy events) +// It takes care of wrapping the handler with an HTTP server, which receives requests when functions are triggered +// And execute the handler after formatting the HTTP Request to an API Gateway Proxy Event +func Start(handler FunctionHandler) { + portEnv := os.Getenv("PORT") + port, err := strconv.Atoi(portEnv) + if err != nil { + port = defaultPort + } + + s := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + MaxHeaderBytes: 1 << 20, // Max header of 1MB + } + + http.HandleFunc("/", makeRequestHandler(handler)) + log.Fatal(s.ListenAndServe()) +} + +func makeRequestHandler(handler FunctionHandler) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var input string + + if r.Body != nil { + defer r.Body.Close() + + bodyBytes, bodyErr := ioutil.ReadAll(r.Body) + + if bodyErr != nil { + log.Printf("Error reading body from request.") + } + + input = string(bodyBytes) + } + + // HTTP Headers - only first value + // TODO: use HeaderMultipleValue + headers := map[string]string{} + for key, value := range r.Header { + headers[key] = value[len(value)-1] + } + + queryParameters := map[string]string{} + for key, value := range r.URL.Query() { + queryParameters[key] = value[len(value)-1] + } + + isBase64Encoded := true + _, err := base64.StdEncoding.DecodeString(input) + if err != nil { + isBase64Encoded = false + } + + event := events.APIGatewayProxyRequest{ + Path: r.URL.Path, + HTTPMethod: r.Method, + Headers: headers, + QueryStringParameters: queryParameters, + StageVariables: map[string]string{}, + Body: input, + IsBase64Encoded: isBase64Encoded, + RequestContext: events.APIGatewayProxyRequestContext{ + Stage: "", + HTTPMethod: r.Method, + }, + } + + result, resultErr := handler(event) + + if result.Headers != nil { + for key, value := range result.Headers { + w.Header().Set(key, value) + } + } + + if resultErr != nil { + log.Print(resultErr) + w.WriteHeader(http.StatusInternalServerError) + } else { + if result.StatusCode == 0 { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(result.StatusCode) + } + } + + responseBody := []byte(result.Body) + w.Write(responseBody) + } +} diff --git a/package.json b/package.json index daa065cc..92ea7f06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-scaleway-functions", - "version": "0.1.6", + "version": "0.1.7", "description": "Provider plugin for the Serverless Framework v1.x which adds support for Scaleway Functions.", "main": "index.js", "author": "scaleway.com",