diff --git a/README.md b/README.md index b745d50d..5149bc5c 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,13 @@ custom: custom_domains: - my-container.some.domain.com + # Health check configuration + healthCheck: + type: http # Or tcp if you only want to check that the port is open + httpPath: /health + interval: 10s + failureThreshold: 3 + # List of events to trigger the container events: - schedule: diff --git a/deploy/lib/createContainers.js b/deploy/lib/createContainers.js index 7d3b865f..0c17b626 100644 --- a/deploy/lib/createContainers.js +++ b/deploy/lib/createContainers.js @@ -5,6 +5,26 @@ const singleSource = require("../../shared/singleSource"); const secrets = require("../../shared/secrets"); const domainUtils = require("../../shared/domains"); +function adaptHealthCheckToAPI(healthCheck) { + if (!healthCheck) { + return null; + } + + // We need to find the type of the health check (tcp, http, ...) + // If httpPath is provided, we default to http, otherwise we default to tcp + let type = healthCheck.httpPath ? "http" : "tcp"; + if (healthCheck.type) { + type = healthCheck.type; + } + + return { + failure_threshold: healthCheck.failureThreshold, + interval: healthCheck.interval, + ...(type === "http" && { http: { path: healthCheck.httpPath || "/" } }), + ...(type === "tcp" && { tcp: {} }), + }; +} + module.exports = { createContainers() { return BbPromise.bind(this) @@ -104,6 +124,7 @@ module.exports = { port: container.port, http_option: container.httpOption, sandbox: container.sandbox, + health_check: adaptHealthCheckToAPI(container.healthCheck), }; // checking if there is custom_domains set on container creation. @@ -144,6 +165,7 @@ module.exports = { port: container.port, http_option: container.httpOption, sandbox: container.sandbox, + health_check: adaptHealthCheckToAPI(container.healthCheck), }; this.serverless.cli.log(`Updating container ${container.name}...`); diff --git a/examples/container/my-container/requirements.txt b/examples/container/my-container/requirements.txt index e3e9a71d..f08dea74 100644 --- a/examples/container/my-container/requirements.txt +++ b/examples/container/my-container/requirements.txt @@ -1 +1 @@ -Flask +flask~=3.1.0 diff --git a/examples/container/my-container/server.py b/examples/container/my-container/server.py index e5fea352..b9f8bf8c 100644 --- a/examples/container/my-container/server.py +++ b/examples/container/my-container/server.py @@ -1,6 +1,5 @@ -from flask import Flask +from flask import Flask, jsonify import os -import json DEFAULT_PORT = "8080" MESSAGE = "Hello, World from Scaleway Container !" @@ -9,12 +8,18 @@ @app.route("/") def root(): - return json.dumps({ + return jsonify({ "message": MESSAGE }) +@app.route("/health") +def health(): + # You could add more complex logic here, for example checking the health of a database... + return jsonify({ + "status": "UP" + }) + 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) + port = os.getenv("PORT", DEFAULT_PORT) + app.run(host="0.0.0.0", port=int(port)) diff --git a/examples/container/serverless.yml b/examples/container/serverless.yml index 4964b24e..2f7a89e7 100644 --- a/examples/container/serverless.yml +++ b/examples/container/serverless.yml @@ -32,3 +32,7 @@ custom: # Local environment variables - used only in given function env: local: local + healthCheck: + httpPath: /health + interval: 10s + failureThreshold: 3 diff --git a/shared/api/index.js b/shared/api/index.js index cc131102..375ebd0a 100644 --- a/shared/api/index.js +++ b/shared/api/index.js @@ -1,6 +1,4 @@ -const https = require("https"); -const axios = require("axios"); - +const { getApiManager } = require("./utils"); const accountApi = require("./account"); const domainApi = require("./domain"); const namespacesApi = require("./namespaces"); @@ -14,21 +12,6 @@ const runtimesApi = require("./runtimes"); // Registry const RegistryApi = require("./registry"); -const version = "0.4.13"; - -function getApiManager(apiUrl, token) { - return axios.create({ - baseURL: apiUrl, - headers: { - "User-Agent": `serverless-scaleway-functions/${version}`, - "X-Auth-Token": token, - }, - httpsAgent: new https.Agent({ - rejectUnauthorized: false, - }), - }); -} - class AccountApi { constructor(apiUrl, token) { this.apiManager = getApiManager(apiUrl, token); diff --git a/shared/api/registry.js b/shared/api/registry.js index b0d7b243..b0a54f45 100644 --- a/shared/api/registry.js +++ b/shared/api/registry.js @@ -1,6 +1,6 @@ "use strict"; -const { getApiManager } = require("./index"); +const { getApiManager } = require("./utils"); const { manageError } = require("./utils"); class RegistryApi { diff --git a/shared/api/utils.js b/shared/api/utils.js index 70116ecd..2413649e 100644 --- a/shared/api/utils.js +++ b/shared/api/utils.js @@ -1,3 +1,23 @@ +const axios = require("axios"); +const https = require("https"); + +const version = "0.4.13"; + +const invalidArgumentsType = "invalid_arguments"; + +function getApiManager(apiUrl, token) { + return axios.create({ + baseURL: apiUrl, + headers: { + "User-Agent": `serverless-scaleway-functions/${version}`, + "X-Auth-Token": token, + }, + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }); +} + /** * Custom Error class, to print an error message, and pass the Response if applicable */ @@ -20,13 +40,25 @@ function manageError(err) { throw new Error(err); } if (err.response.data.message) { - throw new CustomError(err.response.data.message, err.response); + let message = err.response.data.message; + + // In case the error is an InvalidArgumentsError, provide some extra information + if (err.response.data.type === invalidArgumentsType) { + for (const details of err.response.data.details) { + const argumentName = details.argument_name; + const helpMessage = details.help_message; + message += `\n${argumentName}: ${helpMessage}`; + } + } + + throw new CustomError(message, err.response); } else if (err.response.data.error_message) { throw new CustomError(err.response.data.error_message, err.response); } } module.exports = { + getApiManager, manageError, CustomError, };