The OpenShift 3 Command Line Interface (CLI) is a set of command-line tools designed for managing OpenShift servers and performing multiple client actions against them.
This document provides information about how to contribute to the CLI. For usage and other end-user information check the official documentation and cli.md.
The OpenShift CLI is distributed as a single binary that can act as a different tool depending on its name and/or symlinks. So if named as (or have a symlink created with the name) oc
it will provide higher-level commands generally targeted for end-users. If renamed to kubectl
, it will provide only the functionality of a kubectl
binary.
Commands are organized in the package structure as:
-
-
pkg/cmd/openshift -
openshift
ororigin
command. -
-
pkg/cmd/infra/deployer -
openshift-deploy
command.
-
-
-
-
pkg/oc/cli -
oc
andkubectl
commands. -
pkg/oc/cli/experimental -
oc ex
command.
-
For every command we have a NewCmd<CommandName>
function that creates the command and returns a pointer to a cobra.Command
, which can later be added to other parent commands to compose the structure tree.
We have a <CommandName>Options
struct with a variable to every flag and argument declared by the command (and any other variable required for the command to run). Each Options
struct has a New<CommandName>Options
function that instantiates the command options struct, and sets any default values for flag fields. When declaring a command’s flags, each flag is bound to its corresponding field in the options struct via the <FlagType>Var
Cobra method. This makes tests and mocking easier. The options struct exposes three functions:
-
Complete
: Completes the struct variables with values that may not be directly provided via flags. Here you will usually take theargs
slice and set the values as appropriate variables, instantiate configs or clients, etc. -
Validate
: performs validation and returns errors. -
Run
: runs the actual command, assuming that the struct is complete with all required values to run, and that they are valid.
For every command that requires printing information to the screen, we provide a "printing stack" which allows every command to handle binding printing flags, printing flag values, and displaying a consistent output format for various operations to the user.
Sample command skeleton:
// MineRecommendedCommandName is the recommended command name
const MineRecommendedCommandName = "mine"
// MineOptions contains all the options for running the mine cli command
type MineOptions struct {
PrintFlags *genericclioptions.PrintFlags // printer flags provide several methods to bind flags and obtain a suitable printer
Printer printers.ResourcePrinter // This field is set in the "Complete" method (usually).
mineLatest bool
genericclioptions.IOStreams // this field is always provided last. It is inlined in the options struct, and set during options instantiation.
}
var (
mineLong = templates.LongDesc(`
Some long description
for my command.`)
mineExample = templates.Examples(`
# Run my command's first action
%[1]s first
# Run my command's second action on latest stuff
%[1]s second --latest`)
)
func NewMineOptions(streams genericclioptions.IOStreams) *MineOptions {
return &MineOptions{
PrintFlags: genericclioptions.NewPrintFlags("this message is printed on command success").WithTypeSetter(scheme.Scheme), // here we instantiate our PrintFlags
mineLatest: true, // perform flag defaulting here
IOStreams: streams,
}
}
// NewCmdMine implement the OpenShift cli mine command.
func NewCmdMine(name, fullName string, f *clientcmd.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewMineOptions(streams)
cmd := &cobra.Command{
Use: fmt.Sprintf("%s [--latest]", name),
Short: "Run my command",
Long: mineLong,
Example: fmt.Sprintf(mineExample, fullName),
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(f, cmd, args))
kcmdutil.CheckErr(o.Validate())
kcmdutil.CheckErr(o.Run())
},
}
// always use <Type>Var cobra methods, as they allow us to bind flag values
// directly to struct fields by passing their address as the first parameter.
cmd.Flags().BoolVar(&o.mineLatest, "latest", o.mineLatest, "Use latest stuff")
// make sure to bind any printing flags if the command makes use of the printing stack
o.PrintFlags.AddFlags(cmd)
return cmd
}
// Complete completes all the required options for mine.
func (o *MineOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
// obtain a printer from our PrintFlags
var err error
o.Printer, err = o.PrintFlags.ToPrinter()
return err
}
// Validate validates all the required options for mine.
func (o *MineOptions) Validate() error {
return nil
}
// Run implements all the necessary functionality for mine.
func (o *MineOptions) Run() error {
return nil
}
When writing a usage string, make sure you cover the most important path for the given command. Use the following conventions:
-
Arguments and flag values names in upper case, e.g.
RESOURCE
,-n NAME
. -
Optional arguments or flags between brackets, e.g.
[RESOURCE]
,[-f FILENAME]
. -
Mutually exclusive required arguments and/or flags with the OR operator, e.g.
--add|--remove|--list
, with parenthesis if they are of mixed types (arguments and flags), e.g.(RESOURCE | -f FILENAME)
. -
If multiple values are supported for a given argument use three dots, e.g.
KEY_1=VAL_1 … KEY_N=VAL_N
. -
Arguments don’t have names, but we have to reference them somehow in usage. Try to be concise with the names already used by the usage of other commands. For example, these are some very recurring names:
BUILD
(meaning a build name or ID),DEPLOYMENT
(meaning a deployment name or ID),RESOURCE
(e.g. pod, pods, replicationcontroller, rc, deploymentconfig, dc, build, etc),NAME
,RESOURCE/NAME
(e.g. pod/mypodname, rc/myrcname, etc),URL
,TEMPLATE
,KEY=VALUE
,FILENAME
and so on.
A few examples:
cancel-build BUILD deploy DEPLOYMENTCONFIG login [URL] edit (RESOURCE/NAME | -f FILENAME) new-app (IMAGE | IMAGESTREAM | TEMPLATE | PATH | URL ...) process (TEMPLATE | -f FILENAME) [-v KEY=VALUE]
Examples must have 2-space tabbing. Always try to have a consistent explanation for every example as a comment (starting with #
). The full command name is parameterized for every example (usually with %[1]s
) so that the examples are still valid if the command is used by different parent commands. Make sure you don’t have a newline character at the end of the string.
Example:
deployExample = templates.Examples(`
# Display the latest deployment for the 'database' deployment config
%[1]s database
# Start a new deployment based on the 'database' deployment config
%[1]s database --latest`)
When introducing modifications to the structure of the commands set (changes in flags, command names, arguments, etc) you may need to update the bash completions files. To check if an update to completions is needed, you can run the command:
$ hack/verify-generated-completions.sh
To update completions, run:
$ hack/update-generated-completions.sh
In case you need additional control over how flags behave in terms of code completion, there are some helper functions:
|
allows the given flag to autocomplete as a path to file or directory. |
|
consider the given file extensions when doing autocomplete. |
|
mark a flag as required. |
When delcaring the Run:
field in the cobra comand, make sure to call the Complete
, Validate
, Run
methods
within the k8s.io/kubernetes/pkg/kubectl/cmd/util#CheckErr
helper, which will take care of exiting with the correct
exit code in the event of an error:
cmd := &cobra.Command{
Use: "foo [flags]",
Short: "short command description",
Long: descLong,
Example: fmt.Sprintf(fooExample, fullName),
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(f, cmd, args))
kcmdutil.CheckErr(o.Validate())
kcmdutil.CheckErr(o.Run())
},
}
There are a number of helper functions available in cmdutil
and kcmdutil
. Import them with:
import (
// other imports...
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
cmdutil "github.com/openshift/oc/pkg/helpers/cmd"
)
Examples:
|
handles an error (check for |
|
gets the instance of a declared flag, by type. If possible, use the struct var binding to get flag values instead. |
|
checks if the given |
Taking the oc deploy
command as an example, the code structure for a command will usually look like the one below.
// 1.
type DeployOptions struct {
PrintFlags *genericclioptions.PrintFlags
Printer printers.ResourcePrinter
// other fields...
deployLatest bool
retryDeploy bool
// inlined IOStreams provide standard error, standard out, and standard input streams
genericclioptions.IOStreams
}
var (
// 2.
deployLong = templates.LongDesc(`
Some long description
for the deploy command.`)
// 3.
deployExample = templates.Examples(`
# Display the latest deployment for the 'database' DeploymentConfig
%[1]s database
# Start a new deployment based on the 'database' DeploymentConfig
%[1]s database --latest`)
)
// 4
func NewDeployOptions(streams genericclioptions.IOStreams) *DeployOptions {
return &DeployOptions{
PrintFlags: genericclioptions.NewPrintFlags("deployed").WithTypeSetter(scheme.Scheme),
IOStreams: streams,
}
}
// 5
func NewCmdDeploy(name, fullName string, f *clientcmd.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewDeployOptions(streams)
cmd := &cobra.Command{
// 6.
Use: fmt.Sprintf("%s DEPLOYMENTCONFIG", name),
Short: "View, start, cancel, or retry deployments",
Long: deployLong,
Example: fmt.Sprintf(deployExample, fullName),
Run: func(cmd *cobra.Command, args []string) {
// 7.
kcmdutil.CheckErr(o.Complete(f, cmd, args))
// 8.
kcmdutil.CheckErr(o.Validate())
// 9.
kcmdutil.CheckErr(o.Run())
},
}
cmd.Flags().BoolVar(&options.deployLatest, "latest", false, "Start a new deployment now.")
cmd.Flags().BoolVar(&options.retryDeploy, "retry", false, "Retry the latest failed deployment.")
// 10.
o.PrintFlags.AddFlags(cmd)
return cmd
}
func (o *DeployOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
// 11.
var err error
o.Printer, err = o.PrintFlags.ToPrinter()
return err
}
func (o DeployOptions) Validate() error {
return nil
}
func (o DeployOptions) Run() error {
return nil
}
-
Create a struct to contain vars for every flag declared (and other vars that the command may need). This struct will usually have the
Complete
,Validate
andRun<Command>
methods (explained below). -
Multiple lines describing the command.
-
Command examples. Try to cover every important command path (flags, arguments, etc).
-
Create a "constructor" for the command options struct. Here you will instantiate command options, default any values, and set the IO streams for writing to the screen.
-
This function creates the command. Notice it takes the parent command name as argument and also a
io.Writer
that will be used to print messages. -
Command usage.
-
Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error
is used to populate any object or variable that will be required to run the command and is still missing at this point. For example, if the command will make use of an API client it can be created from the factory in this method. Can also be used to take argument values from theargs
slice and hold it in explicit variables in your struct, store theio.Writer
that will be used later, etc. -
Validate() error
perform validations on anything required in order to run this command. Notice that if theComplete
andValidate
methods implementations are simple enough, you may have only one of them that does both. -
Run() error
does the actual command logic and returns errors as required. Notice that this method does not take anything as argument - it’s expected that you previously extracted and stored in thestruct
anything that will be needed to run this command. This makes commands more easily testable once you can run and populate the command struct with the values you want to test and then just run this method and check for the returned error(s). Try to always use the functions ink8s.io/kubernetes/pkg/kubectl/cmd/util
to check and handle errors. It is not expected that commands callglog.Fatalf
,os.Exit
or anything similar directly. -
Always remember to bind printer-related flags, if your command makes use of the printing stack.
-
Similarly, remember to retrieve a valid printer from the printer-related flags struct in your
Complete
method. The printer obtained in this step will always be the correct printer based on the output-format specified by the user.