• Stars
    star
    154
  • Rank 242,095 (Top 5 %)
  • Language
    Go
  • License
    MIT License
  • Created about 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Generate a CLI from an OpenAPI 3 specification

OpenAPI CLI Generator

GoDoc Build Status Go Report Card Platforms

openapi-to-cli


Note: this project has been superceded by Restish, an advanced auto-configured OpenAPI CLI that just works:


This project can be used to generate CLIs from OpenAPI 3 specs. The generated CLIs have the following features:

  • Authentication support for API keys and Auth0.
  • Commands, subcommands, & flag parsing through Cobra
  • Configuration through Viper
    • JSON, YAML, or TOML config files in /etc/ and $HOME, e.g. {"verbose": true} in ~/.my-app/config.json
    • From environment: APP_NAME_VERBOSE=1
    • From flags: --verbose
  • HTTP middleware through Gentleman
  • Command middleware with custom parameters (see customization below)
  • Input through stdin or CLI shorthand
  • Built-in cache to save data between runs
  • Fast structured logging via zerolog
  • Pretty output colored by Chroma
  • Response filtering & projection by JMESPath plus enhancements

Getting Started

First, make sure you have Go installed. Then, you can grab this project:

$ go get -u github.com/danielgtaylor/openapi-cli-generator

Next, make your project directory and generate the commands file.

# Set up your new project
$ mkdir my-cli && cd my-cli

# Create the default main file. The app name is used for config and env settings.
$ openapi-cli-generator init <app-name>

# Generate the commands
$ openapi-cli-generator generate openapi.yaml

Last, add a line like the following to your main.go file:

openapiRegister(false)

If you would like to generate a client for many APIs and have each available under their own namespace, pass true instead. Next, build your client:

# Build & install the generated client.
$ go install

# Test it out!
$ my-cli --help

OpenAPI Extensions

Several extensions properties may be used to change the behavior of the CLI.

Name Description
x-cli-aliases Sets up command aliases for operations.
x-cli-description Provide an alternate description for the CLI.
x-cli-ignore Ignore this path, operation, or parameter.
x-cli-hidden Hide this path, or operation.
x-cli-name Provide an alternate name for the CLI.
x-cli-waiters Generate commands/params to wait until a certain state is reached.

Aliases

The following example shows how you would set up a command that can be invoked by either list-items or simply ls:

paths:
  /items:
    get:
      operationId: ListItems
      x-cli-aliases:
        - ls

Description

You can override the default description easily:

paths:
  /items:
    description: Some info talking about HTTP headers.
    x-cli-description: Some info talking about command line arguments.

Exclusion

It is possible to exclude paths, operations, and/or parameters from the generated CLI. No code will be generated as they will be completely skipped.

