|
| 1 | +# Command structure standards |
| 2 | + |
| 3 | +Creation date: 2025.11.21 |
| 4 | + |
| 5 | +## Description |
| 6 | + |
| 7 | +I would collect and propose all requirements and good practices that all new commands should meet before moving them out of the `alpha` group. |
| 8 | + |
| 9 | +This document is focused on what users see, and it's not about command functionalities. |
| 10 | + |
| 11 | +## Name |
| 12 | + |
| 13 | +The command's name field is defined under the `cobra.Command{}.Use` field. This field must contain the command name and the symbolic visualisation of allowed arguments and flags. |
| 14 | + |
| 15 | +### Names in practice |
| 16 | + |
| 17 | +Command groups: |
| 18 | + |
| 19 | +```yaml |
| 20 | +function <command> [flags] # function command group that is not runnable and can receive optional flags |
| 21 | +``` |
| 22 | + |
| 23 | +Runnable commands: |
| 24 | + |
| 25 | +```yaml |
| 26 | +explain [flags] # runnable command without argument |
| 27 | +create <name> [flags] # runnable command that receive one required arg and optional flags |
| 28 | +scale <name> <replicas> [flags] # runnable command that can receive two arguments and optional flags |
| 29 | +delete <name\s> # runnable command that receive at least one argument and has no flags |
| 30 | +get [<name>] [flags] # runnable command that receive optional one argument and optional flags |
| 31 | +``` |
| 32 | + |
| 33 | +### Name details |
| 34 | + |
| 35 | +The command name must be a verb or noun. In most cases, it is recommended to use nouns to define domain-related command groups (like `kyma function`, `kyma app`, or `kyma apirule`) and verbs to define runnable operations around the domain (like `kyma app push`, `kyma function create`, `kyma apirule expose`). This rule is only a suggestion, and it depends on the use case. For example, the `kyma diagnose` command works as a runnable command and command group at the same (`kyma diagnose logs`) time, and because of this, it's not a noun. On the other hand, the `kyma registry config` in another example, where after the noun is another noun (runnable noun), but in this case, the `config` word is shorter than `get-config`. |
| 36 | + |
| 37 | +After the first word, there must be a description of possible arguments/commands. If the command is a non-runnable command group, then it should contain `<command>`, which means that this command accepts only one argument, and this argument is the command name. If command is runnable then is must describes possible inputs (if allowed) in the following format: `<arg_name>` for single, required argument, `<arg_name/s>` for at least one required argument, `[<arg_name>]` for one optional argument, `[<arg_name/s>]` for optional arguments list of the same type. If the command receives more than one argument type, then it is possible to describe many arguments separated by a space. For example: `scale <name> <replicas>` |
| 38 | + |
| 39 | +The last element must be optional flags represented by the `[flags]` element. In our case, it must be a part of every command because we add persistent flags for the parent `kyma` command, and these flags will be valid for every sub-command. |
| 40 | + |
| 41 | +## Descriptions |
| 42 | + |
| 43 | +There are two types of description under the `cobra.Command{}.Short` and the `cobra.Command{}.Long` fields. The first one represents a shorter description that will be shown when running parent commands help, and the second one when running the current command. |
| 44 | + |
| 45 | +### Descriptions in practice |
| 46 | + |
| 47 | +For the long and short description: |
| 48 | + |
| 49 | +```yaml |
| 50 | +Lists modules catalog # short description of the catalog command |
| 51 | +Use this command to list all available Kyma modules. # long description of the catalog command |
| 52 | +``` |
| 53 | + |
| 54 | +The parents help: |
| 55 | + |
| 56 | +```bash |
| 57 | +kyma module --help |
| 58 | + |
| 59 | +Use this command to manage modules in the Kyma cluster. |
| 60 | + |
| 61 | +Usage: |
| 62 | + kyma module [command] |
| 63 | + |
| 64 | +Available Commands: |
| 65 | + catalog Lists modules catalog # short description of the catalog command |
| 66 | +``` |
| 67 | + |
| 68 | +The commands help: |
| 69 | + |
| 70 | +```bash |
| 71 | +kyma module catalog --help |
| 72 | + |
| 73 | +Use this command to list all available Kyma modules. # long description of the catalog command |
| 74 | + |
| 75 | +Usage: |
| 76 | + kyma module catalog [flags] |
| 77 | +``` |
| 78 | + |
| 79 | +### Description details |
| 80 | + |
| 81 | +The short desc should help users choose the right sub-command in the context of the domain they are in. This description must start with a huge letter and end without a period. |
| 82 | + |
| 83 | +The long one describes what exactly the command is doing. It can be multiline, describing all use-cases. It must start with a huge letter and always end with a period. |
| 84 | + |
| 85 | +## Flags |
| 86 | + |
| 87 | +Flags are elements of the command that can be added using the `cobra.Command{}.Flags()` function. |
| 88 | + |
| 89 | +### Flags in practice |
| 90 | + |
| 91 | +For flags configuration: |
| 92 | + |
| 93 | +```golang |
| 94 | +cmd.Flags().StringVarP(&cfg.channel, "channel", "c", "fast", "Name of the Kyma channel to use for the module") |
| 95 | +cmd.Flags().StringVar(&cfg.crPath, "cr-path", "", "Path to the custom resource file") |
| 96 | +cmd.Flags().BoolVar(&cfg.defaultCR, "default-cr", false, "Deploys the module with the default CR") |
| 97 | +cmd.Flags().BoolVar(&cfg.autoApprove, "auto-approve", false, "Automatically approve community module installation") |
| 98 | +cmd.Flags().StringVar(&cfg.version, "version", "", "Specifies version of the community module to install") |
| 99 | +cmd.Flags().StringVar(&cfg.modulePath, "origin", "", "Specifies the source of the module (kyma or custom name)") |
| 100 | +_ = cmd.Flags().MarkHidden("origin") |
| 101 | +cmd.Flags().BoolVar(&cfg.community, "community", false, "Install a community module (no official support, no binding SLA)") |
| 102 | +_ = cmd.Flags().MarkHidden("community") |
| 103 | +``` |
| 104 | + |
| 105 | +The commands help looks: |
| 106 | + |
| 107 | +```bash |
| 108 | +kyma module add --help |
| 109 | + |
| 110 | +... |
| 111 | + |
| 112 | +Flags: |
| 113 | + --auto-approve Automatically approve community module installation |
| 114 | + -c, --channel string Name of the Kyma channel to use for the module (default "fast") |
| 115 | + --cr-path string Path to the custom resource file |
| 116 | + --default-cr Deploys the module with the default CR |
| 117 | + --version string Specifies version of the community module to install |
| 118 | +``` |
| 119 | + |
| 120 | +### Flags details |
| 121 | + |
| 122 | +The most important thing from the user perspective is flag description, defaulting and validation. |
| 123 | + |
| 124 | +The description should be as minimalistic as possible, but describe for which flag it can be used. If the flag is related to only one command's use case, then it's a good opportunity to add example usage of such a command (read more in the [Examples](#examples) section). Every description should start with a huge letter and end with no period. |
| 125 | + |
| 126 | +If possible, every flag should have its own default value passed to the flag building function (to display it in commands' help). |
| 127 | + |
| 128 | +Flag shorthand should be added only to flags that are often used in most cases to speed up typing. The shorthand should be the first letter of the full flag name (for example, `-c` for `--channel`). |
| 129 | + |
| 130 | +Flags can be marked as hidden. This functionality may be helpful while some flags are deprecated and it's functionality is removed or moved. Hiding the flag allows us to validate later if the user uses it and display a well-crafted error with detailed information on what is going on and where such functionality was moved to. |
| 131 | + |
| 132 | +To validate flags, the [flags](../../../internal/flags/validate.go) package must be used to keep all validations of all commands in the same shape. Functionality of this package can be run in the `cobra.Command{}.PreRun`. |
| 133 | + |
| 134 | +## Examples |
| 135 | + |
| 136 | +Examples is an optional field that is highly recommended to use. It's under the `cobra.Command{}.Examples` field and can be used to propose the most common use cases or propositions of flag usage. |
| 137 | + |
| 138 | +### Examples in practice |
| 139 | + |
| 140 | +For the following examples: |
| 141 | + |
| 142 | +```yaml |
| 143 | + # Add a Kyma module with the default CR |
| 144 | + kyma module add kyma-module --default-cr |
| 145 | + |
| 146 | + # Add a Kyma module with a custom CR from a file |
| 147 | + kyma module add kyma-module --cr-path ./kyma-module-cr.yaml |
| 148 | + |
| 149 | + ## Add a community module with a default CR and auto-approve the SLA |
| 150 | + # passed argument must be in the format <namespace>/<module-template-name> |
| 151 | + kyma module add my-namespace/my-module-template-name --default-cr --auto-approve |
| 152 | +``` |
| 153 | + |
| 154 | +The help will displays: |
| 155 | + |
| 156 | +```bash |
| 157 | +kyma module add --help |
| 158 | + |
| 159 | +Use this command to add a module. |
| 160 | + |
| 161 | +Usage: |
| 162 | + kyma module add <module> [flags] |
| 163 | + |
| 164 | +Examples: |
| 165 | + # Add a Kyma module with the default CR |
| 166 | + kyma module add kyma-module --default-cr |
| 167 | + |
| 168 | + # Add a Kyma module with a custom CR from a file |
| 169 | + kyma module add kyma-module --cr-path ./kyma-module-cr.yaml |
| 170 | + |
| 171 | + ## Add a community module with a default CR and auto-approve the SLA |
| 172 | + # passed argument must be in the format <namespace>/<module-template-name> |
| 173 | + kyma module add my-namespace/my-module-template-name --default-cr --auto-approve |
| 174 | +``` |
| 175 | + |
| 176 | +### Examples details |
| 177 | + |
| 178 | +Every line of the examples must start with double spaces to display them correctly in the terminal. |
| 179 | + |
| 180 | +All examples for one command must be separated with an empty line and have their own description starting from the `#` symbol. If the example description is longer than one line, then the first line should start with the `##` prefix, and then every next line should start with `#` and two spaces after. |
| 181 | + |
| 182 | +Examples should reflect real use cases, so if possible, they should use real data as arguments and flags if possible. If not, then use fiction one representing real data, like `my-module`, `my-resource`, `my-something`. |
| 183 | + |
| 184 | +## Aliases |
| 185 | + |
| 186 | +The alias is an array table in the `cobra.Command{}.Aliases`, and there are no specific requirements about command aliases. The good idea is always to use this functionality to provide a shorthand of the command (`del` for `delete` command), or a word form that can help with avoiding small typos (`modules` for `module` command). |
| 187 | + |
| 188 | +### Aliases in practice |
| 189 | + |
| 190 | +```go |
| 191 | +cmd := &cobra.Command{ |
| 192 | + Use: "delete <module> [flags]", |
| 193 | + Aliases: []string{"del"}, |
| 194 | +} |
| 195 | +``` |
| 196 | + |
| 197 | +## Errors&Hints |
| 198 | + |
| 199 | +Errors were proposed in the [ADR 001](001-error-output-format.md) proposal and implemented quite after that. This functionality allows users to understand what is going on on three levels of abstraction. The general message called `Error` should contain the last, user-understandable operation that fails. The second thing is `Error Details` that can contain an internal error message generated from the library or called server. The `Hints` section is something that should help users figure out how the problem can be fixed. Every hint should be in one of two formats: |
| 200 | + |
| 201 | +* `to <what>, <do>` - format used to describe possible optional configurations that may be used or misconfigured |
| 202 | +* `make sure <what>` - format used to describe the required configuration user may have misconfigured |
| 203 | + |
| 204 | +The cli is designed to always return `clierror.Error` instead of pure `error`. Both errors are not compatible with each other, and to avoid user confusion, we should not use the `cobra.Command{}.RunE` and instead of that use the `cobra.Command{}.Run` and check the error manually inside of it: |
| 205 | + |
| 206 | +```go |
| 207 | +cmd := &cobra.Command{ |
| 208 | + Run: func(cmd *cobra.Command, _ []string) { |
| 209 | + clierror.Check(runAdd(&cfg)) // check manually using the `clierror.Check` function |
| 210 | + }, |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +### Example hints |
| 215 | + |
| 216 | +```yaml |
| 217 | +"make sure you provide a valid module name and channel (or version)", |
| 218 | +"to list available modules, call the `kyma module catalog` command", |
| 219 | +"to pull available modules, call the `kyma module pull` command", |
| 220 | +"to add a community module, use the `--origin` flag", |
| 221 | +``` |
| 222 | + |
| 223 | +## Command messaging |
| 224 | + |
| 225 | +It's not allowed to use the `os.Stdout`/`os.Stderr` and `fmt.Print` functionalities. Instead of that we must use the [out](../../../internal/out/out.go) package that provide centralized control over writers. |
| 226 | + |
| 227 | +## Example command in code |
| 228 | + |
| 229 | +The command configuration below applies all rules described above: |
| 230 | + |
| 231 | +```go |
| 232 | +func newCMD() *cobra.Command { |
| 233 | + cfg := addConfig{ |
| 234 | + KymaConfig: kymaConfig, |
| 235 | + } |
| 236 | + |
| 237 | + cmd := &cobra.Command{ |
| 238 | + Use: "add <module> [flags]", |
| 239 | + Short: "Add a module", |
| 240 | + Long: "Use this command to add a module.", |
| 241 | + Example: ` # Add a Kyma module with the default CR |
| 242 | + kyma module add kyma-module --default-cr |
| 243 | +
|
| 244 | + # Add a Kyma module with a custom CR from a file |
| 245 | + kyma module add kyma-module --cr-path ./kyma-module-cr.yaml |
| 246 | +
|
| 247 | + ## Add a community module with a default CR and auto-approve the SLA |
| 248 | + # passed argument must be in the format <namespace>/<module-template-name> |
| 249 | + kyma module add my-namespace/my-module-template-name --default-cr --auto-approve`, |
| 250 | + |
| 251 | + Args: cobra.ExactArgs(1), |
| 252 | + PreRun: func(cmd *cobra.Command, _ []string) { |
| 253 | + clierror.Check(flags.Validate(cmd.Flags(), |
| 254 | + flags.MarkMutuallyExclusive("cr-path", "default-cr"), |
| 255 | + flags.MarkUnsupported("community", "the --community flag is no longer supported - community modules need to be pulled first using 'kyma module pull' command, then installed"), |
| 256 | + flags.MarkUnsupported("origin", "the --origin flag is no longer supported - use commands argument instead"), |
| 257 | + )) |
| 258 | + }, |
| 259 | + Run: func(cmd *cobra.Command, args []string) { |
| 260 | + cfg.complete(args) |
| 261 | + clierror.Check(runAdd(&cfg)) |
| 262 | + }, |
| 263 | + } |
| 264 | + |
| 265 | + cmd.Flags().StringVarP(&cfg.channel, "channel", "c", "", "Name of the Kyma channel to use for the module") |
| 266 | + cmd.Flags().StringVar(&cfg.crPath, "cr-path", "", "Path to the custom resource file") |
| 267 | + cmd.Flags().BoolVar(&cfg.defaultCR, "default-cr", false, "Deploys the module with the default CR") |
| 268 | + cmd.Flags().BoolVar(&cfg.autoApprove, "auto-approve", false, "Automatically approve community module installation") |
| 269 | + cmd.Flags().StringVar(&cfg.version, "version", "", "Specifies version of the community module to install") |
| 270 | + cmd.Flags().StringVar(&cfg.modulePath, "origin", "", "Specifies the source of the module (kyma or custom name)") |
| 271 | + _ = cmd.Flags().MarkHidden("origin") |
| 272 | + cmd.Flags().BoolVar(&cfg.community, "community", false, "Install a community module (no official support, no binding SLA)") |
| 273 | + _ = cmd.Flags().MarkHidden("community") |
| 274 | + |
| 275 | + return cmd |
| 276 | +} |
| 277 | +``` |
0 commit comments