Skip to content

Commit 72b3524

Browse files
committed
finish adr 002
1 parent dd38592 commit 72b3524

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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

Comments
 (0)