Kibana: Automated OpenAPI spec generation

Created on 4 Nov 2020  路  7Comments  路  Source: elastic/kibana

This issue is here to discuss about the feasibility and the possible technical solutions to generate openAPI specs for Kibana endpoints.

Due to the language we are using, this is far from trivial, and way more difficult than it would be done in a compiled language with proper reflection support.

I won't include the global metadata, common types and such in this discussion, as these are more or less constants and do not (I think) represent any technical challenge.

Just for the record, this is what an OpenAPI path spec looks like

{
  "/pets": {
    "get": {
      "description": "Returns pets based on ID",
      "summary": "Find pets by ID",
      "operationId": "getPetsById",
      "responses": {
        "200": {
          "description": "pet response",
          "content": {
            "*/*": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Pet"
                }
              }
            }
          }
        },
        "default": {
          "description": "error payload",
          "content": {
            "text/html": {
              "schema": {
                "$ref": "#/components/schemas/ErrorModel"
              }
            }
          }
        }
      }
    },
    "parameters": [
      {
        "name": "id",
        "in": "path",
        "description": "ID of pet to use",
        "required": true,
        "schema": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "style": "simple"
      }
    ]
  }
}

To simplify and focus on the real challenge, we would need, for each defined endpoint, to be able to retrieve, and convert to OpenAPI format:

  • The path, method and authentication mode of the endpoint
  • The type of the parameters
  • The type of the response payload

Possible solutions

Retrieve the information at runtime

One option would be to have a script that starts Kibana, let all plugins registered their handlers, and then work from the routes that have been registered to core's httpService. This would require to start Kibana in a 'sandboxed' mode that would stop after setup, and collect the routes from core, and then work from there.

The path, method and authentication mode of the endpoint

These would be directly accessible from the route's config

The type of the parameters

We would have access to body, params and query's validation schemas. We would 'just' have to be able to interpret and convert them to OA format at runtime. Of course, this is not something that is currently doable, and would requires to expose metadata on all of kbn/config-schema types to be able to perform reflect-ish operations on the schema.

The type of the response payload

I don't think there is any way to retrieve this information using this approach.

Use AST to parse and retrieve the informations

The second option would be to use AST to parse the code, find all calls to Router#get|post|XXX, and work from there (which is already some heavy parsing work tbh). If it's probably more powerful, this also sounds like colossal work to handle.

The path, method and authentication mode of the endpoint

We should be able to parse the route's configuration and retrieve these infos from there. Note that we would need to be able to resolve variables to scenarios such as path: prefix + "/endpoint".

The type of the parameters

We should be able to retrieve the validation schema nodes, and then use typeChecker.getTypeAtLocation to retrieve the schema type. From there, we would need to retrieve the schema's result type, and then convert it to OpenAPI format.

The type of the response payload

We should be able to find calls to res.ok and use typeChecker.getTypeAtLocation to infer the returned type. We would then need to convert the TS type to openAPI format. I see that https://github.com/grantila/ts-to-openapi exists, so it should probably be doable ourselves (or finding a more used/adopted lib)

Let the endpoints owners specify most of the OpenAPI metadata.

This is what is done by https://www.npmjs.com/package/express-openapi for example

module.exports.get.apiDoc = {
  description: 'A description for retrieving a user.',
  tags: ['users'],
  operationId: 'getUser',
  // parameters for this operation
  parameters: [
    {
      in: 'query',
      name: 'firstName',
      type: 'string'
    }
  ],
  responses: {
    default: {
      $ref: '#/definitions/Error'
    }
  }
};

Not sure this has real value compared to just have solutions teams manually maintain their openAPI spec outside of the raw code.

Core discuss

Most helpful comment

This is great -- thanks for detailing this out @pgayvallet! Just want to share this issue we created over on the Security Detections side that captures what an initial PoC could look like from our perspective: https://github.com/elastic/kibana/issues/81964

All 7 comments

Pinging @elastic/kibana-platform (Team:Platform)

Just brainstorming here, haven't given this a lot of thought, but could we generate source code from the OpenAPI spec instead of generating OpenAPI spec from the source code?

