-
Notifications
You must be signed in to change notification settings - Fork 930
CF CLI Style Guide
This document aims to promote a shared understanding of the user experience for the Cloud Foundry Command Line Interface (cf CLI), so that contributors to the CLI can provide a simple and consistent user experience. The audience for this document includes:
- Developers contributing cf CLI pull requests
- CF/PCF development teams
- cf CLI plug-in authors
- The cf CLI team
This document represents the desired state of the CLI. If existing commands are inconsistent with what is written here, let's discuss whether they can be updated. Going forward, new commands should adhere as closely as possible to these guidelines. To keep plugins consistent with the user experience of the core CLI, plugin authors are encouraged to follow these guidelines as well.
- Mission statement
- General principles
- Implementing commands
- Prefer single positional arguments
- Adding flags to existing commands
- Output formats & exit codes
- Help text
- Miscellaneous
The cf CLI embodies the diversity, friendliness, and openness of the Cloud Foundry Community. The CLI strives to provide a consistent and predictable experience for end users of CF.
The following section describes the patterns we consider to be best practices for the cf CLI user experience. While not every existing command follows all of these patterns, our intent is to revise existing commands and create new commands so that they do.
- Backwards compatibility: When we add functionality to enhance or change existing commands, we must first ensure that functionality is backwards compatible. Do not add functionality that will break existing users without contacting the cf CLI team. Check the latest supported version. Per Semantic Versioning, backwards compatibility should be maintained for minor releases.
- Transparency: Any updates or new commands must be optional, additive, or fully transparent to the user with all changes behind the scenes. They must also provide system feedback.
-
Idempotency: Commands should be idempotent (exits with
0
andOK
when the intended state is unchanged after you run a command. Examples include:-
create-space
(orcreate-route
,create-org
, etc) when the resource already exists -
delete-buildpack
(ordelete
app,delete-org
,delete-private-domain
, etc) when the resource does not exist
-
- Consistency: All create commands, delete commands, list commands, etc., should act in a similar fashion. See Implementing Commands for more information. Targeting requirements should be consistent across new commands.
- Composability: Each command should only perform one function. For example, if it is a linking command, it should only create a link; it should not create additional objects. There may be exceptions, such as the case of creating and uploading a droplet.
The CLI should do whatever it takes to fail in the least amount of time for the end-user. This includes checking the inputs and command setup as well as server-side validation that resources to be acted upon are available/accessible. Some places where client-side validation is relevant are on invalid flags and incorrect command prerequisites. Some places where server-side validation is relevant are on resource unavailable.
When flag combinations are invalid, we prefer to fail fast, printing the error and the command help text. However, there are cases in which we simply can't. For example, consider update-buildpack
. If --path
and --assign-stack
flags are used together, the initial syntax (go flag) validation may succeed because the path provided is valid, but will subsequently fail on semantic flag validation. The guidance is to fail as fast as is practical.
Commands may have prerequisites - for example, the user is required to be logged in, must be targeting an org or space, or must be using an API endpoint of a certain minimum version.
The user should be notified immediately when a prerequisite is not satisfied. Where possible, the command should abort with an error message clarifying which prerequisite was not met and what the user can do about it. The command shall abort with exit code 1
.
Commands may be executed against a resource that's not available - for example, the user executes a command against a resource that doesn't exist within the targeted org/space or to which the user doesn't have the right to access.
The user should be notified immediately when the resource cannot be acted upon. Where possible, the command should abort with an error message clarifying which resource was unavailable. The command shall abort with exit code 1
.
Examples:
-
Scenario: user is not logged in. Most commands require a user to be logged in, with few exceptions such as
cf marketplace
. The user should be immediately informed that they areNot logged in. Use 'cf login' or cf login --sso to log in.
-
Scenario: commands that require the user to be targeting an org and space. When multiple subsequent prerequisites are not met, the user should be provided with that feedback. Most commands require an
org
andspace
to be targeted (Examples of exceptions:feature-flags
,security-groups
). In the case where users are required to be targeted to anorg
orspace
, they should be told to target an org and space - not first be told to target an org, and on the subsequent execution fail again and be told to target a space: -
Where possible, if not all arguments are provided (requirements for running commands), aborting should happen before the initial command feedback text is displayed. For example:
-
Scenario: user misspells resource name(s). The CLI should validate the existence of required resource(s) and abort with immediate feedback when unavailable (especially in cases where a user-prompt/response is required prior to command execution because taking the user all the way through the prompt only to fail subsequently is sub-optimal UX).
We intend to provide users with the following interactions on cf CLI commands:
- Feedback about initiating a process
- Feedback when a process completes
- Quick access to the state of a process
- Quick access to the state of an object
Commands which act on resources that make API calls first give the user feedback that they are about to start something:
- The action about to take place (e.g.
Creating
) - The resource it is taken on (when applicable and in cyan flavor text) (e.g.
space new-space
) - The org and space the resource is in (when applicable and in cyan flavor text) (e.g.
in org some-org
) - The identity (username) assumed on the remote system for the API call (when applicable and in cyan flavor text) (e.g.
as some-user
) - A final
...
to indicate more output is coming.
When commands complete, they should output information according to these rules:
- For create, delete, and update commands, a short
OK
after which the command exits with exit code 0. - Output to indicate a subsequent process or step that is about to be, or should be, initiated to confirm the state of the operation.
- Optionally, a
TIP
to indicate which commands to run next to get more information or perform additional operations on the resource. Add aTIP
if the operation, for some reason, is not as transparent to the user as it should be - an example of this is theupdate-buildpack
command, which unbeknownst to the user, can create a buildpack resource with anull
stack.
Note: Commands that do not act on a resource, (cf login
, for example) do not need to display initial feedback text or echo the resource, org/space and user details. However, they do require the final "..." ending and an OK
to confirm the operation completed successfully.
Example: A command which performs three distinct tasks, with an OK
after each one:
When adding new flags to a command, consider the idea of making impossible states unrepresentable. For example, when we implemented rolling pushes, we added --strategy rolling
rather than --rolling
. This allows us to add more possible values for the --strategy
flag in the future which are mutually exclusive with rolling
. If we had added --rolling
, then we would need to add another flag for the new strategy (--something-else
), and then we would need to add validation to make sure users can't pass mutually exclusive flags together (--rolling --something-else
). It's easier to implement, document, and understand if the flags are already designed to prevent mutually exclusive states.
This can often be thought of as adding an "enum-style" flag (a flag which takes a value), rather than several "boolean-style" flags (since then we need to think about all combinations of presence/absence of the boolean flags).
Most CF CLI commands fall into six types. To give users a predictable and consistent experience, follow these patterns for each command type.
Generally, cf CLI’s commands are named in VERB-NOUN, or ACTION-RESOURCE order.
This allows them to be read like an imperative or request: "Computer, start app!", “Delete app helloworld, please”, “Set space role of [email protected] in myorg and myspace to SpaceManager”, etc.
For CRUD related to resources, we include the resource name in the command -buildpack
, e.g., cf create-buildpack
. (This also ensures command tab completion for cf cr<tab>
takes the user to the long list of "create" commands without needing to enter an additional -[tab]
step.).
The command’s verb should match the action taken.
To make it easy for experienced users to remember command names, new commands should not unnecessarily introduce new verbs when an existing command with a suitable synonymous verb exists.
Commands with opposite actions should use antonyms to make it easy for the user to predict the name of the one command from seeing the other. For example, start
andstop
, bind-service
and unbind-service
, map-route
and unmap-route
, install-plugin
and uninstall-plugin
, etc.
Some flexibility is allowed when a slight variation of the antonym better matches the action, or consequence of an action. For example, reset-space-isolation-segment
clears a previously set isolation segment, which causes app placement to default to the org level configured segment.
Exceptions:
- The noun
app
is omitted from command names when the action is on an app, as the main purpose of the cf CLI is to push and manage apps. The exception is withcreate-app
. - For commands that list resources, or show details of a particular resource, the verb is omitted.
- A few common commands to set up the local environment are single-noun for quick configuration:
api
,target
,config
. -
list-plugin-repos
does not omit "list". Maybe to disambiguate withrepo-plugins
, or simply by error.
Example commands: create-buildpack, create-space, create-user, create-service
Patterns:
- Display the name of the new item to confirm it was created.
- If there are additional actions required after the resource is created, add a
TIP
at the end of the output. - If the resource already exists, the command should return output to say the resource already exists.
Example: Single resource happy path:
Example: Multi-step resource creation (creating and uploading):
Example: The resource already exists:
These commands show information related to a specific resource.
Examples: app
, service
Patterns:
- To present data about the resource, use the key/value pair format.
-
OK
should not be printed, since the printing of the resource summary already indicates success. - Anti-patterns:
target
,api
- where we are not listing a resource.
Example: Showing app summary with process summaries:
These commands show a list of resources, perhaps with some additional information about each resource.
Examples: apps
, marketplace
, services
, orgs
, spaces
Patterns:
- To present a list of resources, use the table format.
- At minimum, display the name of the item to be displayed.
-
OK
should not be printed, since the printing of the table already indicates success. - The list is usually alphabetized.
- When the list is empty, the table (including headers) is replaced with a message like:
No apps found
.
There are exceptions to these patterns which are important to discuss:
-
cf routes --orglevel
will show routes for multiple spaces, and the order of that table is listed by space, not alphabetically. -
cf marketplace --no-plans
hides theplans
column -
cf org-users -a
removes the role-related rows and lists all users in the organization including the OrgUser role
Example: List of apps presented as a table:
Examples: rename
(app), rename-service
, rename-org
, update-service
, update-buildpack
Patterns:
- When renaming, arguments are old name first, then new name.
-
OK
should always be displayed when the update is successful. If not,FAILED
should be displayed.
Examples: delete-service
, unbind-service
, unshare-service
, delete-space
Patterns:
- Provide a prompt to ask the user to confirm the deletion if the deletion is destructive: it recursively deletes resources. If the user enters 'no' at the prompt, confirm to the user that the resource was not deleted.
- Display the name of the item to be deleted.
Examples: bind-service
, share-service
Patterns:
- See the command naming conventions for VERB-NOUN imperatives. Consistency in naming should be adhered to.
- Linking commands should only create a link, it should not create additional objects.
Because it's difficult to remember which argument should be provided first, second, third, and because submitting a command with mis-ordered arguments could have undesired consequences, commands with more than one required input should be designed with a single positional argument.
- The input to choose as the positional argument should be most directly correlated to the command's action - for example, (and this example is aspirational as it's current implementation actually breaks with this recommendation)
cf bind-service
requires both SERVICE_INSTANCE_NAME and APP_NAME. SERVICE_INSTANCE_NAME should be the arg because the action and noun refer directly to the service, not the app - If it wasbind-app
, then it would make sense for APP_NAME to be the arg. - All additional required inputs should be implemented as required flags
- Where a logical sane default for a required flag exists, that flag should be implemented as optional - For example:
cf revision APP_NAME --revision REVISION_NUMBER
can be run without--revision
because returning the app's current deployed revision would be a logical command response when no specific revision was specified. - To migrate an existing command to this pattern, the following steps should be taken:
- Make both work in a minor release of the current cli. Produce a deprecation warning indicating that the next major revision will make a hard cut to the new pattern.
- Hard cut over to the new command format in the next major CLI release.
For information on flag validation, see section about failing fast. For information on flag design, see flag design.
- Args are required (For example,
bind-service
requires the app name and service name) - Flags are optional/supplementary to an arg (For bind-service, you could also provide a
-c
(for json), or--binding-name
flags) - If you are not 100% sure that args are not going to be required in the future, then make them flags - even if they are required right now
- Flags are tab completed; args do not do tab completion
Input file versus raw command line (for cf bind-service
, it takes both a valid JSON object either in-line or in a file. create-security-group
, on the other hand, only takes a file that contains json). Always make it a file for simplicity.
- Command output is first and foremost optimized for human readers.
- Some commands are optimized for scripting (e.g.
ssh-code
) or support flags that optimize output for scripting (e.g.app myapp --guid
). - The v6 CLI strived for the following: information displayed in tables should not change in order or have new columns inserted, so scripts can use tools such as
awk
to parse its output.
All commands should adhere to the following rules for exit codes:
- When a command succeeds without error, the process should exit with code
0
. - When a command fails for any reason (invalid flags, failed API request, etc.), the process should exit with code
1
. - When a multi-step command fails at any step, the process should exit with code
1
. - When an operation is meant to be idempotent (like creating a space that already exists), the process should exit with code
0
.
Roughly, whenever an OK
is printed, the exit code should be 0
. Whenever a FAILED
is printed, the exit code should be 1
. But note that there are success cases (exit code 0
) that do not print OK
.
Avoid removing lines just because they don't have an associated value. Adding/Removing attributes from a key/value table only because it's empty adversely affects a developer's ability to learn about the attributes of an object (e.g., an app). Perhaps knowing the value is empty gives the developer an opportunity to troubleshoot or rectify an issue because they can see an attribute is not defined. Perhaps a developer can discover a feature because the attribute is presented, which gives them an opportunity to explore its purpose.
Use tables when returning a list of multiple objects, possibly with information about those objects in table columns.
Use key/value pairs when returning attributes for one object.
Simple key/value pairs are displayed for one to one or one to many relationships. For many to many relationships, a table is displayed.
When displaying a table:
- Column headers are bold and lowercase (top row)
- Columns are separated by at least three spaces
- If there isn’t a value for a field, it’s left empty
- If there are no values for a table, the table (including headers) is replaced with a message, like
No apps found
.
Example: Table with columns
Example: Empty table with message
When displaying a key/value pair:
- Key value (in left column) is always lowercase
- The value of every field is aligned; columns are separated by at least three spaces from the longest key (such as
requested state
) - If there isn’t a value for a field, the key is displayed but the value is left empty (such as
isolation segment:
andspace quota:
)
The format for displaying time depends on the context: How do we expect the user to be using this information?
Optimized for human readability:
- Shows the time in the CLI user's timezone, localized, with weekday
- Goals: Helps the user answer questions like: Was the app pushed recently? Was it last Friday morning or last month?
-
Location: the
Last timestamp
incf app myapp
output -
Example:
Thu 03 May 16:24:50 PDT 2018
Optimized for machine readability:
- Compact, consistent width, app/server's timezone
- Goals: Help the user compare log time entries, correlate with other logs
-
Location: the
cf logs myapp
output -
Example:
2018-05-08T15:43:12.41-0700
See current time formats as of cf version 6.23.1+a70deb38f.2017-01-13
Some commands use colors to highlight parts of the output. We sometimes refer to the colored text as "flavor text."
Colors can be disabled using a cf config
flag or environment variable (CF_COLOR=false
).
Colors are always disabled when the session is not a TTY session. This allows for the piping of CLI output into other commands (e.g. grep
) without including stray color characters.
Warnings, error messages, TIP
s are in plain text.
Don't add flavor text to anything outside of the following convention:
- In the command intro output text:
resource
to be acted upon (app name, for example),org name
,space name
,role
(admin, for example),user name
are in cyan - After a successful command,
OK
is green - After an error,
FAILED
is red - In
cf app
output,DOWN
is red - In tables, headers are in white and bold
The design team reviewed flavor text and provided the guidance above after a round of research for accessibility. Accessibility is important. Example of color-blind friendly colors in output:
Help text is important for discoverability of commands, args, and flags. Care should be taken to ensure the help text is accurate and clear.
When the cf
command is run without arguments, or with the help
subcommand or -h
, --help
, cf h -a
options, an overview of common app developer commands is returned.
Every CLI command has its own help page, retrievable using cf cmd -h
, cf help cmd
, cf cmd --help
). For information on the help page syntax, see the CF CLI Help Guidelines. They are inspired by man page conventions.
-
The first line returns the command name and version, followed by its short description.
-
The second line returns its Usage. Thereafter, the commands are grouped in sections (Examples, Alias (if applicable), Options, See Also). Each section has a header title displayed in bold and all caps, ending with a colon.
Common global environment variables (if any) are displayed last, for example, see the push
help text.
If required, you may also display a TIP
after the USAGE information (see update-buildpack
) if there are important gotchas that the user should be aware of.
In rare occasions, a WARNING section may appear - see cf login -h
- to alert users of any security-related information they should be aware of.
In cases where a required argument is not provided, if too many args are provided for a command, the CLI will exit immediately and return the help text for the command.
The cf CLI has a policy regarding supporting a range of CF deployment versions here.
Some commands require a minimum version of an API endpoint; these include newer commands such as bind-service
, and assigning a stack to update-buildpack
.
Users are notified when executing a command for a feature not available in their targeted CF endpoint. The CLI aborts with Failed
and exit code 1
, and states the required API version for the command, flag or value specified, and if available and relevant, the current API version. For example, Using FEATURE requires CF API version 3.25.0 or higher. Your target is 3.4.0.
(See buildpacks
functionality for cf push app -b ruby_buildpack -b python_buildpack
for an example of the message.)
For information regarding developing plugins, see developing cf CLI Plugins.