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
- JSON, YAML, or TOML config files in
- 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
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
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. |
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
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.
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
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
.
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.
TODO: Show table describing all well-known configuration keys.
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)
}
}
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)
})
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"
}
See the apikey
module for an example. More docs coming soon.
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