Move command Meta to base.Command and split http options

This commit is contained in:
Kyle Havlovitz 2017-02-07 19:16:33 -05:00
parent edc353914d
commit 49d2ce1c3d
No known key found for this signature in database
GPG Key ID: 8A5E6B173056AD6C
21 changed files with 199 additions and 150 deletions

View File

@ -1,4 +1,4 @@
package command
package base
import (
"bufio"
@ -22,10 +22,13 @@ type FlagSetFlags uint
const (
FlagSetNone FlagSetFlags = iota << 1
FlagSetHTTP FlagSetFlags = iota << 1
FlagSetClientHTTP FlagSetFlags = iota << 1
FlagSetServerHTTP FlagSetFlags = iota << 1
FlagSetHTTP = FlagSetClientHTTP | FlagSetServerHTTP
)
type Meta struct {
type Command struct {
Ui cli.Ui
Flags FlagSetFlags
@ -33,47 +36,64 @@ type Meta struct {
// These are the options which correspond to the HTTP API options
httpAddr string
datacenter string
token string
datacenter string
stale bool
}
// HTTPClient returns a client with the parsed flags. It panics if the command
// does not accept HTTP flags or if the flags have not been parsed.
func (m *Meta) HTTPClient() (*api.Client, error) {
if !m.hasHTTP() {
func (c *Command) HTTPClient() (*api.Client, error) {
if !c.hasClientHTTP() && !c.hasServerHTTP() {
panic("no http flags defined")
}
if !m.flagSet.Parsed() {
if !c.flagSet.Parsed() {
panic("flags have not been parsed")
}
return api.NewClient(&api.Config{
Datacenter: m.datacenter,
Address: m.httpAddr,
Token: m.token,
})
config := api.DefaultConfig()
if c.datacenter != "" {
config.Datacenter = c.datacenter
}
if c.httpAddr != "" {
config.Address = c.httpAddr
}
if c.token != "" {
config.Token = c.token
}
c.Ui.Info(fmt.Sprintf("client http addr: %s", config.Address))
return api.NewClient(config)
}
// httpFlags is the list of flags that apply to HTTP connections.
func (m *Meta) httpFlags(f *flag.FlagSet) *flag.FlagSet {
// httpFlagsClient is the list of flags that apply to HTTP connections.
func (c *Command) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet {
if f == nil {
f = flag.NewFlagSet("", flag.ContinueOnError)
}
f.StringVar(&m.datacenter, "datacenter", "",
"Name of the datacenter to query. If unspecified, this will default to "+
"the datacenter of the queried agent.")
f.StringVar(&m.httpAddr, "http-addr", "",
f.StringVar(&c.httpAddr, "http-addr", "",
"Address and port to the Consul HTTP agent. The value can be an IP "+
"address or DNS address, but it must also include the port. This can "+
"also be specified via the CONSUL_HTTP_ADDR environment variable. The "+
"default value is 127.0.0.1:8500.")
f.StringVar(&m.token, "token", "",
f.StringVar(&c.token, "token", "",
"ACL token to use in the request. This can also be specified via the "+
"CONSUL_HTTP_TOKEN environment variable. If unspecified, the query will "+
"default to the token of the Consul agent at the HTTP address.")
f.BoolVar(&m.stale, "stale", false,
return f
}
// httpFlagsServer is the list of flags that apply to HTTP connections.
func (c *Command) httpFlagsServer(f *flag.FlagSet) *flag.FlagSet {
if f == nil {
f = flag.NewFlagSet("", flag.ContinueOnError)
}
f.StringVar(&c.datacenter, "datacenter", "",
"Name of the datacenter to query. If unspecified, this will default to "+
"the datacenter of the queried agent.")
f.BoolVar(&c.stale, "stale", false,
"Permit any Consul server (non-leader) to respond to this request. This "+
"allows for lower latency and higher throughput, but can result in "+
"stale data. This option has no effect on non-read operations. The "+
@ -84,72 +104,95 @@ func (m *Meta) httpFlags(f *flag.FlagSet) *flag.FlagSet {
// NewFlagSet creates a new flag set for the given command. It automatically
// generates help output and adds the appropriate API flags.
func (m *Meta) NewFlagSet(c cli.Command) *flag.FlagSet {
func (c *Command) NewFlagSet(command cli.Command) *flag.FlagSet {
f := flag.NewFlagSet("", flag.ContinueOnError)
f.Usage = func() { m.Ui.Error(c.Help()) }
f.Usage = func() { c.Ui.Error(command.Help()) }
if m.hasHTTP() {
m.httpFlags(f)
if c.hasClientHTTP() {
c.httpFlagsClient(f)
}
if c.hasServerHTTP() {
c.httpFlagsServer(f)
}
errR, errW := io.Pipe()
errScanner := bufio.NewScanner(errR)
go func() {
for errScanner.Scan() {
m.Ui.Error(errScanner.Text())
c.Ui.Error(errScanner.Text())
}
}()
f.SetOutput(errW)
m.flagSet = f
c.flagSet = f
return f
}
// Parse is used to parse the underlying flag set.
func (m *Meta) Parse(args []string) error {
return m.flagSet.Parse(args)
func (c *Command) Parse(args []string) error {
return c.flagSet.Parse(args)
}
// Help returns the help for this flagSet.
func (m *Meta) Help() string {
return m.helpFlagsFor(m.flagSet)
func (c *Command) Help() string {
return c.helpFlagsFor(c.flagSet)
}
// hasHTTP returns true if this meta command contains HTTP flags.
func (m *Meta) hasHTTP() bool {
return m.Flags&FlagSetHTTP != 0
// hasClientHTTP returns true if this meta command contains client HTTP flags.
func (c *Command) hasClientHTTP() bool {
return c.Flags&FlagSetClientHTTP != 0
}
// hasServerHTTP returns true if this meta command contains server HTTP flags.
func (c *Command) hasServerHTTP() bool {
return c.Flags&FlagSetServerHTTP != 0
}
// helpFlagsFor visits all flags in the given flag set and prints formatted
// help output. This function is sad because there's no "merging" of command
// line flags. We explicitly pull out our "common" options into another section
// by doing string comparisons :(.
func (m *Meta) helpFlagsFor(f *flag.FlagSet) string {
httpFlags := m.httpFlags(nil)
func (c *Command) helpFlagsFor(f *flag.FlagSet) string {
httpFlagsClient := c.httpFlagsClient(nil)
httpFlagsServer := c.httpFlagsServer(nil)
var out bytes.Buffer
first := true
firstHTTP := true
if c.hasClientHTTP() {
if firstHTTP {
printTitle(&out, "HTTP API Options")
firstHTTP = false
}
httpFlagsClient.VisitAll(func(f *flag.Flag) {
printFlag(&out, f)
})
}
if c.hasServerHTTP() {
if firstHTTP {
printTitle(&out, "HTTP API Options")
firstHTTP = false
}
httpFlagsServer.VisitAll(func(f *flag.Flag) {
printFlag(&out, f)
})
}
firstCommand := true
f.VisitAll(func(f *flag.Flag) {
// Skip HTTP flags as they will be grouped separately
if flagContains(httpFlags, f) {
if flagContains(httpFlagsClient, f) || flagContains(httpFlagsServer, f) {
return
}
if first {
if firstCommand {
printTitle(&out, "Command Options")
first = false
firstCommand = false
}
printFlag(&out, f)
})
if m.hasHTTP() {
printTitle(&out, "HTTP API Options")
httpFlags.VisitAll(func(f *flag.Flag) {
printFlag(&out, f)
})
}
return strings.TrimRight(out.String(), "\n")
}
@ -190,7 +233,7 @@ func flagContains(fs *flag.FlagSet, f *flag.Flag) bool {
return skip
}
// wrapAtLength wraps the given text at the maxLineLength, taxing into account
// wrapAtLength wraps the given text at the maxLineLength, taking into account
// any provided left padding.
func wrapAtLength(s string, pad int) string {
wrapped := text.Wrap(s, maxLineLength-pad)

View File

@ -5,14 +5,13 @@ import (
"strings"
"github.com/hashicorp/consul/command/agent"
"github.com/hashicorp/consul/command/base"
)
// ConfigTestCommand is a Command implementation that is used to
// verify config files
type ConfigTestCommand struct {
Meta
configFiles []string
base.Command
}
func (c *ConfigTestCommand) Help() string {
@ -27,29 +26,31 @@ Usage: consul configtest [options]
Returns 0 if the configuration is valid, or 1 if there are problems.
` + c.Meta.Help()
` + c.Command.Help()
return strings.TrimSpace(helpText)
}
func (c *ConfigTestCommand) Run(args []string) int {
f := c.Meta.NewFlagSet(c)
f.Var((*agent.AppendSliceValue)(&c.configFiles), "config-file",
var configFiles []string
f := c.Command.NewFlagSet(c)
f.Var((*agent.AppendSliceValue)(&configFiles), "config-file",
"Path to a JSON file to read configuration from. This can be specified multiple times.")
f.Var((*agent.AppendSliceValue)(&c.configFiles), "config-dir",
f.Var((*agent.AppendSliceValue)(&configFiles), "config-dir",
"Path to a directory to read configuration files from. This will read every file ending in "+
".json as configuration in this directory in alphabetical order.")
if err := c.Meta.Parse(args); err != nil {
if err := c.Command.Parse(args); err != nil {
return 1
}
if len(c.configFiles) <= 0 {
if len(configFiles) <= 0 {
c.Ui.Error("Must specify config using -config-file or -config-dir")
return 1
}
_, err := agent.ReadConfigPaths(c.configFiles)
_, err := agent.ReadConfigPaths(configFiles)
if err != nil {
c.Ui.Error(fmt.Sprintf("Config validation failed: %v", err.Error()))
return 1

View File

@ -6,15 +6,16 @@ import (
"path/filepath"
"testing"
"github.com/hashicorp/consul/command/base"
"github.com/mitchellh/cli"
)
func testConfigTestCommand(t *testing.T) (*cli.MockUi, *ConfigTestCommand) {
ui := new(cli.MockUi)
return ui, &ConfigTestCommand{
Meta: Meta{
Command: base.Command{
Ui: ui,
Flags: FlagSetNone,
Flags: base.FlagSetNone,
},
}
}

View File

@ -2,18 +2,19 @@ package command
import (
"fmt"
"github.com/hashicorp/consul/command/base"
"strings"
)
// ForceLeaveCommand is a Command implementation that tells a running Consul
// to force a member to enter the "left" state.
type ForceLeaveCommand struct {
Meta
base.Command
}
func (c *ForceLeaveCommand) Run(args []string) int {
f := c.Meta.NewFlagSet(c)
if err := c.Meta.Parse(args); err != nil {
f := c.Command.NewFlagSet(c)
if err := c.Command.Parse(args); err != nil {
return 1
}
@ -25,7 +26,7 @@ func (c *ForceLeaveCommand) Run(args []string) int {
return 1
}
client, err := c.Meta.HTTPClient()
client, err := c.Command.HTTPClient()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -55,7 +56,7 @@ Usage: consul force-leave [options] name
Consul will attempt to reconnect to those failed nodes for some period of
time before eventually reaping them.
` + c.Meta.Help()
` + c.Command.Help()
return strings.TrimSpace(helpText)
}

View File

@ -3,6 +3,7 @@ package command
import (
"errors"
"fmt"
"github.com/hashicorp/consul/command/base"
"github.com/hashicorp/consul/testutil"
"github.com/hashicorp/serf/serf"
"github.com/mitchellh/cli"
@ -13,9 +14,9 @@ import (
func testForceLeaveCommand(t *testing.T) (*cli.MockUi, *ForceLeaveCommand) {
ui := new(cli.MockUi)
return ui, &ForceLeaveCommand{
Meta: Meta{
Command: base.Command{
Ui: ui,
Flags: FlagSetHTTP,
Flags: base.FlagSetClientHTTP,
},
}
}

View File

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/agent"
"github.com/hashicorp/consul/command/base"
)
const (
@ -35,18 +36,13 @@ const (
// LockCommand is a Command implementation that is used to setup
// a "lock" which manages lock acquisition and invokes a sub-process
type LockCommand struct {
Meta
base.Command
ShutdownCh <-chan struct{}
child *os.Process
childLock sync.Mutex
limit int
monitorRetry int
name string
passStdin bool
timeout time.Duration
verbose bool
}
@ -69,7 +65,7 @@ Usage: consul lock [options] prefix child...
The prefix provided must have write privileges.
` + c.Meta.Help()
` + c.Command.Help()
return strings.TrimSpace(helpText)
}
@ -81,39 +77,44 @@ func (c *LockCommand) Run(args []string) int {
func (c *LockCommand) run(args []string, lu **LockUnlock) int {
var childDone chan struct{}
var limit int
var monitorRetry int
var name string
var passStdin bool
var timeout time.Duration
f := c.Meta.NewFlagSet(c)
f.IntVar(&c.limit, "n", 1,
f := c.Command.NewFlagSet(c)
f.IntVar(&limit, "n", 1,
"Optional limit on the number of concurrent lock holders. The underlying "+
"implementation switches from a lock to a semaphore when the value is "+
"greater than 1. The default value is 1.")
f.IntVar(&c.monitorRetry, "monitor-retry", defaultMonitorRetry,
"Number of times to retry Consul returns a 500 error while monitoring "+
f.IntVar(&monitorRetry, "monitor-retry", defaultMonitorRetry,
"Number of times to retry if Consul returns a 500 error while monitoring "+
"the lock. This allows riding out brief periods of unavailability "+
"without causing leader elections, but increases the amount of time "+
"required to detect a lost lock in some cases. The default value is 3, "+
"with a 1s wait between retries. Set this value to 0 to disable retires.")
f.StringVar(&c.name, "name", "",
f.StringVar(&name, "name", "",
"Optional name to associate with the lock session. It not provided, one "+
"is generated based on the provided child command.")
f.BoolVar(&c.passStdin, "pass-stdin", false,
f.BoolVar(&passStdin, "pass-stdin", false,
"Pass stdin to the child process.")
f.DurationVar(&c.timeout, "timeout", 0,
f.DurationVar(&timeout, "timeout", 0,
"Maximum amount of time to wait to acquire the lock, specified as a "+
"timestamp like \"1s\" or \"3h\". The default value is 0.")
f.BoolVar(&c.verbose, "verbose", false,
"Enable verbose (debugging) output.")
// Deprecations
f.DurationVar(&c.timeout, "try", 0,
f.DurationVar(&timeout, "try", 0,
"DEPRECATED. Use -timeout instead.")
if err := c.Meta.Parse(args); err != nil {
if err := c.Command.Parse(args); err != nil {
return 1
}
// Check the limit
if c.limit <= 0 {
if limit <= 0 {
c.Ui.Error(fmt.Sprintf("Lock holder limit must be positive"))
return 1
}
@ -128,27 +129,27 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
prefix = strings.TrimPrefix(prefix, "/")
script := strings.Join(extra[1:], " ")
if c.timeout < 0 {
if timeout < 0 {
c.Ui.Error("Timeout must be positive")
return 1
}
// Calculate a session name if none provided
if c.name == "" {
c.name = fmt.Sprintf("Consul lock for '%s' at '%s'", script, prefix)
if name == "" {
name = fmt.Sprintf("Consul lock for '%s' at '%s'", script, prefix)
}
// Calculate oneshot
oneshot := c.timeout > 0
oneshot := timeout > 0
// Check the retry parameter
if c.monitorRetry < 0 {
if monitorRetry < 0 {
c.Ui.Error("Number for 'monitor-retry' must be >= 0")
return 1
}
// Create and test the HTTP client
client, err := c.Meta.HTTPClient()
client, err := c.Command.HTTPClient()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -160,10 +161,10 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
}
// Setup the lock or semaphore
if c.limit == 1 {
*lu, err = c.setupLock(client, prefix, c.name, oneshot, c.timeout, c.monitorRetry)
if limit == 1 {
*lu, err = c.setupLock(client, prefix, name, oneshot, timeout, monitorRetry)
} else {
*lu, err = c.setupSemaphore(client, c.limit, prefix, c.name, oneshot, c.timeout, c.monitorRetry)
*lu, err = c.setupSemaphore(client, limit, prefix, name, oneshot, timeout, monitorRetry)
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Lock setup failed: %s", err))
@ -195,7 +196,7 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
// Start the child process
childDone = make(chan struct{})
go func() {
if err := c.startChild(script, childDone, c.passStdin); err != nil {
if err := c.startChild(script, childDone, passStdin); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
}
}()

View File

@ -9,15 +9,16 @@ import (
"time"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/base"
"github.com/mitchellh/cli"
)
func testLockCommand(t *testing.T) (*cli.MockUi, *LockCommand) {
ui := new(cli.MockUi)
return ui, &LockCommand{
Meta: Meta{
Command: base.Command{
Ui: ui,
Flags: FlagSetHTTP,
Flags: base.FlagSetHTTP,
},
}
}

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/consul/command"
"github.com/hashicorp/consul/command/agent"
"github.com/hashicorp/consul/command/base"
"github.com/hashicorp/consul/version"
"github.com/mitchellh/cli"
)
@ -31,8 +32,8 @@ func init() {
"configtest": func() (cli.Command, error) {
return &command.ConfigTestCommand{
Meta: command.Meta{
Flags: command.FlagSetNone,
Command: base.Command{
Flags: base.FlagSetNone,
Ui: ui,
},
}, nil
@ -53,8 +54,8 @@ func init() {
"force-leave": func() (cli.Command, error) {
return &command.ForceLeaveCommand{
Meta: command.Meta{
Flags: command.FlagSetHTTP,
Command: base.Command{
Flags: base.FlagSetClientHTTP,
Ui: ui,
},
}, nil
@ -123,8 +124,8 @@ func init() {
"lock": func() (cli.Command, error) {
return &command.LockCommand{
ShutdownCh: makeShutdownCh(),
Meta: command.Meta{
Flags: command.FlagSetHTTP,
Command: base.Command{
Flags: base.FlagSetHTTP,
Ui: ui,
},
}, nil

View File

@ -1,16 +0,0 @@
* `-http-addr=<addr>` - Address of the Consul agent with the port. This can be
an IP address or DNS address, but it must include the port. This can also be
specified via the CONSUL_HTTP_ADDR environment variable. The default value is
127.0.0.1:8500.
* `-datacenter=<name>` - Name of the datacenter to query. If unspecified, the
query will default to the datacenter of the Consul agent at the HTTP address.
* `-token=<value>` - ACL token to use in the request. This can also be specified
via the `CONSUL_HTTP_TOKEN` environment variable. If unspecified, the query
will default to the token of the Consul agent at the HTTP address.
* `-stale` - Permit any Consul server (non-leader) to respond to this request.
This allows for lower latency and higher throughput, but can result in stale
data. This option has no effect on non-read operations. The default value is
false.

View File

@ -0,0 +1,8 @@
* `-http-addr=<addr>` - Address of the Consul agent with the port. This can be
an IP address or DNS address, but it must include the port. This can also be
specified via the `CONSUL_HTTP_ADDR` environment variable. The default value is
127.0.0.1:8500.
* `-token=<value>` - ACL token to use in the request. This can also be specified
via the `CONSUL_HTTP_TOKEN` environment variable. If unspecified, the query
will default to the token of the Consul agent at the HTTP address.

View File

@ -0,0 +1,7 @@
* `-datacenter=<name>` - Name of the datacenter to query. If unspecified, the
query will default to the datacenter of the Consul agent at the HTTP address.
* `-stale` - Permit any Consul server (non-leader) to respond to this request.
This allows for lower latency and higher throughput, but can result in stale
data. This option has no effect on non-read operations. The default value is
false.

View File

@ -28,11 +28,7 @@ as it will be removed from the Raft quorum.
Usage: `consul force-leave [options] node`
The following command-line options are available for this command.
Every option is optional:
#### API Options
* `-rpc-addr` - Address to the RPC server of the agent you want to contact
to send this command. If this isn't specified, the command checks the
`CONSUL_RPC_ADDR` env variable. If this isn't set, the default RPC
address will be set to `127.0.0.1:8400`.
<%= partial "docs/commands/http_api_options_client" %>

View File

@ -17,7 +17,8 @@ Usage: `consul kv delete [options] KEY_OR_PREFIX`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
<%= partial "docs/commands/http_api_options_server" %>
#### KV Delete Options

View File

@ -19,7 +19,8 @@ Usage: `consul kv export [PREFIX]`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
<%= partial "docs/commands/http_api_options_server" %>
## Examples

View File

@ -20,7 +20,8 @@ Usage: `consul kv get [options] [KEY_OR_PREFIX]`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
<%= partial "docs/commands/http_api_options_server" %>
#### KV Get Options

View File

@ -17,7 +17,8 @@ Usage: `consul kv import [DATA]`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
<%= partial "docs/commands/http_api_options_server" %>
## Examples

View File

@ -16,7 +16,8 @@ Usage: `consul kv put [options] KEY [DATA]`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
<%= partial "docs/commands/http_api_options_server" %>
#### KV Put Options

View File

@ -45,11 +45,18 @@ of 5 seconds, a `SIGKILL` will be used to force termination. For Consul agents
on Windows, the child process is always terminated with a `SIGKILL`, since
Windows has no POSIX compatible notion for `SIGTERM`.
The list of available flags are:
#### API Options
* `-http-addr` - Address to the HTTP server of the agent you want to contact
to send this command. If this isn't specified, the command will contact
"127.0.0.1:8500" which is the default HTTP address of a Consul agent.
<%= partial "docs/commands/http_api_options_client" %>
<%= partial "docs/commands/http_api_options_server" %>
#### Command Options
* `-monitor-retry` - Retry up to this number of times if Consul returns a 500 error
while monitoring the lock. This allows riding out brief periods of unavailability
without causing leader elections, but increases the amount of time required
to detect a lost lock in some cases. Defaults to 3, with a 1s wait between retries.
Set to 0 to disable.
* `-n` - Optional, limit of lock holders. Defaults to 1. The underlying
implementation switches from a lock to a semaphore when increased past
@ -58,20 +65,12 @@ The list of available flags are:
* `-name` - Optional name to associate with the underlying session.
If not provided, one is generated based on the child command.
* `-token` - ACL token to use. Defaults to that of agent.
* `-pass-stdin` - Pass stdin to child process.
* `-try` - Attempt to acquire the lock up to the given timeout. The timeout is a
positive decimal number, with unit suffix, such as "500ms". Valid time units
are "ns", "us" (or "µs"), "ms", "s", "m", "h".
* `-monitor-retry` - Retry up to this number of times if Consul returns a 500 error
while monitoring the lock. This allows riding out brief periods of unavailability
without causing leader elections, but increases the amount of time required
to detect a lost lock in some cases. Defaults to 3, with a 1s wait between retries.
Set to 0 to disable.
* `-verbose` - Enables verbose output.
## SHELL

View File

@ -60,7 +60,7 @@ Usage: `consul snapshot agent [options]`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
#### Config File Options:

View File

@ -27,7 +27,7 @@ Usage: `consul snapshot restore [options] FILE`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
## Examples

View File

@ -22,7 +22,7 @@ Usage: `consul snapshot save [options] FILE`
#### API Options
<%= partial "docs/commands/http_api_options" %>
<%= partial "docs/commands/http_api_options_client" %>
## Examples