diff --git a/package-lock.json b/package-lock.json index be72ce0b..7cdfba22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22327,6 +22327,7 @@ "dependencies": { "@whook/authorization": "^11.0.1", "@whook/cors": "^11.0.1", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "^11.0.1", "@whook/http-server": "^11.0.1", "@whook/http-transaction": "^11.0.1", @@ -22334,6 +22335,7 @@ "@whook/whook": "^11.0.1", "common-services": "^12.0.0", "http-auth-utils": "^3.0.4", + "js-yaml": "^3.13.1", "jwt-service": "^9.0.2", "knifecycle": "^14.1.0", "openapi-schema-validator": "^12.0.2", @@ -27481,6 +27483,7 @@ "@typescript-eslint/parser": "^5.36.0", "@whook/authorization": "^11.0.1", "@whook/cors": "^11.0.1", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "^11.0.1", "@whook/http-server": "^11.0.1", "@whook/http-transaction": "^11.0.1", @@ -27497,6 +27500,7 @@ "eslint-plugin-prettier": "^4.2.1", "http-auth-utils": "^3.0.4", "jest": "^29.0.1", + "js-yaml": "^3.13.1", "jsarch": "^6.0.0", "jwt-service": "^9.0.2", "knifecycle": "^14.1.0", diff --git a/packages/whook-create/src/services/__snapshots__/createWhook.test.ts.snap b/packages/whook-create/src/services/__snapshots__/createWhook.test.ts.snap index 2b6bd1ce..c42b88f6 100644 --- a/packages/whook-create/src/services/__snapshots__/createWhook.test.ts.snap +++ b/packages/whook-create/src/services/__snapshots__/createWhook.test.ts.snap @@ -115,6 +115,7 @@ Wayne Campbell "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -127,6 +128,7 @@ Wayne Campbell "openapi-schema-validator": "^12.0.2", "openapi-types": "^12.1.0", "pkg-dir": "^7.0.0", + "js-yaml": "^3.13.1", "strict-qs": "^7.0.1", "type-fest": "^3.4.0", "yerror": "^6.2.1", @@ -416,6 +418,7 @@ Wayne Campbell "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -428,6 +431,7 @@ Wayne Campbell "openapi-schema-validator": "^12.0.2", "openapi-types": "^12.1.0", "pkg-dir": "^7.0.0", + "js-yaml": "^3.13.1", "strict-qs": "^7.0.1", "type-fest": "^3.4.0", "yerror": "^6.2.1", @@ -749,6 +753,7 @@ Wayne Campbell "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -761,6 +766,7 @@ Wayne Campbell "openapi-schema-validator": "^12.0.2", "openapi-types": "^12.1.0", "pkg-dir": "^7.0.0", + "js-yaml": "^3.13.1", "strict-qs": "^7.0.1", "type-fest": "^3.4.0", "yerror": "^6.2.1", @@ -1070,6 +1076,7 @@ Wayne Campbell "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -1082,6 +1089,7 @@ Wayne Campbell "openapi-schema-validator": "^12.0.2", "openapi-types": "^12.1.0", "pkg-dir": "^7.0.0", + "js-yaml": "^3.13.1", "strict-qs": "^7.0.1", "type-fest": "^3.4.0", "yerror": "^6.2.1", diff --git a/packages/whook-create/src/services/createWhook.test.ts b/packages/whook-create/src/services/createWhook.test.ts index 8d8340a1..6091fcf0 100644 --- a/packages/whook-create/src/services/createWhook.test.ts +++ b/packages/whook-create/src/services/createWhook.test.ts @@ -143,6 +143,7 @@ Mr Bean "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -150,6 +151,7 @@ Mr Bean "@whook/whook": "", "common-services": "^12.0.0", "http-auth-utils": "^3.0.4", + "js-yaml": "^3.13.1", "jwt-service": "^9.0.2", "knifecycle": "^14.1.0", "openapi-schema-validator": "^12.0.2", @@ -363,6 +365,7 @@ Mr Bean "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -370,6 +373,7 @@ Mr Bean "@whook/whook": "", "common-services": "^12.0.0", "http-auth-utils": "^3.0.4", + "js-yaml": "^3.13.1", "jwt-service": "^9.0.2", "knifecycle": "^14.1.0", "openapi-schema-validator": "^12.0.2", @@ -568,6 +572,7 @@ Mr Bean "dependencies": { "@whook/authorization": "", "@whook/cors": "", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "", "@whook/http-server": "", "@whook/http-transaction": "", @@ -575,6 +580,7 @@ Mr Bean "@whook/whook": "", "common-services": "^12.0.0", "http-auth-utils": "^3.0.4", + "js-yaml": "^3.13.1", "jwt-service": "^9.0.2", "knifecycle": "^14.1.0", "openapi-schema-validator": "^12.0.2", diff --git a/packages/whook-example/README.md b/packages/whook-example/README.md index d677ac8c..11fd5a1e 100644 --- a/packages/whook-example/README.md +++ b/packages/whook-example/README.md @@ -92,6 +92,57 @@ Debug `knifecycle` internals (dependency injection issues): DEBUG=knifecycle npm run dev ``` +## Deploying with Google Cloud Functions + +Create a project and save its credentials to `.credentials.json`. + +Then install Terraform: +```sh +wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip +mkdir .bin +unzip -d .bin terraform_0.12.24_linux_amd64.zip +rm terraform_0.12.24_linux_amd64.zip +``` + +Then initialize the Terraform configuration: +```sh +.bin/terraform init ./terraform; +``` + +Create a new workspace: +```sh +.bin/terraform workspace new staging +``` + +Build the functions: +```sh +NODE_ENV=staging npm run build +``` + +Build the Whook commands Terraform depends on: +```sh +npm run compile +``` + +Plan the deployment: +```sh +.bin/terraform plan -var="project_id=my-project-1664" \ + -out=terraform.plan terraform +``` + +Apply changes: +```sh +# parallelism may be necessary to avoid hitting +# timeouts with a slow connection +.bin/terraform apply -parallelism=1 terraform.plan +``` + +Finally retrieve the API URL and enjoy! +```sh +.bin/terraform -var="project_id=my-project-1664" \ + output api_url +``` + [//]: # (::contents:end) # Authors diff --git a/packages/whook-example/package.json b/packages/whook-example/package.json index 3efe61d6..643e5b3e 100644 --- a/packages/whook-example/package.json +++ b/packages/whook-example/package.json @@ -79,6 +79,7 @@ "dependencies": { "@whook/authorization": "^11.0.1", "@whook/cors": "^11.0.1", + "@whook/gcp-functions": "^11.0.1", "@whook/http-router": "^11.0.1", "@whook/http-server": "^11.0.1", "@whook/http-transaction": "^11.0.1", @@ -91,6 +92,7 @@ "openapi-schema-validator": "^12.0.2", "openapi-types": "^12.1.0", "pkg-dir": "^7.0.0", + "js-yaml": "^3.13.1", "strict-qs": "^7.0.1", "type-fest": "^3.4.0", "yerror": "^6.2.1", diff --git a/packages/whook-example/src/__snapshots__/cli.test.ts.snap b/packages/whook-example/src/__snapshots__/cli.test.ts.snap index c2e795b2..cd184515 100644 --- a/packages/whook-example/src/__snapshots__/cli.test.ts.snap +++ b/packages/whook-example/src/__snapshots__/cli.test.ts.snap @@ -28,8 +28,16 @@ exports[`commands should work with ls 1`] = ` ", "stdout": " -# Provided by "@whook/example": 1 commands +# Provided by "@whook/example": 2 commands - printEnv: A command printing every env values +- terraformValues: A command printing functions informations for Terraform + + +# Provided by "@whook/gcp-functions": 1 commands +- testHTTPFunction: A command for testing GCP HTTP function + + +# Provided by "@whook/cors": none # Provided by "@whook/whook": 8 commands @@ -41,9 +49,6 @@ exports[`commands should work with ls 1`] = ` - handler: Runs the given server handler for testing purpose - inspect: A simple program that returns the result of the injected service - ls: Print available commands - - -# Provided by "@whook/cors": none ", } `; diff --git a/packages/whook-example/src/__snapshots__/index.test.ts.snap b/packages/whook-example/src/__snapshots__/index.test.ts.snap index 17ad6d5d..242fe329 100644 --- a/packages/whook-example/src/__snapshots__/index.test.ts.snap +++ b/packages/whook-example/src/__snapshots__/index.test.ts.snap @@ -163,6 +163,9 @@ exports[`runServer should work 1`] = ` [ "➰ - Plugin "@whook/cors" resolved to "/home/whoiam/projects/whook/packages/whook-cors/dist".", ], + [ + "➰ - Plugin "@whook/gcp-functions" resolved to "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist".", + ], [ "➰ - Plugin "@whook/whook" resolved to "/home/whoiam/projects/whook/dist".", ], @@ -319,9 +322,15 @@ exports[`runServer should work 1`] = ` [ "🚫 - Could not load ".env.test" file.", ], + [ + "🚫 - Service "BASE_URL" not found in "/home/whoiam/projects/whook/packages/whook-cors/dist/services/BASE_URL".", + ], [ "🚫 - Service "BASE_URL" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/BASE_URL".", ], + [ + "🚫 - Service "BASE_URL" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/BASE_URL".", + ], [ "🚫 - Service "BUFFER_LIMIT" not found in "/home/whoiam/projects/whook/dist/services/BUFFER_LIMIT".", ], @@ -331,6 +340,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "BUFFER_LIMIT" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/BUFFER_LIMIT".", ], + [ + "🚫 - Service "BUFFER_LIMIT" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/BUFFER_LIMIT".", + ], [ "🚫 - Service "DECODERS" not found in "/home/whoiam/projects/whook/dist/services/DECODERS".", ], @@ -340,6 +352,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "DECODERS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/DECODERS".", ], + [ + "🚫 - Service "DECODERS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/DECODERS".", + ], [ "🚫 - Service "DEFAULT_ERROR_CODE" not found in "/home/whoiam/projects/whook/dist/services/DEFAULT_ERROR_CODE".", ], @@ -349,6 +364,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "DEFAULT_ERROR_CODE" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/DEFAULT_ERROR_CODE".", ], + [ + "🚫 - Service "DEFAULT_ERROR_CODE" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/DEFAULT_ERROR_CODE".", + ], [ "🚫 - Service "DEFAULT_MECHANISM" not found in "/home/whoiam/projects/whook/dist/services/DEFAULT_MECHANISM".", ], @@ -358,6 +376,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "DEFAULT_MECHANISM" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/DEFAULT_MECHANISM".", ], + [ + "🚫 - Service "DEFAULT_MECHANISM" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/DEFAULT_MECHANISM".", + ], [ "🚫 - Service "DEV_ACCESS_TOKEN" not found in "/home/whoiam/projects/whook/dist/services/DEV_ACCESS_TOKEN".", ], @@ -367,6 +388,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "DEV_ACCESS_TOKEN" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/DEV_ACCESS_TOKEN".", ], + [ + "🚫 - Service "DEV_ACCESS_TOKEN" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/DEV_ACCESS_TOKEN".", + ], [ "🚫 - Service "ENCODERS" not found in "/home/whoiam/projects/whook/dist/services/ENCODERS".", ], @@ -376,6 +400,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "ENCODERS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/ENCODERS".", ], + [ + "🚫 - Service "ENCODERS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/ENCODERS".", + ], [ "🚫 - Service "IGNORED_FILES_PREFIXES" not found in "/home/whoiam/projects/whook/dist/services/IGNORED_FILES_PREFIXES".", ], @@ -385,6 +412,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "IGNORED_FILES_PREFIXES" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/IGNORED_FILES_PREFIXES".", ], + [ + "🚫 - Service "IGNORED_FILES_PREFIXES" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/IGNORED_FILES_PREFIXES".", + ], [ "🚫 - Service "IGNORED_FILES_SUFFIXES" not found in "/home/whoiam/projects/whook/dist/services/IGNORED_FILES_SUFFIXES".", ], @@ -394,6 +424,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "IGNORED_FILES_SUFFIXES" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/IGNORED_FILES_SUFFIXES".", ], + [ + "🚫 - Service "IGNORED_FILES_SUFFIXES" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/IGNORED_FILES_SUFFIXES".", + ], [ "🚫 - Service "JWT_SECRET_ENV_NAME" not found in "/home/whoiam/projects/whook/dist/services/JWT_SECRET_ENV_NAME".", ], @@ -403,6 +436,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "JWT_SECRET_ENV_NAME" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/JWT_SECRET_ENV_NAME".", ], + [ + "🚫 - Service "JWT_SECRET_ENV_NAME" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/JWT_SECRET_ENV_NAME".", + ], [ "🚫 - Service "KEEP_ALIVE_TIMEOUT" not found in "/home/whoiam/projects/whook/dist/services/KEEP_ALIVE_TIMEOUT".", ], @@ -412,6 +448,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "KEEP_ALIVE_TIMEOUT" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/KEEP_ALIVE_TIMEOUT".", ], + [ + "🚫 - Service "KEEP_ALIVE_TIMEOUT" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/KEEP_ALIVE_TIMEOUT".", + ], [ "🚫 - Service "MAX_CLEAR_CHARS" not found in "/home/whoiam/projects/whook/dist/services/MAX_CLEAR_CHARS".", ], @@ -421,6 +460,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "MAX_CLEAR_CHARS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/MAX_CLEAR_CHARS".", ], + [ + "🚫 - Service "MAX_CLEAR_CHARS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/MAX_CLEAR_CHARS".", + ], [ "🚫 - Service "MAX_CLEAR_RATIO" not found in "/home/whoiam/projects/whook/dist/services/MAX_CLEAR_RATIO".", ], @@ -430,6 +472,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "MAX_CLEAR_RATIO" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/MAX_CLEAR_RATIO".", ], + [ + "🚫 - Service "MAX_CLEAR_RATIO" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/MAX_CLEAR_RATIO".", + ], [ "🚫 - Service "MAX_CONNECTIONS" not found in "/home/whoiam/projects/whook/dist/services/MAX_CONNECTIONS".", ], @@ -439,6 +484,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "MAX_CONNECTIONS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/MAX_CONNECTIONS".", ], + [ + "🚫 - Service "MAX_CONNECTIONS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/MAX_CONNECTIONS".", + ], [ "🚫 - Service "MAX_HEADERS_COUNT" not found in "/home/whoiam/projects/whook/dist/services/MAX_HEADERS_COUNT".", ], @@ -448,6 +496,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "MAX_HEADERS_COUNT" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/MAX_HEADERS_COUNT".", ], + [ + "🚫 - Service "MAX_HEADERS_COUNT" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/MAX_HEADERS_COUNT".", + ], [ "🚫 - Service "PARSERS" not found in "/home/whoiam/projects/whook/dist/services/PARSERS".", ], @@ -457,6 +508,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "PARSERS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/PARSERS".", ], + [ + "🚫 - Service "PARSERS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/PARSERS".", + ], [ "🚫 - Service "PROCESS_ENV" not found in "/home/whoiam/projects/whook/dist/services/PROCESS_ENV".", ], @@ -466,6 +520,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "PROCESS_ENV" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/PROCESS_ENV".", ], + [ + "🚫 - Service "PROCESS_ENV" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/PROCESS_ENV".", + ], [ "🚫 - Service "PROCESS_NAME" not found in "/home/whoiam/projects/whook/dist/services/PROCESS_NAME".", ], @@ -475,6 +532,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "PROCESS_NAME" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/PROCESS_NAME".", ], + [ + "🚫 - Service "PROCESS_NAME" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/PROCESS_NAME".", + ], [ "🚫 - Service "PROTOCOL" not found in "/home/whoiam/projects/whook/dist/services/PROTOCOL".", ], @@ -484,6 +544,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "PROTOCOL" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/PROTOCOL".", ], + [ + "🚫 - Service "PROTOCOL" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/PROTOCOL".", + ], [ "🚫 - Service "REDUCED_FILES_SUFFIXES" not found in "/home/whoiam/projects/whook/dist/services/REDUCED_FILES_SUFFIXES".", ], @@ -493,6 +556,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "REDUCED_FILES_SUFFIXES" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/REDUCED_FILES_SUFFIXES".", ], + [ + "🚫 - Service "REDUCED_FILES_SUFFIXES" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/REDUCED_FILES_SUFFIXES".", + ], [ "🚫 - Service "SENSIBLE_HEADERS" not found in "/home/whoiam/projects/whook/dist/services/SENSIBLE_HEADERS".", ], @@ -502,6 +568,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "SENSIBLE_HEADERS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/SENSIBLE_HEADERS".", ], + [ + "🚫 - Service "SENSIBLE_HEADERS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/SENSIBLE_HEADERS".", + ], [ "🚫 - Service "SENSIBLE_PROPS" not found in "/home/whoiam/projects/whook/dist/services/SENSIBLE_PROPS".", ], @@ -511,6 +580,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "SENSIBLE_PROPS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/SENSIBLE_PROPS".", ], + [ + "🚫 - Service "SENSIBLE_PROPS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/SENSIBLE_PROPS".", + ], [ "🚫 - Service "SHIELD_CHAR" not found in "/home/whoiam/projects/whook/dist/services/SHIELD_CHAR".", ], @@ -520,6 +592,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "SHIELD_CHAR" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/SHIELD_CHAR".", ], + [ + "🚫 - Service "SHIELD_CHAR" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/SHIELD_CHAR".", + ], [ "🚫 - Service "SIGNALS" not found in "/home/whoiam/projects/whook/dist/services/SIGNALS".", ], @@ -529,6 +604,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "SIGNALS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/SIGNALS".", ], + [ + "🚫 - Service "SIGNALS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/SIGNALS".", + ], [ "🚫 - Service "SOCKET_TIMEOUT" not found in "/home/whoiam/projects/whook/dist/services/SOCKET_TIMEOUT".", ], @@ -538,6 +616,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "SOCKET_TIMEOUT" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/SOCKET_TIMEOUT".", ], + [ + "🚫 - Service "SOCKET_TIMEOUT" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/SOCKET_TIMEOUT".", + ], [ "🚫 - Service "STRINGIFYERS" not found in "/home/whoiam/projects/whook/dist/services/STRINGIFYERS".", ], @@ -547,6 +628,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "STRINGIFYERS" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/STRINGIFYERS".", ], + [ + "🚫 - Service "STRINGIFYERS" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/STRINGIFYERS".", + ], [ "🚫 - Service "TIMEOUT" not found in "/home/whoiam/projects/whook/dist/services/TIMEOUT".", ], @@ -556,15 +640,24 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "TIMEOUT" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/TIMEOUT".", ], + [ + "🚫 - Service "TIMEOUT" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/TIMEOUT".", + ], + [ + "🚫 - Service "getPingWrapped" not found in "/home/whoiam/projects/whook/packages/whook-cors/dist/handlers/getPing".", + ], [ "🚫 - Service "getPingWrapped" not found in "/home/whoiam/projects/whook/packages/whook-example/src/handlers/getPing".", ], [ - "🚫 - Service "optionsWithCORSWrapped" not found in "/home/whoiam/projects/whook/dist/handlers/optionsWithCORS".", + "🚫 - Service "getPingWrapped" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/handlers/getPing".", ], [ "🚫 - Service "optionsWithCORSWrapped" not found in "/home/whoiam/projects/whook/packages/whook-example/src/handlers/optionsWithCORS".", ], + [ + "🚫 - Service "optionsWithCORSWrapped" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/handlers/optionsWithCORS".", + ], [ "🚫 - Service "readDir" not found in "/home/whoiam/projects/whook/dist/services/readDir".", ], @@ -574,6 +667,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "readDir" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/readDir".", ], + [ + "🚫 - Service "readDir" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/readDir".", + ], [ "🚫 - Service "readFile" not found in "/home/whoiam/projects/whook/dist/services/readFile".", ], @@ -583,6 +679,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "readFile" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/readFile".", ], + [ + "🚫 - Service "readFile" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/readFile".", + ], [ "🚫 - Service "uniqueId" not found in "/home/whoiam/projects/whook/dist/services/uniqueId".", ], @@ -592,6 +691,9 @@ exports[`runServer should work 1`] = ` [ "🚫 - Service "uniqueId" not found in "/home/whoiam/projects/whook/packages/whook-example/src/services/uniqueId".", ], + [ + "🚫 - Service "uniqueId" not found in "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/services/uniqueId".", + ], [ "🛂 - Dynamic import of "/home/whoiam/projects/whook/dist/handlers/getPing.js".", ], @@ -730,6 +832,9 @@ exports[`runServer should work 1`] = ` [ "🛂 - Resolving "@whook/cors" to "/home/whoiam/projects/whook/packages/whook-cors/dist/index.js".", ], + [ + "🛂 - Resolving "@whook/gcp-functions" to "/home/whoiam/projects/whook/packages/whook-gcp-functions/dist/index.js".", + ], [ "🛂 - Resolving "@whook/whook" to "/home/whoiam/projects/whook/dist/index.js".", ], diff --git a/packages/whook-example/src/build.test.ts b/packages/whook-example/src/build.test.ts new file mode 100644 index 00000000..8bf5e97d --- /dev/null +++ b/packages/whook-example/src/build.test.ts @@ -0,0 +1,36 @@ +import { describe, test } from '@jest/globals'; +import { exec } from 'child_process'; + +describe('build should work', () => { + [ + ['getPing', '{}'], + ['getOpenAPI', '{}'], + [ + 'getParameters', + '{ "aHeader": "true", "pathParam1":"4", "pathParam2":"a,b,c,d" }', + ], + ['getTime', '{}'], + ['getDelay', '{ "duration": 1 }'], + ['putEcho', '{"body": { "echo": "YOLO!" }}'], + ].forEach(([operationId, parameters]) => { + test(`with ${operationId} http lambdas`, async () => { + await execCommand( + `JWT_SECRET=test npx whook testHTTPFunction --name ${operationId} --parameters '${parameters}'`, + ); + }); + }); +}); + +async function execCommand( + command, +): Promise<{ stdout: string; stderr: string }> { + return new Promise((resolve, reject) => { + exec(command, (err, stdout, stderr) => { + if (err) { + reject(err); + return; + } + resolve({ stdout, stderr }); + }); + }); +} diff --git a/packages/whook-example/src/build.ts b/packages/whook-example/src/build.ts index 943d48c2..666f3fca 100644 --- a/packages/whook-example/src/build.ts +++ b/packages/whook-example/src/build.ts @@ -2,11 +2,13 @@ import { Knifecycle, constant, alsoInject } from 'knifecycle'; import { DEFAULT_BUILD_INITIALIZER_PATH_MAP, initBuildConstants, - runBuild as runBaseBuild, - prepareBuildEnvironment as prepareBaseBuildEnvironment, } from '@whook/whook'; import { prepareEnvironment } from './index.js'; import { packageDirectory } from 'pkg-dir'; +import { + runBuild as runBaseBuild, + prepareBuildEnvironment as prepareBaseBuildEnvironment, +} from '@whook/gcp-functions'; /* Architecture Note #1.2: The build file @@ -46,6 +48,13 @@ export async function prepareBuildEnvironment( constant('INITIALIZER_PATH_MAP', { ...DEFAULT_BUILD_INITIALIZER_PATH_MAP, // MY_SERVICE: '@my/service_module_name', + ENV: '@whook/whook/dist/services/ProxyedENV', + apm: '@whook/http-transaction/dist/services/apm', + obfuscator: '@whook/http-transaction/dist/services/obfuscator', + errorHandler: '@whook/http-router/dist/services/errorHandler', + log: '@whook/gcp-functions/dist/services/log', + time: 'common-services/dist/time', + delay: 'common-services/dist/delay', }), ); diff --git a/packages/whook-example/src/commands/terraformValues.ts b/packages/whook-example/src/commands/terraformValues.ts new file mode 100644 index 00000000..dfa04c1d --- /dev/null +++ b/packages/whook-example/src/commands/terraformValues.ts @@ -0,0 +1,227 @@ +import { extra, autoService } from 'knifecycle'; +import { readArgs } from '@whook/whook'; +import { flattenOpenAPI, getOpenAPIOperations } from '@whook/http-router'; +import { YError } from 'yerror'; +import { exec } from 'child_process'; +import crypto from 'crypto'; +import yaml from 'js-yaml'; +import type { ExecException } from 'child_process'; +import type { LogService } from 'common-services'; +import type { + WhookCommandArgs, + WhookCommandDefinition, + WhookAPIHandlerDefinition, +} from '@whook/whook'; +import type { OpenAPIV3 } from 'openapi-types'; + +export const definition: WhookCommandDefinition = { + description: 'A command printing functions informations for Terraform', + example: `whook terraformValues --type paths`, + arguments: { + type: 'object', + additionalProperties: false, + required: ['type'], + properties: { + type: { + description: 'Type of values to return', + type: 'string', + enum: ['globals', 'paths', 'functions', 'function'], + }, + pretty: { + description: 'Pretty print JSON values', + type: 'boolean', + }, + functionName: { + description: 'Name of the function', + type: 'string', + }, + pathsIndex: { + description: 'Index of the paths to retrieve', + type: 'number', + }, + functionType: { + description: 'Types of the functions to return', + type: 'string', + }, + }, + }, +}; + +export default extra(definition, autoService(initTerraformValuesCommand)); + +async function initTerraformValuesCommand({ + API, + BASE_PATH, + log, + args, + execAsync = _execAsync, +}: { + API: OpenAPIV3.Document; + BASE_PATH: string; + log: LogService; + args: WhookCommandArgs; + execAsync: typeof _execAsync; +}) { + return async () => { + const { + namedArguments: { type, pretty, functionName, functionType }, + } = readArgs<{ + type: string; + pretty: boolean; + functionName: string; + functionType: string; + }>(definition.arguments, args); + const operations = getOpenAPIOperations< + WhookAPIHandlerDefinition['operation']['x-whook'] + >(await flattenOpenAPI(API)); + const configurations = operations.map((operation) => { + const whookConfiguration = (operation['x-whook'] || { + type: 'http', + }) as WhookAPIHandlerDefinition['operation']['x-whook']; + const configuration = { + type: 'http', + timeout: '10', + memory: '128', + description: operation.summary || '', + enabled: 'true', + sourceOperationId: operation.operationId, + suffix: '', + ...Object.keys(whookConfiguration || {}).reduce( + (accConfigurations, key) => ({ + ...accConfigurations, + [key]: ( + ( + whookConfiguration as NonNullable< + WhookAPIHandlerDefinition['operation']['x-whook'] + > + )[key] as string + ).toString(), + }), + {}, + ), + }; + const qualifiedOperationId = + (configuration.sourceOperationId || operation.operationId) + + (configuration.suffix || ''); + + return { + qualifiedOperationId, + method: operation.method.toUpperCase(), + path: operation.path, + ...configuration, + }; + }); + + if (type === 'globals') { + const commitHash = await execAsync(`git rev-parse HEAD`); + const commitMessage = ( + await execAsync(`git rev-list --format=%B --max-count=1 HEAD`) + ).split('\n')[1]; + const openapi2 = yaml.safeDump({ + swagger: '2.0', + info: { + title: API.info.title, + description: API.info.description, + version: API.info.version, + }, + host: '${infos_host}', + basePath: BASE_PATH, + schemes: ['https'], + produces: ['application/json'], + paths: configurations.reduce((accPaths, configuration) => { + const operation = operations.find( + ({ operationId }) => + operationId === configuration.sourceOperationId, + ); + + return { + ...accPaths, + [configuration.path]: { + ...(accPaths[configuration.path] || {}), + [configuration.method.toLowerCase()]: { + summary: configuration.description || '', + operationId: configuration.qualifiedOperationId, + ...((operation?.parameters || []).length + ? { + parameters: ( + operation?.parameters as OpenAPIV3.ParameterObject[] + ).map(({ in: theIn, name, required }) => ({ + in: theIn, + name, + type: 'string', + required: required || false, + })), + } + : undefined), + 'x-google-backend': { + address: `\${function_${configuration.qualifiedOperationId}}`, + }, + responses: { + '200': { description: 'x', schema: { type: 'string' } }, + }, + }, + }, + }; + }, {}), + }); + const openapiHash = crypto + .createHash('md5') + .update(JSON.stringify(API)) + .digest('hex'); + const infos = { + commitHash, + commitMessage, + openapi2, + openapiHash, + }; + log('info', JSON.stringify(infos)); + return; + } + + if (type === 'functions') { + const functions = configurations + .filter((configuration) => + functionType ? configuration.type === functionType : true, + ) + .reduce( + (accLambdas, configuration) => ({ + ...accLambdas, + [configuration.qualifiedOperationId]: + configuration.qualifiedOperationId, + }), + {}, + ); + + log('info', `${JSON.stringify(functions, null, pretty ? 2 : 0)}`); + return; + } + + if (!functionName) { + throw new YError('E_FUNCTION_NAME_REQUIRED'); + } + + const functionConfiguration = configurations.find( + ({ qualifiedOperationId }) => qualifiedOperationId === functionName, + ); + + log( + 'info', + `${JSON.stringify(functionConfiguration, null, pretty ? 2 : 0)}`, + ); + }; +} + +async function _execAsync(command: string): Promise { + return await new Promise((resolve, reject) => { + exec( + command, + (err: ExecException | null, stdout: string, stderr: string) => { + if (err) { + reject(YError.wrap(err, 'E_EXEC_FAILURE', stderr)); + return; + } + resolve(stdout.trim()); + }, + ); + }); +} diff --git a/packages/whook-example/src/config/common/config.ts b/packages/whook-example/src/config/common/config.ts index 425ebc90..deaf1e39 100644 --- a/packages/whook-example/src/config/common/config.ts +++ b/packages/whook-example/src/config/common/config.ts @@ -29,9 +29,17 @@ const CONFIG: Omit = { CONFIG: { name: _packageJSON.name, description: _packageJSON.description || '', + baseURL: 'https://api.example.com', }, NODE_ENVS, DEBUG_NODE_ENVS: process.env.DEBUG ? NODE_ENVS : DEBUG_NODE_ENVS, + COMPILER_OPTIONS: { + externalModules: ['portfinder', 'internal-ip'], + ignoredModules: ['ecstatic', 'swagger-ui-dist'], + excludeNodeModules: true, + }, + BUILD_PARALLELISM: 10, + PROXYED_ENV_VARS: ['NODE_ENV', 'JWT_SECRET'], SERVICE_NAME_MAP: {}, ERRORS_DESCRIPTORS: { ...DEFAULT_ERRORS_DESCRIPTORS, diff --git a/packages/whook-example/src/config/test/config.ts b/packages/whook-example/src/config/test/config.ts index 609179c3..708e3b2b 100644 --- a/packages/whook-example/src/config/test/config.ts +++ b/packages/whook-example/src/config/test/config.ts @@ -2,6 +2,10 @@ import COMMON_CONFIG from '../common/config.js'; import type { WhookConfigs } from '@whook/whook'; const CONFIG: WhookConfigs = { + BASE_ENV: { + ...COMMON_CONFIG.BASE_ENV, + JWT_SECRET: 'yop', + }, ...COMMON_CONFIG, HOST: 'localhost', }; diff --git a/packages/whook-example/src/index.ts b/packages/whook-example/src/index.ts index e00f9bc5..b2c0b022 100644 --- a/packages/whook-example/src/index.ts +++ b/packages/whook-example/src/index.ts @@ -176,7 +176,13 @@ export async function prepareEnvironment( You can also avoid Whook defaults by leaving it empty. */ - $.register(constant('WHOOK_PLUGINS', ['@whook/whook', '@whook/cors'])); + $.register( + constant('WHOOK_PLUGINS', [ + '@whook/gcp-functions', + '@whook/cors', + '@whook/whook', + ]), + ); // Add the CORS wrapped error handler $.register(initErrorHandlerWithCORS); diff --git a/packages/whook-example/src/whook.d.ts b/packages/whook-example/src/whook.d.ts index 3d276b95..67d2dc6d 100644 --- a/packages/whook-example/src/whook.d.ts +++ b/packages/whook-example/src/whook.d.ts @@ -3,6 +3,8 @@ import type { WhookBaseEnv, WhookBaseConfigs, WhookAPIOperation, + ProxyedENVConfig, + WhookCompilerConfig, } from '@whook/whook'; import type { WhookAuthorizationConfig } from '@whook/authorization'; import type { @@ -13,6 +15,10 @@ import type { import type { WhookAPIOperationCORSConfig, WhookCORSConfig } from '@whook/cors'; import type { APIConfig } from './services/API.js'; import type { JWTServiceConfig } from 'jwt-service'; +import type { + WhookAPIOperationGCPFunctionConfig, + WhookGCPBuildConfig, +} from '@whook/gcp-functions'; declare module '@whook/whook' { // Eventually override the process env type here @@ -29,6 +35,9 @@ The configuration is typed so that you are sure you cannot WhookSwaggerUIConfig, WhookCORSConfig, APIConfig, + ProxyedENVConfig, + WhookCompilerConfig, + WhookGCPBuildConfig, JWTServiceConfig {} /* Architecture Note #3.2.3: Typings @@ -46,7 +55,18 @@ Here we export a custom handler definition type in order > extends WhookBaseAPIHandlerDefinition { operation: U & WhookAPIOperation< - T & WhookAPIOperationSwaggerConfig & WhookAPIOperationCORSConfig + T & + WhookAPIOperationGCPFunctionConfig & + WhookAPIOperationSwaggerConfig & + WhookAPIOperationCORSConfig & + // TODO: Add those properties to Whook GCP Functions? + { + private?: boolean; + memory?: number; + timeout?: number; + suffix?: string; + sourceOperationId?: string; + } >; } } diff --git a/packages/whook-example/terraform/functions.tf b/packages/whook-example/terraform/functions.tf new file mode 100644 index 00000000..c19b9f70 --- /dev/null +++ b/packages/whook-example/terraform/functions.tf @@ -0,0 +1,53 @@ +data "external" "functionConfiguration" { + for_each = data.external.functions.result + + program = ["env", "NODE_ENV=${terraform.workspace}", "npx", "whook", "terraformValues", "--type='function'", "--functionName='${each.key}'"] +} + +resource "google_storage_bucket" "storage_bucket" { + name = "whook_functions" +} + +data "archive_file" "functions" { + for_each = data.external.functions.result + + type = "zip" + source_dir = "./builds/${terraform.workspace}/${each.key}" + output_path = "./builds/${terraform.workspace}/${each.key}.zip" +} + +resource "google_storage_bucket_object" "storage_bucket_object" { + for_each = data.external.functions.result + + name = "${terraform.workspace}_${each.key}" + source = "./builds/${terraform.workspace}/${each.key}.zip" + bucket = google_storage_bucket.storage_bucket.name + depends_on = [data.archive_file.functions] +} + +resource "google_cloudfunctions_function" "cloudfunctions_function" { + for_each = data.external.functions.result + + name = "${terraform.workspace}_${each.key}" + description = data.external.functionConfiguration[each.key].result["description"] + runtime = "nodejs10" + + available_memory_mb = data.external.functionConfiguration[each.key].result["memory"] + timeout = data.external.functionConfiguration[each.key].result["timeout"] + source_archive_bucket = google_storage_bucket.storage_bucket.name + source_archive_object = google_storage_bucket_object.storage_bucket_object[each.key].name + trigger_http = true + entry_point = "default" +} + +# Seems to not work (no idea why) +# resource "google_cloudfunctions_function_iam_member" "invoker" { +# for_each = data.external.functions.result + +# project = google_cloudfunctions_function.cloudfunctions_function[each.key].project +# region = google_cloudfunctions_function.cloudfunctions_function[each.key].region +# cloud_function = google_cloudfunctions_function.cloudfunctions_function[each.key].name + +# role = "roles/cloudfunctions.invoker" +# member = "allUsers" +# } diff --git a/packages/whook-example/terraform/main.tf b/packages/whook-example/terraform/main.tf new file mode 100644 index 00000000..ff7c5946 --- /dev/null +++ b/packages/whook-example/terraform/main.tf @@ -0,0 +1,67 @@ +variable "project_id" { + type = string +} + +variable "region" { + type = string + default = "europe-west1" +} + +variable "zone" { + type = string + default = "europe-west1-b" +} + +variable "api_name" { + type = string + default = "api2" +} + +provider "google" { + version = "~> 3.14" + project = var.project_id + region = var.region + zone = var.zone + credentials = file(".credentials.json") +} + +provider "archive" { + version = "~> 1.3" +} + +provider "template" { + version = "~> 2.1.2" +} + +output "api_url" { + value = google_endpoints_service.endpoints_service.dns_address +} + +data "google_project" "project" { + project_id = var.project_id +} + +# imports the functions list +data "external" "functions" { + program = ["env", "NODE_ENV=${terraform.workspace}", "npx", "whook", "terraformValues", "--type='functions'", "--functionType='http'"] +} +data "external" "globals" { + program = ["env", "NODE_ENV=${terraform.workspace}", "npx", "whook", "terraformValues", "--type='globals'"] +} + +data "template_file" "template_file" { + template = data.external.globals.result["openapi2"] + + vars = merge({ + "infos_host" : "${var.api_name}.endpoints.${data.google_project.project.project_id}.cloud.goog" + }, zipmap( + [for key in keys(data.external.functions.result) : "function_${key}"], + [for key in keys(data.external.functions.result) : google_cloudfunctions_function.cloudfunctions_function[key].https_trigger_url] + )) +} + +resource "google_endpoints_service" "endpoints_service" { + service_name = "${var.api_name}.endpoints.${data.google_project.project.project_id}.cloud.goog" + project = data.google_project.project.project_id + openapi_config = data.template_file.template_file.rendered +}