From 21f949fc3a8cb19d133929e2820ce37c4e8b4fd1 Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Tue, 10 Aug 2021 21:00:26 -0700 Subject: [PATCH] feat(cmd-api-server): support grpc web services #1189 Primary change: -------------- The API server now contains a gRPC server in addition to what it had before (HTTP+SocketIO servers) so that through this it can provide the opportunity for plugins to expose their gRPC services via the Cactus API server. It is possible for plugins to serve their requests on multiple protocols at the same time which means that this is a big step towards our goal of being properly multi- protocol capable. Secondary change(s): ------------------- 1. Custom protobuf-schema openapi generator template added because of this issue: https://github.com/thesayyn/protoc-gen-ts/issues/82 (we override the stock template to not use the public keyword) TODO: ---- 1. Implement streaming healthcheck endpiont with gRPC 2. Allow plugins to hook in their own gRPC service implementations. Fixes #1189 Signed-off-by: Peter Somogyvari --- .cspell.json | 5 + .eslintignore | 2 + .../main/typescript/carbon-accounting-app.ts | 1 + .../admin-enroll-v1-endpoint.test.ts | 1 + .../package.json | 1 - .../src/main/typescript/supply-chain-app.ts | 1 + .../package.json | 1 - package.json | 6 +- packages/cactus-cmd-api-server/README.md | 1 + packages/cactus-cmd-api-server/package.json | 6 + .../templates/protobuf-schema/api.mustache | 46 ++++++ .../templates/protobuf-schema/model.mustache | 37 +++++ .../templates/protobuf-schema/root.mustache | 22 +++ .../openapi/.openapi-generator-ignore | 23 +++ .../openapi/.openapi-generator/FILES | 6 + .../openapi/.openapi-generator/VERSION | 1 + .../main/proto/generated/openapi/README.md | 31 ++++ .../models/health_check_response_pb.proto | 24 +++ .../openapi/models/memory_usage_pb.proto | 27 ++++ .../models/watch_healthcheck_v1_pb.proto | 17 +++ .../openapi/services/default_service.proto | 28 ++++ .../src/main/typescript/api-server.ts | 91 +++++++++-- .../common/determine-address-family.ts | 13 ++ .../main/typescript/config/config-service.ts | 22 +++ .../protoc-gen-ts/google/protobuf/empty.ts | 45 ++++++ .../models/health_check_response_pb.ts | 106 +++++++++++++ .../protoc-gen-ts/models/memory_usage_pb.ts | 143 ++++++++++++++++++ .../protoc-gen-ts/services/default_service.ts | 114 ++++++++++++++ .../src/main/typescript/proto.d.ts | 5 + .../src/main/typescript/public-api.ts | 7 + .../grpc/grpc-server-api-server.ts | 45 ++++++ .../benchmark/artillery-api-benchmark.test.ts | 1 + .../jwt-endpoint-authorization.test.ts | 1 + ...t-endpoint-authz-scope-enforcement.test.ts | 1 + ...wt-socketio-endpoint-authorization.test.ts | 1 + ...otected-endpoint-authz-ops-confirm.test.ts | 1 + .../jwt-unprotected-endpoint-authz.test.ts | 1 + .../integration/remote-plugin-imports.test.ts | 1 + ...js-proto-loader-client-healthcheck.test.ts | 105 +++++++++++++ ...pc-proto-gen-ts-client-healthcheck.test.ts | 85 +++++++++++ ...-proto-gen-ts-client-m-tls-enabled.test.ts | 98 ++++++++++++ ...all-basic-plugin-consortium-manual.test.ts | 1 + ...stall-basic-plugin-keychain-memory.test.ts | 1 + .../api-client-routing-node-to-node.test.ts | 2 + .../plugin-import-with-npm-install.test.ts | 1 + .../integration/remote-plugin-imports.test.ts | 1 + .../runtime-plugin-imports.test.ts | 1 + .../get-consortium-jws-endpoint.test.ts | 3 + .../get-balance-endpoint.test.ts | 1 + .../get-block-endpoint.test.ts | 1 + .../get-past-logs-endpoint.test.ts | 1 + .../get-transaction-endpoint.test.ts | 1 + .../sign-transaction-endpoint.test.ts | 1 + yarn.lock | 76 +++++++--- 54 files changed, 1225 insertions(+), 39 deletions(-) create mode 100644 packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/api.mustache create mode 100644 packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/model.mustache create mode 100644 packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/root.mustache create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator-ignore create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/VERSION create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/README.md create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/health_check_response_pb.proto create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/memory_usage_pb.proto create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/watch_healthcheck_v1_pb.proto create mode 100644 packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/common/determine-address-family.ts create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/google/protobuf/empty.ts create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/health_check_response_pb.ts create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/memory_usage_pb.ts create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/proto.d.ts create mode 100644 packages/cactus-cmd-api-server/src/main/typescript/web-services/grpc/grpc-server-api-server.ts create mode 100644 packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-js-proto-loader-client-healthcheck.test.ts create mode 100644 packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-healthcheck.test.ts create mode 100644 packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-m-tls-enabled.test.ts diff --git a/.cspell.json b/.cspell.json index 4bf71935059..a74cd44451b 100644 --- a/.cspell.json +++ b/.cspell.json @@ -36,6 +36,7 @@ "gopath", "grpc", "grpcs", + "grpcwebtext", "hashicorp", "Healthcheck", "HTLC", @@ -72,12 +73,16 @@ "NODETXPOOLACK", "notok", "Oidc", + "oneofs", "onsi", "OpenAPI", "openethereum", "organisation", "parameterizable", "Postgres", + "proto", + "protobuf", + "protoc", "protos", "RUSTC", "Secp", diff --git a/.eslintignore b/.eslintignore index cd9187a3096..b1945136b6a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,3 +14,5 @@ # **/coverage/** # typings/** + +**/src/main/typescript/generated/proto/** diff --git a/examples/cactus-example-carbon-accounting-backend/src/main/typescript/carbon-accounting-app.ts b/examples/cactus-example-carbon-accounting-backend/src/main/typescript/carbon-accounting-app.ts index bb0cee9a753..b668ebb6edf 100644 --- a/examples/cactus-example-carbon-accounting-backend/src/main/typescript/carbon-accounting-app.ts +++ b/examples/cactus-example-carbon-accounting-backend/src/main/typescript/carbon-accounting-app.ts @@ -231,6 +231,7 @@ export class CarbonAccountingApp { config.apiHost = addressInfoApi.address; config.cockpitHost = addressInfoCockpit.address; config.cockpitPort = addressInfoCockpit.port; + config.grpcPort = 0; // TODO - make this configurable as well config.logLevel = this.options.logLevel || "INFO"; } diff --git a/examples/cactus-example-carbon-accounting-backend/src/test/typescript/integration/admin-enroll-v1-endpoint.test.ts b/examples/cactus-example-carbon-accounting-backend/src/test/typescript/integration/admin-enroll-v1-endpoint.test.ts index 475e2b42b3d..b6837462080 100644 --- a/examples/cactus-example-carbon-accounting-backend/src/test/typescript/integration/admin-enroll-v1-endpoint.test.ts +++ b/examples/cactus-example-carbon-accounting-backend/src/test/typescript/integration/admin-enroll-v1-endpoint.test.ts @@ -81,6 +81,7 @@ test(testCase, async (t: Test) => { apiSrvOpts.apiCorsDomainCsv = "*"; apiSrvOpts.apiPort = 0; apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcPort = 0; apiSrvOpts.apiTlsEnabled = false; apiSrvOpts.plugins = []; const convictConfig = configService.newExampleConfigConvict(apiSrvOpts); diff --git a/examples/cactus-example-carbon-accounting-frontend/package.json b/examples/cactus-example-carbon-accounting-frontend/package.json index 78297971d91..eece2dd71a1 100644 --- a/examples/cactus-example-carbon-accounting-frontend/package.json +++ b/examples/cactus-example-carbon-accounting-frontend/package.json @@ -45,7 +45,6 @@ "@angular/compiler-cli": "12.1.1", "@angular/language-service": "12.1.1", "@ionic/angular-toolkit": "2.3.0", - "@types/jasminewd2": "2.0.3", "@types/node": "12.11.1", "codelyzer": "6.0.2", "https-browserify": "1.0.0", diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts index ff75b21884f..15482dda429 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts @@ -492,6 +492,7 @@ export class SupplyChainApp { properties.apiHost = addressInfoApi.address; properties.cockpitHost = addressInfoCockpit.address; properties.cockpitPort = addressInfoCockpit.port; + properties.grpcPort = 0; // TODO - make this configurable as well properties.logLevel = this.options.logLevel || "INFO"; const apiServer = new ApiServer({ diff --git a/examples/cactus-example-supply-chain-frontend/package.json b/examples/cactus-example-supply-chain-frontend/package.json index dd428d117a9..74665c27677 100644 --- a/examples/cactus-example-supply-chain-frontend/package.json +++ b/examples/cactus-example-supply-chain-frontend/package.json @@ -46,7 +46,6 @@ "@angular/compiler-cli": "12.1.1", "@angular/language-service": "12.1.1", "@ionic/angular-toolkit": "2.3.0", - "@types/jasminewd2": "2.0.3", "@types/node": "12.11.1", "codelyzer": "6.0.2", "constants-browserify": "1.0.0", diff --git a/package.json b/package.json index 14ef3cafb34..7506fa288d7 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "start:example-supply-chain": "cd ./examples/supply-chain-app/ && npm i --no-package-lock && npm run start", "start:example-carbon-accounting": "CONFIG_FILE=examples/cactus-example-carbon-accounting-backend/example-config.json node examples/cactus-example-carbon-accounting-backend/dist/lib/main/typescript/carbon-accounting-app-cli.js", "purge-build-cache": "del-cli .build-cache/*", - "clean": "npm run purge-build-cache && del-cli \"./{packages,examples,extensions}/cactus-*/{dist,.nyc_output,src/main/typescript/generated/openapi/typescript-axios/*}\"", + "clean": "npm run purge-build-cache && del-cli \"./{packages,examples,extensions}/cactus-*/{dist,.nyc_output,src/main/proto/generated/*,src/main/typescript/generated/proto/protoc-gen-ts/*,src/main/typescript/generated/openapi/typescript-axios/*}\"", "lint": "eslint '*/*/src/**/*.{js,ts}' --quiet --fix && cspell \"*/*/src/**/*.{js,ts}\"", "tsc": "tsc --build --verbose", "codegen": "lerna run codegen", @@ -79,7 +79,6 @@ "@commitlint/config-conventional": "8.0.0", "@openapitools/openapi-generator-cli": "2.3.3", "@types/fs-extra": "9.0.11", - "@types/jasminewd2": "2.0.10", "@types/node": "15.14.7", "@types/node-fetch": "2.5.4", "@types/tape": "4.13.0", @@ -104,6 +103,8 @@ "fs-extra": "10.0.0", "git-cz": "4.7.6", "globby": "10.0.2", + "grpc-tools": "1.11.2", + "grpc_tools_node_protoc_ts": "5.3.1", "husky": "4.2.5", "inquirer": "8.1.1", "json5": "2.2.0", @@ -118,6 +119,7 @@ "npm-run-all": "4.1.5", "npm-watch": "0.7.0", "prettier": "2.0.5", + "protoc-gen-ts": "0.4.0", "run-time-error": "1.4.0", "secp256k1": "4.0.0", "shebang-loader": "0.0.1", diff --git a/packages/cactus-cmd-api-server/README.md b/packages/cactus-cmd-api-server/README.md index 714e1e7fabc..0054e398fb6 100644 --- a/packages/cactus-cmd-api-server/README.md +++ b/packages/cactus-cmd-api-server/README.md @@ -116,6 +116,7 @@ const main = async () => { apiServerOptions.apiCorsDomainCsv = "your.domain.example.com"; apiServerOptions.apiPort = 3000; apiServerOptions.cockpitPort = 3100; + apiServerOptions.grpcPort = 5000; // Disble TLS (or provide TLS certs for secure HTTP if you are deploying to production) apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = [ diff --git a/packages/cactus-cmd-api-server/package.json b/packages/cactus-cmd-api-server/package.json index d5ae8919a23..9f5881696c5 100644 --- a/packages/cactus-cmd-api-server/package.json +++ b/packages/cactus-cmd-api-server/package.json @@ -14,6 +14,9 @@ "scripts": { "generate-sdk": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/ --reserved-words-mappings protected=protected", "codegen:openapi": "npm run generate-sdk", + "proto:openapi": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g protobuf-schema --model-name-suffix=PB --additional-properties=packageName=org.hyperledger.cactus.cmd_api_server -o ./src/main/proto/generated/openapi/ -t=./src/main/openapi-generator/templates/protobuf-schema/", + "proto:protoc-gen-ts": "yarn run grpc_tools_node_protoc --plugin=protoc-gen-ts=../../node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:./src/main/typescript/generated/proto/protoc-gen-ts/ --proto_path ./src/main/proto/generated/openapi/ ./src/main/proto/generated/openapi/services/*.proto", + "codegen:proto": "run-s proto:openapi proto:protoc-gen-ts", "codegen": "run-p 'codegen:*'", "watch": "npm-watch", "webpack": "npm-run-all webpack:dev webpack:prod", @@ -72,6 +75,8 @@ }, "homepage": "https://github.com/hyperledger/cactus#readme", "dependencies": { + "@grpc/grpc-js": "1.3.6", + "@grpc/proto-loader": "0.6.4", "@hyperledger/cactus-common": "0.8.0", "@hyperledger/cactus-core": "0.8.0", "@hyperledger/cactus-core-api": "0.8.0", @@ -109,6 +114,7 @@ "@types/express": "4.17.8", "@types/express-http-proxy": "1.6.1", "@types/express-jwt": "6.0.1", + "@types/google-protobuf": "3.15.3", "@types/jsonwebtoken": "8.5.1", "@types/multer": "1.4.5", "@types/node-forge": "0.9.3", diff --git a/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/api.mustache b/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/api.mustache new file mode 100644 index 00000000000..839363b689a --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/api.mustache @@ -0,0 +1,46 @@ +{{>partial_header}} +syntax = "proto3"; + +package {{{packageName}}}; + +import "google/protobuf/empty.proto"; +{{#imports}} +{{#import}} +import "{{{modelPackage}}}/{{{.}}}.proto"; +{{/import}} +{{/imports}} + +service {{classname}} { +{{#operations}} +{{#operation}} + {{#description}} + // {{{.}}} + {{/description}} + rpc {{operationId}} ({{#hasParams}}{{operationId}}Request{{/hasParams}}{{^hasParams}}google.protobuf.Empty{{/hasParams}}) returns ({{#vendorExtensions.x-grpc-response}}{{.}}{{/vendorExtensions.x-grpc-response}}{{^vendorExtensions.x-grpc-response}}{{operationId}}Response{{/vendorExtensions.x-grpc-response}}); + +{{/operation}} +{{/operations}} +} + +{{#operations}} +{{#operation}} +{{#hasParams}} +message {{operationId}}Request { + {{#allParams}} + {{#description}} + // {{{.}}} + {{/description}} + {{#vendorExtensions.x-protobuf-type}}{{.}} {{/vendorExtensions.x-protobuf-type}}{{vendorExtensions.x-protobuf-data-type}} {{paramName}} = {{vendorExtensions.x-protobuf-index}}; + {{/allParams}} + +} + +{{/hasParams}} +{{^vendorExtensions.x-grpc-response}} +message {{operationId}}Response { + {{{vendorExtensions.x-grpc-response-type}}} data = 1; +} + +{{/vendorExtensions.x-grpc-response}} +{{/operation}} +{{/operations}} \ No newline at end of file diff --git a/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/model.mustache b/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/model.mustache new file mode 100644 index 00000000000..2a9ef1b1347 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/model.mustache @@ -0,0 +1,37 @@ +{{>partial_header}} +syntax = "proto3"; + +package {{{packageName}}}; + +{{#imports}} +{{#import}} +import "{{{modelPackage}}}/{{{import}}}.proto"; +{{/import}} +{{/imports}} + +{{#models}} +{{#model}} +message {{classname}} { + {{#vars}} + {{#description}} + // {{{.}}} + {{/description}} + {{^isEnum}} + {{#vendorExtensions.x-protobuf-type}}{{{.}}} {{/vendorExtensions.x-protobuf-type}}{{{vendorExtensions.x-protobuf-data-type}}} {{{name}}} = {{vendorExtensions.x-protobuf-index}}{{#vendorExtensions.x-protobuf-packed}} [packed=true]{{/vendorExtensions.x-protobuf-packed}}; + {{/isEnum}} + {{#isEnum}} + enum {{enumName}} { + {{#allowableValues}} + {{#enumVars}} + {{{name}}} = {{{protobuf-enum-index}}}; + {{/enumVars}} + {{/allowableValues}} + } + + {{enumName}} {{name}} = {{vendorExtensions.x-protobuf-index}}; + {{/isEnum}} + + {{/vars}} +} +{{/model}} +{{/models}} \ No newline at end of file diff --git a/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/root.mustache b/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/root.mustache new file mode 100644 index 00000000000..e2df456144c --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/openapi-generator/templates/protobuf-schema/root.mustache @@ -0,0 +1,22 @@ +{{>partial_header}} +syntax = "proto3"; + +package {{{packageName}}}; + +{{#vendorExtensions.x-grpc-options}} +option {{{.}}}; +{{/vendorExtensions.x-grpc-options}} + +// Models +{{#models}} +{{#model}} +import "{{modelPackage}}/{{classFilename}}.proto"; +{{/model}} +{{/models}} + +// APIs +{{#apiInfo}} +{{#apis}} +import "{{apiPackage}}/{{classFilename}}.proto"; +{{/apis}} +{{/apiInfo}} \ No newline at end of file diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator-ignore b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES new file mode 100644 index 00000000000..bd08ea9f6fb --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/FILES @@ -0,0 +1,6 @@ +.openapi-generator-ignore +README.md +models/health_check_response_pb.proto +models/memory_usage_pb.proto +models/watch_healthcheck_v1_pb.proto +services/default_service.proto diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/VERSION b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/VERSION new file mode 100644 index 00000000000..3bff059174b --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/.openapi-generator/VERSION @@ -0,0 +1 @@ +5.1.1 \ No newline at end of file diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/README.md b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/README.md new file mode 100644 index 00000000000..e8f8c7b3554 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/README.md @@ -0,0 +1,31 @@ +# gPRC for org.hyperledger.cactus.cmd_api_server + +Interact with a Cactus deployment through HTTP. + +## Overview +These files were generated by the [OpenAPI Generator](https://openapi-generator.tech) project. + +- API version: 0.0.1 +- Package version: +- Build package: org.openapitools.codegen.languages.ProtobufSchemaCodegen + +## Usage + +Below are some usage examples for Go and Ruby. For other languages, please refer to https://grpc.io/docs/quickstart/. + +### Go +``` +# assuming `protoc-gen-go` has been installed with `go get -u github.com/golang/protobuf/protoc-gen-go` +mkdir /var/tmp/go/ +protoc --go_out=/var/tmp/go/ services/* +protoc --go_out=/var/tmp/go/ models/* +``` + +### Ruby +``` +# assuming `grpc_tools_ruby_protoc` has been installed via `gem install grpc-tools` +RUBY_OUTPUT_DIR="/var/tmp/ruby/org.hyperledger.cactus.cmd_api_server" +mkdir $RUBY_OUTPUT_DIR +grpc_tools_ruby_protoc --ruby_out=$RUBY_OUTPUT_DIR --grpc_out=$RUBY_OUTPUT_DIR/lib services/* +grpc_tools_ruby_protoc --ruby_out=$RUBY_OUTPUT_DIR --grpc_out=$RUBY_OUTPUT_DIR/lib models/* +``` diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/health_check_response_pb.proto b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/health_check_response_pb.proto new file mode 100644 index 00000000000..2a4fc3b9920 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/health_check_response_pb.proto @@ -0,0 +1,24 @@ +/* + Hyperledger Cactus API + + Interact with a Cactus deployment through HTTP. + + The version of the OpenAPI document: 0.0.1 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package org.hyperledger.cactus.cmd_api_server; + +import "models/memory_usage_pb.proto"; + +message HealthCheckResponsePB { + bool success = 256557056; + + string createdAt = 61500732; + + MemoryUsagePB memoryUsage = 335792418; + +} diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/memory_usage_pb.proto b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/memory_usage_pb.proto new file mode 100644 index 00000000000..0124db27e7c --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/memory_usage_pb.proto @@ -0,0 +1,27 @@ +/* + Hyperledger Cactus API + + Interact with a Cactus deployment through HTTP. + + The version of the OpenAPI document: 0.0.1 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package org.hyperledger.cactus.cmd_api_server; + + +message MemoryUsagePB { + float rss = 113234; + + float heapTotal = 114487480; + + float heapUsed = 30910521; + + float external = 210148408; + + float arrayBuffers = 116952168; + +} diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/watch_healthcheck_v1_pb.proto b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/watch_healthcheck_v1_pb.proto new file mode 100644 index 00000000000..51fc928a969 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/models/watch_healthcheck_v1_pb.proto @@ -0,0 +1,17 @@ +/* + Hyperledger Cactus API + + Interact with a Cactus deployment through HTTP. + + The version of the OpenAPI document: 0.0.1 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package org.hyperledger.cactus.cmd_api_server; + + +message WatchHealthcheckV1PB { +} diff --git a/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto new file mode 100644 index 00000000000..59155057a70 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/proto/generated/openapi/services/default_service.proto @@ -0,0 +1,28 @@ +/* + Hyperledger Cactus API + + Interact with a Cactus deployment through HTTP. + + The version of the OpenAPI document: 0.0.1 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package org.hyperledger.cactus.cmd_api_server; + +import "google/protobuf/empty.proto"; +import "models/health_check_response_pb.proto"; + +service DefaultService { + rpc GetHealthCheckV1 (google.protobuf.Empty) returns (HealthCheckResponsePB); + + rpc GetPrometheusMetricsV1 (google.protobuf.Empty) returns (GetPrometheusMetricsV1Response); + +} + +message GetPrometheusMetricsV1Response { + string data = 1; +} + diff --git a/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts b/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts index ec25467f3b9..6bcf79f9cfb 100644 --- a/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts +++ b/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts @@ -5,10 +5,13 @@ import path from "path"; import tls from "tls"; import { Server, createServer } from "http"; import { createServer as createSecureServer } from "https"; +import { RuntimeError } from "run-time-error"; import { gte } from "semver"; import lmify from "lmify"; import fs from "fs-extra"; import expressHttpProxy from "express-http-proxy"; +import { Server as GrpcServer } from "@grpc/grpc-js"; +import { ServerCredentials as GrpcServerCredentials } from "@grpc/grpc-js"; import type { Application, Request, Response, RequestHandler } from "express"; import express from "express"; import { OpenApiValidator } from "express-openapi-validator"; @@ -43,16 +46,20 @@ import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; import { AuthorizerFactory } from "./authzn/authorizer-factory"; import { WatchHealthcheckV1 } from "./generated/openapi/typescript-axios"; import { WatchHealthcheckV1Endpoint } from "./web-services/watch-healthcheck-v1-endpoint"; -import { RuntimeError } from "run-time-error"; +import * as default_service from "./generated/proto/protoc-gen-ts/services/default_service"; +import { GrpcServerApiServer } from "./web-services/grpc/grpc-server-api-server"; +import { determineAddressFamily } from "./common/determine-address-family"; + export interface IApiServerConstructorOptions { - pluginManagerOptions?: { pluginsPath: string }; - pluginRegistry?: PluginRegistry; - httpServerApi?: Server | SecureServer; - wsServerApi?: SocketIoServer; - wsOptions?: SocketIoServerOptions; - httpServerCockpit?: Server | SecureServer; - config: ICactusApiServerOptions; - prometheusExporter?: PrometheusExporter; + readonly pluginManagerOptions?: { pluginsPath: string }; + readonly pluginRegistry?: PluginRegistry; + readonly httpServerApi?: Server | SecureServer; + readonly wsServerApi?: SocketIoServer; + readonly grpcServer?: GrpcServer; + readonly wsOptions?: SocketIoServerOptions; + readonly httpServerCockpit?: Server | SecureServer; + readonly config: ICactusApiServerOptions; + readonly prometheusExporter?: PrometheusExporter; } export class ApiServer { @@ -73,6 +80,7 @@ export class ApiServer { private readonly httpServerApi: Server | SecureServer; private readonly httpServerCockpit: Server | SecureServer; private readonly wsApi: SocketIoServer; + private readonly grpcServer: GrpcServer; private readonly expressApi: Application; private readonly expressCockpit: Application; private readonly pluginsPath: string; @@ -114,6 +122,7 @@ export class ApiServer { this.httpServerCockpit = createServer(); } + this.grpcServer = this.options.grpcServer || new GrpcServer({}); this.wsApi = new SocketIoServer(); this.expressApi = express(); this.expressCockpit = express(); @@ -169,6 +178,7 @@ export class ApiServer { async start(): Promise<{ addressInfoCockpit: AddressInfo; addressInfoApi: AddressInfo; + addressInfoGrpc: AddressInfo; }> { this.checkNodeVersion(); const tlsMaxVersion = this.options.config.tlsDefaultMaxVersion; @@ -179,6 +189,13 @@ export class ApiServer { const { cockpitTlsEnabled, apiTlsEnabled } = this.options.config; const addressInfoCockpit = await this.startCockpitFileServer(); const addressInfoApi = await this.startApiServer(); + const addressInfoGrpc = await this.startGrpcServer(); + + { + const { port, address } = addressInfoGrpc; + const grpcUrl = `${address}:${port}`; + this.log.info(`Cactus gRPC reachable ${grpcUrl}`); + } { const { apiHost: host } = this.options.config; @@ -196,7 +213,7 @@ export class ApiServer { this.log.info(`Cactus Cockpit reachable ${httpUrl}`); } - return { addressInfoCockpit, addressInfoApi }; + return { addressInfoCockpit, addressInfoApi, addressInfoGrpc }; } catch (ex) { const errorMessage = `Failed to start ApiServer: ${ex.stack}`; this.log.error(errorMessage); @@ -368,6 +385,21 @@ export class ApiServer { await Servers.shutdown(this.httpServerCockpit); this.log.info(`Close HTTP server of the cockpit OK`); } + + if (this.grpcServer) { + this.log.info(`Closing gRPC server ...`); + await new Promise((resolve, reject) => { + this.grpcServer.tryShutdown((ex?: Error) => { + if (ex) { + this.log.error("Failed to shut down gRPC server: ", ex); + reject(ex); + } else { + resolve(); + } + }); + }); + this.log.info(`Close gRPC server OK`); + } } async startCockpitFileServer(): Promise { @@ -501,6 +533,45 @@ export class ApiServer { ); } + async startGrpcServer(): Promise { + return new Promise((resolve, reject) => { + // const grpcHost = "0.0.0.0"; // FIXME - make this configurable (config-service.ts) + const grpcHost = "127.0.0.1"; // FIXME - make this configurable (config-service.ts) + const grpcHostAndPort = `${grpcHost}:${this.options.config.grpcPort}`; + + const grpcTlsCredentials = this.options.config.grpcMtlsEnabled + ? GrpcServerCredentials.createSsl( + Buffer.from(this.options.config.apiTlsCertPem), + [ + { + cert_chain: Buffer.from(this.options.config.apiTlsCertPem), + private_key: Buffer.from(this.options.config.apiTlsKeyPem), + }, + ], + true, + ) + : GrpcServerCredentials.createInsecure(); + + this.grpcServer.bindAsync( + grpcHostAndPort, + grpcTlsCredentials, + (error: Error | null, port: number) => { + if (error) { + return reject(new RuntimeError("Binding gRPC failed: ", error)); + } + this.grpcServer.addService( + default_service.org.hyperledger.cactus.cmd_api_server + .UnimplementedDefaultServiceService.definition, + new GrpcServerApiServer(), + ); + this.grpcServer.start(); + const family = determineAddressFamily(grpcHost); + resolve({ address: grpcHost, port, family }); + }, + ); + }); + } + async startApiServer(): Promise { const { options, expressApi: app, wsApi } = this; const { config } = options; diff --git a/packages/cactus-cmd-api-server/src/main/typescript/common/determine-address-family.ts b/packages/cactus-cmd-api-server/src/main/typescript/common/determine-address-family.ts new file mode 100644 index 00000000000..12a6cc857df --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/common/determine-address-family.ts @@ -0,0 +1,13 @@ +import { isIPv4, isIPv6 } from "net"; + +export function determineAddressFamily( + address: string, +): "IPv4" | "IPv6" | "IPvUnknown" { + if (isIPv4(address)) { + return "IPv4"; + } else if (isIPv6(address)) { + return "IPv6"; + } else { + return "IPvUnknown"; + } +} diff --git a/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts b/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts index 25a6e632bfc..a37692f1b5e 100644 --- a/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts +++ b/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts @@ -52,6 +52,8 @@ export interface ICactusApiServerOptions { apiTlsCertPem: string; apiTlsKeyPem: string; apiTlsClientCaPem: string; + grpcPort: number; + grpcMtlsEnabled: boolean; plugins: PluginImport[]; keyPairPem: string; keychainSuffixKeyPairPem: string; @@ -357,6 +359,22 @@ export class ConfigService { arg: "api-tls-key-pem", default: null as string | null, }, + grpcPort: { + doc: "The gRPC port to serve web services on.", + format: "port", + env: "GRPC_PORT", + arg: "grpc-port", + default: 5000, + }, + grpcMtlsEnabled: { + doc: + "Enable TLS termination on the grpc server. Useful if you do not have/want to " + + "have a reverse proxy or load balancer doing the SSL/TLS termination in your environment.", + format: Boolean, + env: "GRPC_TLS_ENABLED", + arg: "grpc-tls-enabled", + default: true, + }, keyPairPem: { sensitive: true, doc: @@ -445,6 +463,8 @@ export class ConfigService { const apiPort = (schema.apiPort as SchemaObj).default; const apiProtocol = apiTlsEnabled ? "https:" : "http"; const apiBaseUrl = `${apiProtocol}//${apiHost}:${apiPort}`; + const grpcPort = (schema.grpcPort as SchemaObj).default; + const grpcMtlsEnabled = (schema.grpcMtlsEnabled as SchemaObj).default; const keyPair = JWK.generateSync("EC", "secp256k1", { use: "sig" }, true); const keyPairPem = keyPair.toPEM(true); @@ -545,6 +565,8 @@ export class ConfigService { apiTlsCertPem: pkiServer.certificatePem, apiTlsKeyPem: pkiServer.privateKeyPem, apiTlsClientCaPem: "-", // API mTLS is off so this will not crash the server + grpcPort, + grpcMtlsEnabled, cockpitHost, cockpitPort, cockpitWwwRoot: (schema.cockpitWwwRoot as SchemaObj).default, diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/google/protobuf/empty.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/google/protobuf/empty.ts new file mode 100644 index 00000000000..b7e69f9aaac --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/google/protobuf/empty.ts @@ -0,0 +1,45 @@ +/** + * Generated by the protoc-gen-ts. DO NOT EDIT! + * compiler version: 3.15.6 + * source: google/protobuf/empty.proto + * git: https://github.com/thesayyn/protoc-gen-ts + * buymeacoffee: https://www.buymeacoffee.com/thesayyn + * */ +import * as pb_1 from "google-protobuf"; +export namespace google.protobuf { + export class Empty extends pb_1.Message { + constructor(data?: any[] | {}) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], []); + if (!Array.isArray(data) && typeof data == "object") { } + } + toObject() { + const data: {} = {}; + return data; + } + serialize(): Uint8Array; + serialize(w: pb_1.BinaryWriter): void; + serialize(w?: pb_1.BinaryWriter): Uint8Array | void { + const writer = w || new pb_1.BinaryWriter(); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Empty { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Empty(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): Empty { + return Empty.deserialize(bytes); + } + } +} diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/health_check_response_pb.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/health_check_response_pb.ts new file mode 100644 index 00000000000..14065d3222c --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/health_check_response_pb.ts @@ -0,0 +1,106 @@ +/** + * Generated by the protoc-gen-ts. DO NOT EDIT! + * compiler version: 3.15.6 + * source: models/health_check_response_pb.proto + * git: https://github.com/thesayyn/protoc-gen-ts + * buymeacoffee: https://www.buymeacoffee.com/thesayyn + * */ +import * as dependency_1 from "./memory_usage_pb"; +import * as pb_1 from "google-protobuf"; +export namespace org.hyperledger.cactus.cmd_api_server { + export class HealthCheckResponsePB extends pb_1.Message { + constructor(data?: any[] | { + success?: boolean; + createdAt?: string; + memoryUsage?: dependency_1.org.hyperledger.cactus.cmd_api_server.MemoryUsagePB; + }) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], []); + if (!Array.isArray(data) && typeof data == "object") { + if ("success" in data && data.success != undefined) { + this.success = data.success; + } + if ("createdAt" in data && data.createdAt != undefined) { + this.createdAt = data.createdAt; + } + if ("memoryUsage" in data && data.memoryUsage != undefined) { + this.memoryUsage = data.memoryUsage; + } + } + } + get success() { + return pb_1.Message.getField(this, 256557056) as boolean; + } + set success(value: boolean) { + pb_1.Message.setField(this, 256557056, value); + } + get createdAt() { + return pb_1.Message.getField(this, 61500732) as string; + } + set createdAt(value: string) { + pb_1.Message.setField(this, 61500732, value); + } + get memoryUsage() { + return pb_1.Message.getWrapperField(this, dependency_1.org.hyperledger.cactus.cmd_api_server.MemoryUsagePB, 335792418) as dependency_1.org.hyperledger.cactus.cmd_api_server.MemoryUsagePB; + } + set memoryUsage(value: dependency_1.org.hyperledger.cactus.cmd_api_server.MemoryUsagePB) { + pb_1.Message.setWrapperField(this, 335792418, value); + } + toObject() { + const data: { + success?: boolean; + createdAt?: string; + memoryUsage?: ReturnType; + } = {}; + if (this.success != null) { + data.success = this.success; + } + if (this.createdAt != null) { + data.createdAt = this.createdAt; + } + if (this.memoryUsage != null) { + data.memoryUsage = this.memoryUsage.toObject(); + } + return data; + } + serialize(): Uint8Array; + serialize(w: pb_1.BinaryWriter): void; + serialize(w?: pb_1.BinaryWriter): Uint8Array | void { + const writer = w || new pb_1.BinaryWriter(); + if (this.success !== undefined) + writer.writeBool(256557056, this.success); + if (typeof this.createdAt === "string" && this.createdAt.length) + writer.writeString(61500732, this.createdAt); + if (this.memoryUsage !== undefined) + writer.writeMessage(335792418, this.memoryUsage, () => this.memoryUsage.serialize(writer)); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): HealthCheckResponsePB { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new HealthCheckResponsePB(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + case 256557056: + message.success = reader.readBool(); + break; + case 61500732: + message.createdAt = reader.readString(); + break; + case 335792418: + reader.readMessage(message.memoryUsage, () => message.memoryUsage = dependency_1.org.hyperledger.cactus.cmd_api_server.MemoryUsagePB.deserialize(reader)); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): HealthCheckResponsePB { + return HealthCheckResponsePB.deserialize(bytes); + } + } +} diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/memory_usage_pb.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/memory_usage_pb.ts new file mode 100644 index 00000000000..ee17b4c3718 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/models/memory_usage_pb.ts @@ -0,0 +1,143 @@ +/** + * Generated by the protoc-gen-ts. DO NOT EDIT! + * compiler version: 3.15.6 + * source: models/memory_usage_pb.proto + * git: https://github.com/thesayyn/protoc-gen-ts + * buymeacoffee: https://www.buymeacoffee.com/thesayyn + * */ +import * as pb_1 from "google-protobuf"; +export namespace org.hyperledger.cactus.cmd_api_server { + export class MemoryUsagePB extends pb_1.Message { + constructor(data?: any[] | { + rss?: number; + heapTotal?: number; + heapUsed?: number; + external?: number; + arrayBuffers?: number; + }) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], []); + if (!Array.isArray(data) && typeof data == "object") { + if ("rss" in data && data.rss != undefined) { + this.rss = data.rss; + } + if ("heapTotal" in data && data.heapTotal != undefined) { + this.heapTotal = data.heapTotal; + } + if ("heapUsed" in data && data.heapUsed != undefined) { + this.heapUsed = data.heapUsed; + } + if ("external" in data && data.external != undefined) { + this.external = data.external; + } + if ("arrayBuffers" in data && data.arrayBuffers != undefined) { + this.arrayBuffers = data.arrayBuffers; + } + } + } + get rss() { + return pb_1.Message.getField(this, 113234) as number; + } + set rss(value: number) { + pb_1.Message.setField(this, 113234, value); + } + get heapTotal() { + return pb_1.Message.getField(this, 114487480) as number; + } + set heapTotal(value: number) { + pb_1.Message.setField(this, 114487480, value); + } + get heapUsed() { + return pb_1.Message.getField(this, 30910521) as number; + } + set heapUsed(value: number) { + pb_1.Message.setField(this, 30910521, value); + } + get external() { + return pb_1.Message.getField(this, 210148408) as number; + } + set external(value: number) { + pb_1.Message.setField(this, 210148408, value); + } + get arrayBuffers() { + return pb_1.Message.getField(this, 116952168) as number; + } + set arrayBuffers(value: number) { + pb_1.Message.setField(this, 116952168, value); + } + toObject() { + const data: { + rss?: number; + heapTotal?: number; + heapUsed?: number; + external?: number; + arrayBuffers?: number; + } = {}; + if (this.rss != null) { + data.rss = this.rss; + } + if (this.heapTotal != null) { + data.heapTotal = this.heapTotal; + } + if (this.heapUsed != null) { + data.heapUsed = this.heapUsed; + } + if (this.external != null) { + data.external = this.external; + } + if (this.arrayBuffers != null) { + data.arrayBuffers = this.arrayBuffers; + } + return data; + } + serialize(): Uint8Array; + serialize(w: pb_1.BinaryWriter): void; + serialize(w?: pb_1.BinaryWriter): Uint8Array | void { + const writer = w || new pb_1.BinaryWriter(); + if (this.rss !== undefined) + writer.writeFloat(113234, this.rss); + if (this.heapTotal !== undefined) + writer.writeFloat(114487480, this.heapTotal); + if (this.heapUsed !== undefined) + writer.writeFloat(30910521, this.heapUsed); + if (this.external !== undefined) + writer.writeFloat(210148408, this.external); + if (this.arrayBuffers !== undefined) + writer.writeFloat(116952168, this.arrayBuffers); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): MemoryUsagePB { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new MemoryUsagePB(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + case 113234: + message.rss = reader.readFloat(); + break; + case 114487480: + message.heapTotal = reader.readFloat(); + break; + case 30910521: + message.heapUsed = reader.readFloat(); + break; + case 210148408: + message.external = reader.readFloat(); + break; + case 116952168: + message.arrayBuffers = reader.readFloat(); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): MemoryUsagePB { + return MemoryUsagePB.deserialize(bytes); + } + } +} diff --git a/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts new file mode 100644 index 00000000000..de628e92b3b --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/generated/proto/protoc-gen-ts/services/default_service.ts @@ -0,0 +1,114 @@ +/** + * Generated by the protoc-gen-ts. DO NOT EDIT! + * compiler version: 3.15.6 + * source: services/default_service.proto + * git: https://github.com/thesayyn/protoc-gen-ts + * buymeacoffee: https://www.buymeacoffee.com/thesayyn + * */ +import * as dependency_1 from "./../google/protobuf/empty"; +import * as dependency_2 from "./../models/health_check_response_pb"; +import * as pb_1 from "google-protobuf"; +import * as grpc_1 from "@grpc/grpc-js"; +export namespace org.hyperledger.cactus.cmd_api_server { + export class GetPrometheusMetricsV1Response extends pb_1.Message { + constructor(data?: any[] | { + data?: string; + }) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], []); + if (!Array.isArray(data) && typeof data == "object") { + if ("data" in data && data.data != undefined) { + this.data = data.data; + } + } + } + get data() { + return pb_1.Message.getField(this, 1) as string; + } + set data(value: string) { + pb_1.Message.setField(this, 1, value); + } + toObject() { + const data: { + data?: string; + } = {}; + if (this.data != null) { + data.data = this.data; + } + return data; + } + serialize(): Uint8Array; + serialize(w: pb_1.BinaryWriter): void; + serialize(w?: pb_1.BinaryWriter): Uint8Array | void { + const writer = w || new pb_1.BinaryWriter(); + if (typeof this.data === "string" && this.data.length) + writer.writeString(1, this.data); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): GetPrometheusMetricsV1Response { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new GetPrometheusMetricsV1Response(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + case 1: + message.data = reader.readString(); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): GetPrometheusMetricsV1Response { + return GetPrometheusMetricsV1Response.deserialize(bytes); + } + } + export abstract class UnimplementedDefaultServiceService { + static definition = { + GetHealthCheckV1: { + path: "/org.hyperledger.cactus.cmd_api_server.DefaultService/GetHealthCheckV1", + requestStream: false, + responseStream: false, + requestSerialize: (message: dependency_1.google.protobuf.Empty) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => dependency_1.google.protobuf.Empty.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: dependency_2.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => dependency_2.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB.deserialize(new Uint8Array(bytes)) + }, + GetPrometheusMetricsV1: { + path: "/org.hyperledger.cactus.cmd_api_server.DefaultService/GetPrometheusMetricsV1", + requestStream: false, + responseStream: false, + requestSerialize: (message: dependency_1.google.protobuf.Empty) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => dependency_1.google.protobuf.Empty.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: GetPrometheusMetricsV1Response) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => GetPrometheusMetricsV1Response.deserialize(new Uint8Array(bytes)) + } + }; + [method: string]: grpc_1.UntypedHandleCall; + abstract GetHealthCheckV1(call: grpc_1.ServerUnaryCall, callback: grpc_1.requestCallback): void; + abstract GetPrometheusMetricsV1(call: grpc_1.ServerUnaryCall, callback: grpc_1.requestCallback): void; + } + export class DefaultServiceClient extends grpc_1.makeGenericClientConstructor(UnimplementedDefaultServiceService.definition, "DefaultService", {}) { + constructor(address: string, credentials: grpc_1.ChannelCredentials, options?: Partial) { + super(address, credentials, options) + } + GetHealthCheckV1(message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata, options: grpc_1.CallOptions, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetHealthCheckV1(message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetHealthCheckV1(message: dependency_1.google.protobuf.Empty, options: grpc_1.CallOptions, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetHealthCheckV1(message: dependency_1.google.protobuf.Empty, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetHealthCheckV1(message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall { + return super.GetHealthCheckV1(message, metadata, options, callback); + } + GetPrometheusMetricsV1(message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata, options: grpc_1.CallOptions, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetPrometheusMetricsV1(message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetPrometheusMetricsV1(message: dependency_1.google.protobuf.Empty, options: grpc_1.CallOptions, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetPrometheusMetricsV1(message: dependency_1.google.protobuf.Empty, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; + GetPrometheusMetricsV1(message: dependency_1.google.protobuf.Empty, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall { + return super.GetPrometheusMetricsV1(message, metadata, options, callback); + } + } +} diff --git a/packages/cactus-cmd-api-server/src/main/typescript/proto.d.ts b/packages/cactus-cmd-api-server/src/main/typescript/proto.d.ts new file mode 100644 index 00000000000..770637e29e1 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/proto.d.ts @@ -0,0 +1,5 @@ + +declare module '*.proto' { + const fileContent: string; + export default fileContent; +} diff --git a/packages/cactus-cmd-api-server/src/main/typescript/public-api.ts b/packages/cactus-cmd-api-server/src/main/typescript/public-api.ts index f2fdad000b1..34a44578a16 100755 --- a/packages/cactus-cmd-api-server/src/main/typescript/public-api.ts +++ b/packages/cactus-cmd-api-server/src/main/typescript/public-api.ts @@ -16,6 +16,13 @@ export { IPki, } from "./config/self-signed-pki-generator"; +// gRPC - generated models and client +export * as default_service from "./generated/proto/protoc-gen-ts/services/default_service"; +export * as health_check_response_pb from "./generated/proto/protoc-gen-ts/models/health_check_response_pb"; +export * as memory_usage_pb from "./generated/proto/protoc-gen-ts/models/memory_usage_pb"; +export * as empty from "./generated/proto/protoc-gen-ts/google/protobuf/empty"; + +// HTTP - generated models and client export * from "./generated/openapi/typescript-axios/index"; export { ApiServerApiClient } from "./api-client/api-server-api-client"; diff --git a/packages/cactus-cmd-api-server/src/main/typescript/web-services/grpc/grpc-server-api-server.ts b/packages/cactus-cmd-api-server/src/main/typescript/web-services/grpc/grpc-server-api-server.ts new file mode 100644 index 00000000000..b6ae3f2063a --- /dev/null +++ b/packages/cactus-cmd-api-server/src/main/typescript/web-services/grpc/grpc-server-api-server.ts @@ -0,0 +1,45 @@ +import { ServerUnaryCall, requestCallback } from "@grpc/grpc-js"; +import { Empty } from "google-protobuf/google/protobuf/empty_pb"; + +import * as health_check_response_pb from "../../generated/proto/protoc-gen-ts/models/health_check_response_pb"; +import * as memory_usage_pb from "../../generated/proto/protoc-gen-ts/models/memory_usage_pb"; +import * as default_service from "../../generated/proto/protoc-gen-ts/services/default_service"; + +export class GrpcServerApiServer extends default_service.org.hyperledger.cactus + .cmd_api_server.UnimplementedDefaultServiceService { + GetHealthCheckV1( + call: ServerUnaryCall< + Empty, + health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB + >, + callback: requestCallback< + health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB + >, + ): void { + const memoryUsage = new memory_usage_pb.org.hyperledger.cactus.cmd_api_server.MemoryUsagePB( + process.memoryUsage(), + ); + + const healthCheckResponse = new health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB( + { + success: true, + createdAt: new Date().toJSON(), + memoryUsage, + }, + ); + callback(null, healthCheckResponse); + } + + GetPrometheusMetricsV1( + call: ServerUnaryCall< + Empty, + default_service.org.hyperledger.cactus.cmd_api_server.GetPrometheusMetricsV1Response + >, + callback: requestCallback< + default_service.org.hyperledger.cactus.cmd_api_server.GetPrometheusMetricsV1Response + >, + ): void { + const res = new default_service.org.hyperledger.cactus.cmd_api_server.GetPrometheusMetricsV1Response(); + callback(null, res); + } +} diff --git a/packages/cactus-cmd-api-server/src/test/typescript/benchmark/artillery-api-benchmark.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/benchmark/artillery-api-benchmark.test.ts index 16e4393ab0c..ad1273b04dc 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/benchmark/artillery-api-benchmark.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/benchmark/artillery-api-benchmark.test.ts @@ -64,6 +64,7 @@ test("Start API server, and run Artillery benchmark test.", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 4000; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.logLevel = "info"; apiServerOptions.plugins = [ diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts index 1bafbf5cd23..8d426b95e6b 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authorization.test.ts @@ -82,6 +82,7 @@ test(testCase, async (t: Test) => { apiSrvOpts.apiCorsDomainCsv = "*"; apiSrvOpts.apiPort = 0; apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcPort = 0; apiSrvOpts.apiTlsEnabled = false; apiSrvOpts.plugins = [ { diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authz-scope-enforcement.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authz-scope-enforcement.test.ts index 539e143765e..1bb8aa70ce8 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authz-scope-enforcement.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-endpoint-authz-scope-enforcement.test.ts @@ -67,6 +67,7 @@ test(testCase, async (t: Test) => { apiSrvOpts.apiCorsDomainCsv = "*"; apiSrvOpts.apiPort = 0; apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcPort = 0; apiSrvOpts.apiTlsEnabled = false; apiSrvOpts.plugins = []; const config = configService.newExampleConfigConvict(apiSrvOpts); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts index 140e0c1c87e..bbc3dc2c619 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-socketio-endpoint-authorization.test.ts @@ -55,6 +55,7 @@ test(testCase, async (t: Test) => { apiSrvOpts.apiCorsDomainCsv = "*"; apiSrvOpts.apiPort = 0; apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcPort = 0; apiSrvOpts.apiTlsEnabled = false; apiSrvOpts.plugins = []; const config = configService.newExampleConfigConvict(apiSrvOpts); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz-ops-confirm.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz-ops-confirm.test.ts index 1bebde1a90c..bf1ab283d17 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz-ops-confirm.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz-ops-confirm.test.ts @@ -57,6 +57,7 @@ test(testCase, async (t: Test) => { apiSrvOpts.apiCorsDomainCsv = "*"; apiSrvOpts.apiPort = 0; apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcPort = 0; apiSrvOpts.apiTlsEnabled = false; apiSrvOpts.plugins = []; const config = configService.newExampleConfigConvict(apiSrvOpts); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz.test.ts index 190d44bf407..e7cc4259121 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/jwt-unprotected-endpoint-authz.test.ts @@ -65,6 +65,7 @@ test(testCase, async (t: Test) => { apiSrvOpts.apiCorsDomainCsv = "*"; apiSrvOpts.apiPort = 0; apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcPort = 0; apiSrvOpts.apiTlsEnabled = false; apiSrvOpts.plugins = []; const config = configService.newExampleConfigConvict(apiSrvOpts); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts index cf7935ead7d..8cf1d41ee20 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts @@ -74,6 +74,7 @@ test("NodeJS API server + Rust plugin work together", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 0; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = [ { diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-js-proto-loader-client-healthcheck.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-js-proto-loader-client-healthcheck.test.ts new file mode 100644 index 00000000000..efd15b8f60f --- /dev/null +++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-js-proto-loader-client-healthcheck.test.ts @@ -0,0 +1,105 @@ +import test, { Test } from "tape-promise/tape"; +import path from "path"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import { + ApiServer, + ConfigService, + HealthCheckResponse, +} from "../../../main/typescript/public-api"; +import { AuthorizationProtocol } from "../../../main/typescript/public-api"; +import { ServiceClientConstructor } from "@grpc/grpc-js/build/src/make-client"; +import * as grpc from "@grpc/grpc-js"; +import * as protoLoader from "@grpc/proto-loader"; +import { Empty } from "google-protobuf/google/protobuf/empty_pb"; + +const testCase = "API server: runs gRPC web services - proto loader"; +const logLevel: LogLevelDesc = "TRACE"; + +test(testCase, async (t: Test) => { + const configService = new ConfigService(); + const apiSrvOpts = configService.newExampleConfig(); + apiSrvOpts.authorizationProtocol = AuthorizationProtocol.NONE; + apiSrvOpts.configFile = ""; + apiSrvOpts.logLevel = logLevel; + apiSrvOpts.apiCorsDomainCsv = "*"; + apiSrvOpts.apiPort = 0; + apiSrvOpts.grpcPort = 0; + apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcMtlsEnabled = false; + apiSrvOpts.apiTlsEnabled = false; + apiSrvOpts.plugins = []; + const config = configService.newExampleConfigConvict(apiSrvOpts); + + const apiServer = new ApiServer({ + config: config.getProperties(), + }); + test.onFinish(async () => await apiServer.shutdown()); + + const startResponse = apiServer.start(); + await t.doesNotReject(startResponse, "start API server OK"); + t.ok(startResponse, "startResponse truthy OK"); + + const addressInfoApi = (await startResponse).addressInfoGrpc; + const { address, port } = addressInfoApi; + const grpcHostAndPort = `${address}:${port}`; + t.ok(grpcHostAndPort, "grpcHostAndPort truthy OK"); + + const PROTO_PATH = path.join( + __dirname, + "../../../main/proto/generated/openapi/services/default_service.proto", + ); + + const PROTO_INCLUDE_DIR = path.join( + __dirname, + "../../../main/proto/generated/openapi/", + ); + + const packageDefinition = await protoLoader.load(PROTO_PATH, { + includeDirs: [PROTO_INCLUDE_DIR], + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const grpcPkg = grpc.loadPackageDefinition(packageDefinition); + t.ok(grpcPkg, "grpcPkg truthy OK"); + + const DefaultService: ServiceClientConstructor = (grpcPkg as any).org + .hyperledger.cactus.cmd_api_server.DefaultService; + + t.ok(DefaultService, "DefaultService truthy OK"); + + const client = new DefaultService( + grpcHostAndPort, + grpc.credentials.createInsecure(), + ); + t.ok(client, "proto loaded client truthy OK"); + + const request = new Empty(); + + const res1 = await new Promise((resolve, reject) => { + client.getHealthCheckV1( + request, + (err: grpc.ServiceError | null, value: HealthCheckResponse) => { + if (err) { + reject(err); + } else { + resolve(value); + } + }, + ); + }); + t.ok(res1, "res1 truthy OK"); + t.ok(res1.createdAt, "res1.createdAt truthy OK"); + t.ok(res1.memoryUsage, "res1.memoryUsage truthy OK"); + t.ok(res1.memoryUsage.heapTotal, "res1.memoryUsage.heapTotal truthy OK"); + t.ok(res1.memoryUsage.heapUsed, "res1.memoryUsage.heapUsed truthy OK"); + t.ok(res1.memoryUsage.rss, "res1.memoryUsage.rss truthy OK"); + t.true(res1.success, "res1.success true OK"); + + t.end(); +}); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-healthcheck.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-healthcheck.test.ts new file mode 100644 index 00000000000..65f5da21336 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-healthcheck.test.ts @@ -0,0 +1,85 @@ +import test, { Test } from "tape-promise/tape"; + +import * as grpc from "@grpc/grpc-js"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import { ApiServer, ConfigService } from "../../../main/typescript/public-api"; +import { AuthorizationProtocol } from "../../../main/typescript/public-api"; +import { default_service } from "../../../main/typescript/public-api"; +import { health_check_response_pb } from "../../../main/typescript/public-api"; +import { empty } from "../../../main/typescript/public-api"; +import { RuntimeError } from "run-time-error"; + +const testCase = "API server: runs gRPC TS-proto web services"; +const logLevel: LogLevelDesc = "TRACE"; + +test(testCase, async (t: Test) => { + const configService = new ConfigService(); + const apiSrvOpts = configService.newExampleConfig(); + apiSrvOpts.authorizationProtocol = AuthorizationProtocol.NONE; + apiSrvOpts.configFile = ""; + apiSrvOpts.logLevel = logLevel; + apiSrvOpts.apiCorsDomainCsv = "*"; + apiSrvOpts.apiPort = 0; + apiSrvOpts.grpcPort = 0; + apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcMtlsEnabled = false; + apiSrvOpts.apiTlsEnabled = false; + apiSrvOpts.plugins = []; + const config = configService.newExampleConfigConvict(apiSrvOpts); + + const apiServer = new ApiServer({ + config: config.getProperties(), + }); + test.onFinish(async () => await apiServer.shutdown()); + + const startResponse = apiServer.start(); + await t.doesNotReject( + startResponse, + "failed to start API server with dynamic plugin imports configured for it...", + ); + t.ok(startResponse, "startResponse truthy OK"); + + const addressInfoGrpc = (await startResponse).addressInfoGrpc; + const { address, port } = addressInfoGrpc; + const grpcHostAndPort = `${address}:${port}`; + + const apiClient = new default_service.org.hyperledger.cactus.cmd_api_server.DefaultServiceClient( + grpcHostAndPort, + grpc.credentials.createInsecure(), + ); + t.ok(apiClient, "apiClient truthy OK"); + + const responsePromise = new Promise< + health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB + >((resolve, reject) => { + apiClient.GetHealthCheckV1( + new empty.google.protobuf.Empty(), + ( + error: grpc.ServiceError | null, + response?: health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB, + ) => { + if (error) { + reject(error); + } else if (response) { + resolve(response); + } else { + throw new RuntimeError("No error, nor response received."); + } + }, + ); + }); + + await t.doesNotReject(responsePromise, "No error in healthcheck OK"); + const res = await responsePromise; + + const resHc = res?.toObject(); + + t.ok(resHc, `healthcheck response truthy OK`); + t.ok(resHc?.createdAt, `resHc.createdAt truthy OK`); + t.ok(resHc?.memoryUsage, `resHc.memoryUsage truthy OK`); + t.ok(resHc?.memoryUsage?.rss, `resHc.memoryUsage.rss truthy OK`); + t.ok(resHc?.success, `resHc.success truthy OK`); + t.end(); +}); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-m-tls-enabled.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-m-tls-enabled.test.ts new file mode 100644 index 00000000000..7183497ea97 --- /dev/null +++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/grpc-proto-gen-ts-client-m-tls-enabled.test.ts @@ -0,0 +1,98 @@ +import test, { Test } from "tape-promise/tape"; + +import * as grpc from "@grpc/grpc-js"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import { ApiServer, ConfigService } from "../../../main/typescript/public-api"; +import { SelfSignedPkiGenerator } from "../../../main/typescript/public-api"; +import { AuthorizationProtocol } from "../../../main/typescript/public-api"; +import { default_service } from "../../../main/typescript/public-api"; +import { health_check_response_pb } from "../../../main/typescript/public-api"; +import { empty } from "../../../main/typescript/public-api"; +import { RuntimeError } from "run-time-error"; + +const testCase = "API server: runs gRPC web services - mTLS"; +const logLevel: LogLevelDesc = "TRACE"; + +test(testCase, async (t: Test) => { + const generator = new SelfSignedPkiGenerator(); + t.ok(generator, "Instantiated SelfSignedCertificateGenerator OK."); + + const serverCert = generator.create("localhost"); + const clientCert = generator.create("client.localhost", serverCert); + const serverRootCertPemBuf = Buffer.from(serverCert.certificatePem); + + const configService = new ConfigService(); + const apiSrvOpts = configService.newExampleConfig(); + apiSrvOpts.authorizationProtocol = AuthorizationProtocol.NONE; + apiSrvOpts.configFile = ""; + apiSrvOpts.logLevel = logLevel; + apiSrvOpts.apiCorsDomainCsv = "*"; + apiSrvOpts.apiPort = 0; + apiSrvOpts.apiTlsCertPem = serverCert.certificatePem; + apiSrvOpts.apiTlsKeyPem = serverCert.privateKeyPem; + apiSrvOpts.apiTlsClientCaPem = clientCert.certificatePem; + apiSrvOpts.grpcPort = 0; + apiSrvOpts.cockpitPort = 0; + apiSrvOpts.grpcMtlsEnabled = true; + apiSrvOpts.apiTlsEnabled = false; + apiSrvOpts.plugins = []; + const config = configService.newExampleConfigConvict(apiSrvOpts); + + const apiServer = new ApiServer({ + config: config.getProperties(), + }); + test.onFinish(async () => await apiServer.shutdown()); + + const startResponse = apiServer.start(); + await t.doesNotReject(startResponse, "API server started OK"); + t.ok(startResponse, "startResponse truthy OK"); + + const addressInfoGrpc = (await startResponse).addressInfoGrpc; + const { address, port } = addressInfoGrpc; + const grpcHostAndPort = `${address}:${port}`; + + const tlsCredentials = grpc.credentials.createSsl( + serverRootCertPemBuf, + Buffer.from(clientCert.privateKeyPem), + Buffer.from(clientCert.certificatePem), + ); + const apiClient = new default_service.org.hyperledger.cactus.cmd_api_server.DefaultServiceClient( + grpcHostAndPort, + tlsCredentials, + ); + t.ok(apiClient, "apiClient truthy OK"); + + const responsePromise = new Promise< + health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB + >((resolve, reject) => { + apiClient.GetHealthCheckV1( + new empty.google.protobuf.Empty(), + ( + error: grpc.ServiceError | null, + response?: health_check_response_pb.org.hyperledger.cactus.cmd_api_server.HealthCheckResponsePB, + ) => { + if (error) { + reject(error); + } else if (response) { + resolve(response); + } else { + throw new RuntimeError("No error, nor response received."); + } + }, + ); + }); + + await t.doesNotReject(responsePromise, "No error in healthcheck OK"); + const res = await responsePromise; + + const resHc = res.toObject(); + + t.ok(resHc, `healthcheck response truthy OK`); + t.ok(resHc.createdAt, `resHc.createdAt truthy OK`); + t.ok(resHc.memoryUsage, `resHc.memoryUsage truthy OK`); + t.ok(resHc.memoryUsage?.rss, `resHc.memoryUsage.rss truthy OK`); + t.ok(resHc.success, `resHc.success truthy OK`); + t.end(); +}); diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-consortium-manual.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-consortium-manual.test.ts index 94cdc17b5a0..e446d691b97 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-consortium-manual.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-consortium-manual.test.ts @@ -59,6 +59,7 @@ test("can install plugin-consortium-manual", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 0; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = [ { diff --git a/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-keychain-memory.test.ts b/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-keychain-memory.test.ts index 10faffb68ea..9eae7e6a1cc 100644 --- a/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-keychain-memory.test.ts +++ b/packages/cactus-cmd-api-server/src/test/typescript/unit/plugins/install-basic-plugin-keychain-memory.test.ts @@ -39,6 +39,7 @@ test("can import plugins at runtime (CLI)", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 0; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = [ { diff --git a/packages/cactus-test-api-client/src/test/typescript/integration/api-client-routing-node-to-node.test.ts b/packages/cactus-test-api-client/src/test/typescript/integration/api-client-routing-node-to-node.test.ts index 97cecd31eb4..9af45c9ddc3 100644 --- a/packages/cactus-test-api-client/src/test/typescript/integration/api-client-routing-node-to-node.test.ts +++ b/packages/cactus-test-api-client/src/test/typescript/integration/api-client-routing-node-to-node.test.ts @@ -184,6 +184,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); @@ -226,6 +227,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo2.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-cmd-api-server/src/test/typescript/integration/plugin-import-with-npm-install.test.ts b/packages/cactus-test-cmd-api-server/src/test/typescript/integration/plugin-import-with-npm-install.test.ts index d5fde643018..3c22eac030b 100644 --- a/packages/cactus-test-cmd-api-server/src/test/typescript/integration/plugin-import-with-npm-install.test.ts +++ b/packages/cactus-test-cmd-api-server/src/test/typescript/integration/plugin-import-with-npm-install.test.ts @@ -47,6 +47,7 @@ test("can instal plugins at runtime based on imports", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 0; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = [ { diff --git a/packages/cactus-test-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts b/packages/cactus-test-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts index d2cf5c9fb27..b596d9a683c 100644 --- a/packages/cactus-test-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts +++ b/packages/cactus-test-cmd-api-server/src/test/typescript/integration/remote-plugin-imports.test.ts @@ -73,6 +73,7 @@ test("NodeJS API server + Rust plugin work together", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 0; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = []; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-cmd-api-server/src/test/typescript/integration/runtime-plugin-imports.test.ts b/packages/cactus-test-cmd-api-server/src/test/typescript/integration/runtime-plugin-imports.test.ts index 6879c9c0f00..7c6e075ec32 100644 --- a/packages/cactus-test-cmd-api-server/src/test/typescript/integration/runtime-plugin-imports.test.ts +++ b/packages/cactus-test-cmd-api-server/src/test/typescript/integration/runtime-plugin-imports.test.ts @@ -30,6 +30,7 @@ test("can import plugins at runtime (CLI)", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = 0; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; apiServerOptions.plugins = [ { diff --git a/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts index af919a71d77..e05254b4bab 100644 --- a/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts +++ b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts @@ -161,6 +161,7 @@ test("member node public keys and hosts are pre-shared", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); @@ -215,6 +216,7 @@ test("member node public keys and hosts are pre-shared", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo2.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); @@ -270,6 +272,7 @@ test("member node public keys and hosts are pre-shared", async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo3.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-balance-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-balance-endpoint.test.ts index c5dd3da4a14..8255b4e650a 100644 --- a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-balance-endpoint.test.ts +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-balance-endpoint.test.ts @@ -105,6 +105,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-block-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-block-endpoint.test.ts index 7ba80ad2a0c..9559029338e 100644 --- a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-block-endpoint.test.ts +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-block-endpoint.test.ts @@ -103,6 +103,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-past-logs-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-past-logs-endpoint.test.ts index cd080ee85a0..2e76012b3c5 100644 --- a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-past-logs-endpoint.test.ts +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-past-logs-endpoint.test.ts @@ -105,6 +105,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-transaction-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-transaction-endpoint.test.ts index 8e43cfb48ba..fbc05a412a8 100644 --- a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-transaction-endpoint.test.ts +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/get-transaction-endpoint.test.ts @@ -105,6 +105,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/sign-transaction-endpoint.test.ts b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/sign-transaction-endpoint.test.ts index 266baff9fb8..8fd57b4ff67 100644 --- a/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/sign-transaction-endpoint.test.ts +++ b/packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/sign-transaction-endpoint.test.ts @@ -114,6 +114,7 @@ test(testCase, async (t: Test) => { apiServerOptions.apiCorsDomainCsv = "*"; apiServerOptions.apiPort = addressInfo1.port; apiServerOptions.cockpitPort = 0; + apiServerOptions.grpcPort = 0; apiServerOptions.apiTlsEnabled = false; const config = configService.newExampleConfigConvict(apiServerOptions); diff --git a/yarn.lock b/yarn.lock index fee59cead68..ad7282c7053 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2204,14 +2204,14 @@ dependencies: google-gax "^2.12.0" -"@grpc/grpc-js@^1.3.4", "@grpc/grpc-js@~1.3.0": - version "1.3.7" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.7.tgz#58b687aff93b743aafde237fd2ee9a3259d7f2d8" - integrity sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA== +"@grpc/grpc-js@1.3.6", "@grpc/grpc-js@^1.3.4", "@grpc/grpc-js@~1.3.0": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.6.tgz#6e2d17610c2c8df0f6ceab0e1968f563df74b173" + integrity sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg== dependencies: "@types/node" ">=12.12.47" -"@grpc/proto-loader@^0.6.1", "@grpc/proto-loader@^0.6.2": +"@grpc/proto-loader@0.6.4", "@grpc/proto-loader@^0.6.1", "@grpc/proto-loader@^0.6.2": version "0.6.4" resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.4.tgz#5438c0d771e92274e77e631babdc14456441cbdc" integrity sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ== @@ -2999,6 +2999,21 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" +"@mapbox/node-pre-gyp@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950" + integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA== + dependencies: + detect-libc "^1.0.3" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.1" + nopt "^5.0.0" + npmlog "^4.1.2" + rimraf "^3.0.2" + semver "^7.3.4" + tar "^6.1.0" + "@multiformats/base-x@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" @@ -3748,30 +3763,16 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/google-protobuf@3.15.3": + version "3.15.3" + resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.3.tgz#054fb37aecb34d7dec826e1ce2b40cc27ec3d06a" + integrity sha512-MDpu7lit927cdLtBzTPUFjXGANFUnu5ThPqjygY8XmCyI/oDlIA0jAi4sffGOxYaLK2CCxAuU9wGxsgAQbA6FQ== + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== -"@types/jasmine@*": - version "3.8.2" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.8.2.tgz#27ab0aaac29581bcbde5774e1843f90df977078e" - integrity sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg== - -"@types/jasminewd2@2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.10.tgz#ae31c237aa6421bde30f1058b1d20f4577e54443" - integrity sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g== - dependencies: - "@types/jasmine" "*" - -"@types/jasminewd2@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.3.tgz#0d2886b0cbdae4c0eeba55e30792f584bf040a95" - integrity sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg== - dependencies: - "@types/jasmine" "*" - "@types/joi@14.3.4": version "14.3.4" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-14.3.4.tgz#eed1e14cbb07716079c814138831a520a725a1e0" @@ -10414,6 +10415,11 @@ google-p12-pem@^3.0.3: dependencies: node-forge "^0.10.0" +google-protobuf@3.15.8: + version "3.15.8" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.15.8.tgz#5f3948905e4951c867d6bc143f385a80e2a39efe" + integrity sha512-2jtfdqTaSxk0cuBJBtTTWsot4WtR9RVr2rXg7x7OoqiuOKopPrwXpM1G4dXIkLcUNRh3RKzz76C8IOkksZSeOw== + got@9.6.0, got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -10500,6 +10506,21 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== +grpc-tools@1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.11.2.tgz#22d802d40012510ccc6591d11f9c94109ac07aab" + integrity sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.5" + +grpc_tools_node_protoc_ts@5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-5.3.1.tgz#6f81ab7c8289c801cba3373aa334c13ca8f29618" + integrity sha512-OX6pWqN4BbjzdDdoJkkLoODO+XQnGC/hSHCDipF+ZtQlz3fLuYon+8rviUTuwE0etUZK9N34O4iucg3O7FFgyw== + dependencies: + google-protobuf "3.15.8" + handlebars "4.7.7" + gtoken@^5.0.4: version "5.3.1" resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.3.1.tgz#c1c2598a826f2b5df7c6bb53d7be6cf6d50c3c78" @@ -10521,7 +10542,7 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -handlebars@^4.7.6: +handlebars@4.7.7, handlebars@^4.7.6: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== @@ -16745,6 +16766,11 @@ protobufjs@6.11.2, protobufjs@^6.10.0, protobufjs@^6.10.2, protobufjs@^6.11.2: "@types/node" ">=13.7.0" long "^4.0.0" +protoc-gen-ts@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/protoc-gen-ts/-/protoc-gen-ts-0.4.0.tgz#4be7e2086dc32db15f01733a7a200793f1e1081a" + integrity sha512-TUoAT45fC9Fpcbp+rOdjzUPytnYkV8YR0xQ9atZi69RNV2/if600cDxBPXXbujGIegTmiEaUkQIz4Rrk2lOyUQ== + protocols@^1.1.0, protocols@^1.4.0: version "1.4.8" resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8"