paths:
  /included:
    description: I will get included in the CLI.
  /excluded:
    x-cli-ignore: true
    description: I will not be in the CLI :-(

Alternatively, you can have the path or operation exist in the UI but be hidden from the standard help list. Specific help is still available via my-cli my-hidden-operation --help:

paths:
  /hidden:
    x-cli-hidden: true

Name

You can override the default name for the API, operations, and params:

info:
  x-cli-name: foo
paths:
  /items:
    operationId: myOperation
    x-cli-name: my-op
    parameters:
      - name: id
        x-cli-name: item-id
        in: query

With the above, you would be able to call my-cli my-op --item-id=12.

Waiters

Waiters allow you to declaratively define special commands and parameters that will cause a command to block and wait until a particular condition has been met. This is particularly useful for asyncronous operations. For example, you might submit an order and then wait for that order to have been charged successfully before continuing on.

At a high level, waiters consist of an operation and a set of matchers that select a value and compare it to an expectation. For the example above, you might call the GetOrder operation every 30 seconds until the response's JSON status field is equal to charged. Here is what that would look like in your OpenAPI YAML file:

info:
  title: Orders API
paths:
  /order/{id}:
    get:
      operationId: GetOrder
      description: Get an order's details.
      parameters:
        - name: id
          in: path
      responses:
        200:
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: ['placed', 'charged', 'shipped', 'returned']
x-cli-waiters:
  order-charged:
    delay: 30
    attempts: 10
    operationId: GetOrder
    matchers:
      - select: response.body#status
        expected: charged

The generated CLI will work like this: my-cli wait order-charged $ID where $ID corresponds to the GetOrder operation's id parameter. It will try to get and match the status 10 times, with a pause of 30 seconds between tries. If it matches, it will exit with a zero status code. If it fails, it will exit with a non-zero exit code and log a message.

This is a great start, but we can make this a little bit friendlier to use. Take a look at this modified waiter configuration:

x-cli-waiters:
  order-charged:
    short: Short description for CLI `--help`
    long: Long description for CLI `--help`
    delay: 30
    attempts: 10
    operationId: GetOrder
    matchers:
      - select: response.body#status
        expected: charged
      - select: response.status
        expected: 404
        state: failure
    after:
      CreateOrder:
        id: response.body#order_id

Here we added two new features:

  1. A short-circuit to fail fast. If we type an invalid order ID then we want the command to exit immediately with a non-zero exit code.

  2. The after block allows us to add a parameter to an existing operation to invoke the waiter. This block says that after a call to CreateOrder with a --wait-order-charged param, it should call the waiter's GetOrder operation with the id param set to the result of the response.body#order_id selector.

You can now create and wait on an order via my-cli create-order <order.json --wait-order-charged.

Matchers

The following matcher fields are available:

Field Description Example
select The value selection criteria. See the selector table below. response.status
test The test to perform. Defaults to equal but can be set to any and all to match list items. equal
expected The expected value charged
state The state to set. Defaults to success but can be set to failure. success

The following selectors are available:

Selector Description Argument Example
request.param Request parameter Parameter name request.param#id
request.body Request body query JMESPath query request.body#order.id
response.status Response HTTP status code - response.status
response.header Response HTTP header Header name response.header#content-type
response.body Response body query JMESPath query response.body#orders[].status

Customization

Your main.go is the entrypoint to your generated CLI, and may be customized to add additional logic and features. For example, you might set custom headers or handle auth before a request goes out on the wire. The apikey module provides a sample implementation.

Configuration Description

TODO: Show table describing all well-known configuration keys.

Custom Global Flags

It's possible to supply custom flags and a pre-run function. For example, say your OpenAPI spec has two servers: production and testing. You could add a --test flag to select the second server.

func main() {
	// ... init code ...

	// Add a `--test` flag to enable hitting testing.
	cli.AddGlobalFlag("test", "", "Use test endpoint", false)

	cli.PreRun = func(cmd *cobra.Command, args []string) error {
		if viper.GetBool("test") {
			// Use the test server
			viper.Set("server-index", 1)
		}
}

HTTP Middleware

Gentleman provides support for HTTP request and response middleware. Don't forget to call h.Next(ctx) in your handler! For example:

// Register a request middleware handler to print the path.
cli.Client.UseRequest(func(ctx *context.Context, h context.Handler) {
	fmt.Printf("Request path: %s\n", ctx.Request.URL.Path)
	h.Next(ctx)
})

// Register a response middleware handler to print the status code.
cli.Client.UseResponse(func(ctx *context.Context, h context.Handler) {
	fmt.Printf("Response status: %d\n", ctx.Response.StatusCode)
	h.Next(ctx)
})

Custom Command Flags & Middleware

While the above HTTP middleware is great for adding headers or logging various things, there are times when you need to modify the behavior of a generated command. You can do so by registering custom command flags and using command middleware.

Flags and middleware are applied to a command path, which is a space-separated list of commands in a hierarchy. For example, if you have a command foo which has a subcommand bar, then the command path to reference bar for flags and middleware would be foo bar.

Note that any calls to cli.AddFlag must be made before calling the generated command registration function (e.g. openapiRegister(...)) or the flags will not get created properly.

Here's an example showing how a custom flag can change the command response:

// Register a new custom flag for the `foo` command.
cli.AddFlag("foo", "custom", "", "description", "")

cli.RegisterAfter("foo", func(cmd *cobra.Command, params *viper.Viper, resp *gentleman.Response, data interface{}) interface{} {
  m := data.(map[string]interface{})
  m["custom"] = params.GetString("custom")
  return m
})

// Register our generated commands with the CLI after the above.
openapiRegister(false)

If the foo command would normally return a JSON object like {"hello": "world"} it would now return the following if called with --custom=test:

{
  "custom": "test",
  "hello": "world"
}

Authentication & Authorization

See the apikey module for a simple example of a pre-shared key.

If instead you use a third party auth system that vends tokens and want your users to be able to log in and use the API, here's an example using Auth0:

func main() {
	cli.Init(&cli.Config{
		AppName:   "example",
		EnvPrefix: "EXAMPLE",
		Version:   "1.0.0",
	})

  // Auth0 requires a client ID, issuer base URL, and audience fields when
  // requesting a token. We set these up here and use the Authorization Code
  // with PKCE flow to log in, which opens a browser for the user to log into.
  clientID := "abc123"
  issuer := "https://mycompany.auth0.com/"

  cli.UseAuth("user", &oauth.AuthCodeHandler{
    ClientID: "clientID",
    AuthorizeURL: issuer+"authorize",
    TokenURL: issuer+"oauth/token",
    Keys: []string{"audience"},
    Params: []string{"audience"},
    Scopes: []string{"offline_access"},
  })

  // TODO: Register API commands here
  // ...

	cli.Root.Execute()
}

Note that there is a convenience module when using Auth0 specifically, allowing you to do this:

auth0.InitAuthCode(clientID, issuer,
  auth0.Type("user"),
  auth0.Scopes("offline_access"))

The expanded example above is more useful when integrating with other services since it uses basic OAuth 2 primitives.

Development

Working with Templates

The code generator is configured to bundle all necessary assets into the final executable by default. If you wish to modify the templates, you can use the go-bindata tool to help:

# One-time setup of the go-bindata tool:
$ go get -u github.com/shuLhan/go-bindata/...

# Set up development mode (load data from actual files in ./templates/)
$ go-bindata -debug ./templates/...

# Now, do all your edits to the templates. You can test with:
$ go run *.go generate my-api.yaml

# Build the final static embedded files and code generator executable.
$ go generate
$ go install

License

https://dgt.mit-license.org/

More Repositories

1

aglio

An API Blueprint renderer with theme support that outputs static HTML
CoffeeScript
4,746
star
2

huma

Huma REST/HTTP API Framework for Golang with OpenAPI 3.1
Go
1,895
star
3

python-betterproto

Clean, modern, Python 3.6+ code generator & library for Protobuf 3 and async gRPC
Python
1,513
star
4

jpeg-archive

Utilities for archiving JPEGs for long term storage.
C
1,161
star
5

apisprout

Lightweight, blazing fast, cross-platform OpenAPI 3 mock server with validation
Go
639
star
6

restish

Restish is a CLI for interacting with REST-ish HTTP APIs with some nice features built-in
Go
497
star
7

qtfaststart

Quicktime atom positioning in Python for fast streaming
Python
450
star
8

nesh

An enhanced, extensible interactive shell for Node.js and CoffeeScript
CoffeeScript
287
star
9

arista

Arista Transcoder
Python
120
star
10

ladon

A small, simple cross-platform utility to process many files in parallel.
JavaScript
81
star
11

braintree_django

Braintree Django Module
Python
52
star
12

atom-api-blueprint-preview

Live preview API Blueprint in Atom
CoffeeScript
45
star
13

html5-space-fighter

HTML5 Space Fighter Game
JavaScript
30
star
14

apilint

Extensible REST API linter utility
JavaScript
27
star
15

malt.io

Malt.io free community for brewers
Python
26
star
16

qtrotate

Tools for handling rotated Quicktime/MP4 files
Python
26
star
17

qtfaststart.js

Javascript version of qt-faststart
JavaScript
21
star
18

guid-tool-web

GUID conversion tool website
Python
18
star
19

atom-language-api-blueprint

API Blueprint and MSON grammars for the Atom.io text editor.
CoffeeScript
14
star
20

django-ccss

Django CleverCSS
Python
13
star
21

tech-talk

Markdown slideshows with a built-in terminal that just works
JavaScript
13
star
22

mexpr

Micro expression parser library for Go
Go
12
star
23

node-desktop-uploader

Recursively watch directories and upload new/updated files
CoffeeScript
11
star
24

shorthand

Structured data & CLI shorthand syntax for Go
Go
10
star
25

atom-monokai-extended

Extended Monokai theme for the Atom text editor
CSS
9
star
26

unistyle

β„±π’Άπ“ƒπ’Έπ“Ž π˜π—²π˜…π˜ 𝘴𝘡𝘺𝘭𝘦𝘴 𝔣𝔬𝔯 GΜ³oΜ³lΜ³aΜ³nΜ³gΜ³ in a compact, zero dependency library.
Go
7
star
27

guid-tool

A tool to convert between various forms of GUID
Python
7
star
28

ffmpeg

FFmpeg + patches
C
7
star
29

eidolon

Generate JSON or JSON Schema from Refract & MSON data structures
CoffeeScript
6
star
30

paodate

Simpler Python date handling
Python
5
star
31

mateo

A simple API description interface library
JavaScript
5
star
32

sdt

Structured Data Templates
Go
4
star
33

peasant

An opinionated Node.js ES6 module lint/build/test/coverage helper
JavaScript
3
star
34

dotfiles

My public dotfiles to help bootstrap a new machine
Shell
3
star
35

polaris

Lightweight backend utilities for static websites
CoffeeScript
3
star
36

apiscrub

OpenAPI Scrubber
Python
3
star
37

bible-ref

Utilities for handling Bible references
CoffeeScript
2
star
38

homebrew-restish

Homebrew Tap for Restish https://rest.sh/
Ruby
2
star
39

casing

An intelligent casing conversion library for Go
Go
2
star
40

injectobot

Developer-friendly programmable IRC bot
CoffeeScript
2
star
41

huma-build

Docker build utility for Huma REST OpenAPI projects
Go
2
star
42

rhs-color

R.H.S. color conversion utilities
JavaScript
2
star
43

apibin

Example API with modern features
Go
2
star
44

nesh-hello

A simple example plugin for Nesh, the Node.js enhanced shell.
JavaScript
1
star
45

arista-website

Arista Transcoder Website
JavaScript
1
star
46

gaaflora-website

http://www.gaaflora.com/
HTML
1
star
47

vscode-sdt

Visual Studio Code support for Structured Data Templates
JavaScript
1
star