If you "compile" the spec it would generate a my_openapi_routes.ts file which exposes a method like mountOpenApiRoutes(router: IRouter, routeHandlers: MyRouteHandlers). The MyRouteHandlers type will be defined inside my_openapi_routes.ts and will ensure that a route handler is defined for every route and method defined by the spec. We could also generate types from the json schema so that the route handlers have typed parameters and expected return types for the responses.

Would need to think more about it, but this seems to be a viable option yes. Seems technically way easier (not easy, easier) than the other way around.

I think in general it is preferable to keep OpenAPI definitions as your source of truth. This makes the solution language agnostic and allows to generate implementations in multiple languages.

There are projects like this though - https://tsoa-community.github.io/docs/getting-started.html#defining-our-first-model
Java has a similar set of libs to annotate your controllers, but Java has more support for reflection to process method parameters, return types, etc.

I think deciding the source of truth and direction is as much a team/workflow decision as a technical one. That said, my preference is to use JSON Schema & OpenAPI to spec shape & behavior and then generate TS types, JS clients, and servers from that.

What @rudolf describes is essentially the plan for Ingest Manager but ours is currently more manual/gradual

Ingest Manager OpenAPI overview

  • Define endpoints in OpenAPI

    • Live under https://github.com/elastic/kibana/tree/master/x-pack/plugins/ingest_manager/common/openapi

    • Author using multiple files (per route, schema type, etc) for easier maintenance, better code diffs, etc

    • Provide a single bundle w/o $refs for better tooling support

    • Use JSON schema for as many parts as possible (params, responses, etc) to make it easier to generate TS types & share w/others (_ours is currently OpenAPI. Will convert later but not sure if big bang or gradual_)

  • Generate any artifacts (single spec file, TS types, JS clients) needed with script run manually or git hook

    • bundle example: npx swagger-cli bundle -o bundled.json -t json entrypoint.yaml

    • only TS types example: npx openapi-typescript bundled.json --output schema.ts

    • JS client (browser) + TS types: npx swagger-typescript-api -p bundled.json -n client.ts

  • Gradually add those artifacts to each route

    • Can come entirely from spec or only partially

    • e.g. Ingest Manager creates its own validators for a few routes using the JSON schema & AJV https://github.com/elastic/kibana/blob/c355dfebab3cc46a218d3e64f194910deb1e21c9/x-pack/plugins/ingest_manager/server/routes/agent/index.ts#L168-L178

    • the path and method post are added manually, but it'd be trivial to find that info for a specific schema via the operationId (e.g. post-fleet-agents-enroll) or loop through all entries


There are many options for generating clients. Many (most?) allow you to choose a transport (nodejs http, fetch, axios, etc) but no customization in the client code that's generated. We'll want one which allows us to supply our own templates so we can use core.http or anything else we want to add. OpenAPI Generator is probably the most popular & well-supported. swagger-typescript-api also looks pretty nice (it's templates are at https://github.com/acacode/swagger-typescript-api/tree/master/src/templates but you can provide your own)

This is great -- thanks for detailing this out @pgayvallet! Just want to share this issue we created over on the Security Detections side that captures what an initial PoC could look like from our perspective: https://github.com/elastic/kibana/issues/81964

There are many options for generating clients. Many (most?) allow you to choose a transport (nodejs http, fetch, axios, etc) but no customization in the client code that's generated. We'll want one which allows us to supply our own templates so we can use core.http or anything else we want to add. OpenAPI Generator is probably the most popular & well-supported. swagger-typescript-api also looks pretty nice (it's templates are at https://github.com/acacode/swagger-typescript-api/tree/master/src/templates but you can provide your own)

I was mostly working with Java/Maven generator, but in any case supplying your own templates is pretty easy, main concern is who would maintain those templates.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

treussart picture treussart  路  3Comments

MaartenUreel picture MaartenUreel  路  3Comments

socialmineruser1 picture socialmineruser1  路  3Comments

bradvido picture bradvido  路  3Comments

stacey-gammon picture stacey-gammon  路  3Comments