Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd-api-server): swagger.json endpoints #1994

Merged
merged 3 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cactus-cmd-api-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"prom-client": "13.2.0",
"run-time-error": "1.4.0",
"rxjs": "7.8.1",
"safe-stable-stringify": "2.4.3",
"semver": "7.5.2",
"socket.io": "4.5.4",
"socket.io-client": "4.5.4",
Expand Down
29 changes: 29 additions & 0 deletions packages/cactus-cmd-api-server/src/main/json/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
"PrometheusExporterMetricsResponse": {
"type": "string",
"nullable": false
},
"GetOpenApiSpecV1EndpointResponse": {
"type": "string",
"nullable": false
}
}
},
Expand Down Expand Up @@ -126,6 +130,31 @@
}
}
}
},
"/api/v1/api-server/get-open-api-spec": {
"get": {
"description": "Returns the openapi.json document of specific plugin.",
"x-hyperledger-cactus": {
"http": {
"verbLowerCase": "get",
"path": "/api/v1/api-server/get-open-api-spec"
}
},
"operationId": "getOpenApiSpecV1",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetOpenApiSpecV1EndpointResponse"
}
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ All URIs are relative to *http://localhost*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*DefaultApi* | [**getHealthCheckV1**](docs/DefaultApi.md#gethealthcheckv1) | **GET** /api/v1/api-server/healthcheck | Can be used to verify liveness of an API server instance
*DefaultApi* | [**getOpenApiSpecV1**](docs/DefaultApi.md#getopenapispecv1) | **GET** /api/v1/api-server/get-open-api-spec |
*DefaultApi* | [**getPrometheusMetricsV1**](docs/DefaultApi.md#getprometheusmetricsv1) | **GET** /api/v1/api-server/get-prometheus-exporter-metrics | Get the Prometheus Metrics


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,74 @@ class DefaultApi(basePath: kotlin.String = defaultBasePath, client: OkHttpClient
)
}

/**
*
* Returns the openapi.json document of specific plugin.
* @return kotlin.String
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
* @throws UnsupportedOperationException If the API returns an informational or redirection response
* @throws ClientException If the API returns a client error response
* @throws ServerException If the API returns a server error response
*/
@Suppress("UNCHECKED_CAST")
@Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class)
fun getOpenApiSpecV1() : kotlin.String {
val localVarResponse = getOpenApiSpecV1WithHttpInfo()

return when (localVarResponse.responseType) {
ResponseType.Success -> (localVarResponse as Success<*>).data as kotlin.String
ResponseType.Informational -> throw UnsupportedOperationException("Client does not support Informational responses.")
ResponseType.Redirection -> throw UnsupportedOperationException("Client does not support Redirection responses.")
ResponseType.ClientError -> {
val localVarError = localVarResponse as ClientError<*>
throw ClientException("Client error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
}
ResponseType.ServerError -> {
val localVarError = localVarResponse as ServerError<*>
throw ServerException("Server error : ${localVarError.statusCode} ${localVarError.message.orEmpty()}", localVarError.statusCode, localVarResponse)
}
}
}

/**
*
* Returns the openapi.json document of specific plugin.
* @return ApiResponse<kotlin.String?>
* @throws IllegalStateException If the request is not correctly configured
* @throws IOException Rethrows the OkHttp execute method exception
*/
@Suppress("UNCHECKED_CAST")
@Throws(IllegalStateException::class, IOException::class)
fun getOpenApiSpecV1WithHttpInfo() : ApiResponse<kotlin.String?> {
val localVariableConfig = getOpenApiSpecV1RequestConfig()

return request<Unit, kotlin.String>(
localVariableConfig
)
}

/**
* To obtain the request config of the operation getOpenApiSpecV1
*
* @return RequestConfig
*/
fun getOpenApiSpecV1RequestConfig() : RequestConfig<Unit> {
val localVariableBody = null
val localVariableQuery: MultiValueMap = mutableMapOf()
val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
localVariableHeaders["Accept"] = "application/json"

return RequestConfig(
method = RequestMethod.GET,
path = "/api/v1/api-server/get-open-api-spec",
query = localVariableQuery,
headers = localVariableHeaders,
requiresAuthentication = false,
body = localVariableBody
)
}

/**
* Get the Prometheus Metrics
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ import "models/health_check_response_pb.proto";
service DefaultService {
rpc GetHealthCheckV1 (google.protobuf.Empty) returns (HealthCheckResponsePB);

rpc GetOpenApiSpecV1 (google.protobuf.Empty) returns (GetOpenApiSpecV1Response);

rpc GetPrometheusMetricsV1 (google.protobuf.Empty) returns (GetPrometheusMetricsV1Response);

}

message GetOpenApiSpecV1Response {
string data = 1;
}

message GetPrometheusMetricsV1Response {
string data = 1;
}
Expand Down
78 changes: 52 additions & 26 deletions packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { AddressInfo } from "net";
import type { Server as SecureServer } from "https";
import type { Request, Response, RequestHandler } from "express";
import type { ServerOptions as SocketIoServerOptions } from "socket.io";
import type { Socket as SocketIoSocket } from "socket.io";
import exitHook from "async-exit-hook";
import os from "os";
import path from "path";
Expand All @@ -13,7 +16,6 @@ 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 { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";
import compression from "compression";
Expand All @@ -22,8 +24,6 @@ import cors from "cors";

import rateLimit from "express-rate-limit";
import { Server as SocketIoServer } from "socket.io";
import type { ServerOptions as SocketIoServerOptions } from "socket.io";
import type { Socket as SocketIoSocket } from "socket.io";
import { authorize as authorizeSocket } from "@thream/socketio-jwt";

import {
Expand All @@ -37,7 +37,11 @@ import {
PluginImportAction,
} from "@hyperledger/cactus-core-api";

import { PluginRegistry } from "@hyperledger/cactus-core";
import {
PluginRegistry,
registerWebServiceEndpoint,
} from "@hyperledger/cactus-core";

import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core";

import {
Expand All @@ -49,7 +53,6 @@ import {

import { ICactusApiServerOptions } from "./config/config-service";
import OAS from "../json/openapi.json";
// import { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";

import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter";
import { AuthorizerFactory } from "./authzn/authorizer-factory";
Expand All @@ -58,6 +61,10 @@ import { WatchHealthcheckV1Endpoint } from "./web-services/watch-healthcheck-v1-
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";
import {
GetOpenApiSpecV1Endpoint,
IGetOpenApiSpecV1EndpointOptions,
} from "./web-services/get-open-api-spec-v1-endpoint";

export interface IApiServerConstructorOptions {
readonly pluginManagerOptions?: { pluginsPath: string };
Expand Down Expand Up @@ -91,8 +98,8 @@ export class ApiServer {
private readonly httpServerCockpit?: Server | SecureServer;
private readonly wsApi: SocketIoServer;
private readonly grpcServer: GrpcServer;
private readonly expressApi: Application;
private readonly expressCockpit: Application;
private readonly expressApi: express.Express;
private readonly expressCockpit: express.Express;
private readonly pluginsPath: string;
private readonly enableShutdownHook: boolean;

Expand Down Expand Up @@ -305,17 +312,19 @@ export class ApiServer {
}
}

public async initPluginRegistry(): Promise<PluginRegistry> {
const registry = new PluginRegistry({ plugins: [] });
public async initPluginRegistry(req?: {
readonly pluginRegistry: PluginRegistry;
}): Promise<PluginRegistry> {
const { pluginRegistry = new PluginRegistry({ plugins: [] }) } = req || {};
const { plugins } = this.options.config;
this.log.info(`Instantiated empty registry, invoking plugin factories...`);

for (const pluginImport of plugins) {
const plugin = await this.instantiatePlugin(pluginImport, registry);
registry.add(plugin);
const plugin = await this.instantiatePlugin(pluginImport, pluginRegistry);
pluginRegistry.add(plugin);
}

return registry;
return pluginRegistry;
}

private async instantiatePlugin(
Expand Down Expand Up @@ -347,7 +356,8 @@ export class ApiServer {

// eslint-disable-next-line @typescript-eslint/no-var-requires
const pluginPackage = require(/* webpackIgnore: true */ packagePath);
const createPluginFactory = pluginPackage.createPluginFactory as PluginFactoryFactory;
const createPluginFactory =
pluginPackage.createPluginFactory as PluginFactoryFactory;
const pluginFactoryOptions: IPluginFactoryOptions = {
pluginImportType: pluginImport.type,
};
Expand Down Expand Up @@ -550,9 +560,27 @@ export class ApiServer {
* healthcheck and monitoring information.
* @param app
*/
async getOrCreateWebServices(app: express.Application): Promise<void> {
async getOrCreateWebServices(app: express.Express): Promise<void> {
const { log } = this;
const { logLevel } = this.options.config;
const pluginRegistry = await this.getOrInitPluginRegistry();

{
const oasPath = OAS.paths["/api/v1/api-server/get-open-api-spec"];

const operationId = oasPath.get.operationId;
const opts: IGetOpenApiSpecV1EndpointOptions = {
oas: OAS,
oasPath,
operationId,
path: oasPath.get["x-hyperledger-cactus"].http.path,
pluginRegistry,
verbLowerCase: oasPath.get["x-hyperledger-cactus"].http.verbLowerCase,
logLevel,
};
const endpoint = new GetOpenApiSpecV1Endpoint(opts);
await registerWebServiceEndpoint(app, endpoint);
}

const healthcheckHandler = (req: Request, res: Response) => {
res.json({
Expand Down Expand Up @@ -596,13 +624,10 @@ export class ApiServer {
const {
"/api/v1/api-server/get-prometheus-exporter-metrics": oasPathPrometheus,
} = OAS.paths;
const { http: httpPrometheus } = oasPathPrometheus.get[
"x-hyperledger-cactus"
];
const {
path: httpPathPrometheus,
verbLowerCase: httpVerbPrometheus,
} = httpPrometheus;
const { http: httpPrometheus } =
oasPathPrometheus.get["x-hyperledger-cactus"];
const { path: httpPathPrometheus, verbLowerCase: httpVerbPrometheus } =
httpPrometheus;
(app as any)[httpVerbPrometheus](
httpPathPrometheus,
prometheusExporterHandler,
Expand All @@ -628,6 +653,12 @@ export class ApiServer {
)
: GrpcServerCredentials.createInsecure();

this.grpcServer.addService(
default_service.org.hyperledger.cactus.cmd_api_server
.DefaultServiceClient.service,
new GrpcServerApiServer(),
);

this.grpcServer.bindAsync(
grpcHostAndPort,
grpcTlsCredentials,
Expand All @@ -636,11 +667,6 @@ export class ApiServer {
this.log.error("Binding gRPC failed: ", 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 });
Expand Down
Loading
Loading