-
Notifications
You must be signed in to change notification settings - Fork 7
Developer Guide
This page is meant to provide a guideline for developing the IONOS Cloud CLI, a guideline in sync with decisions took inside the SDKs & Tooling Team regarding this product. 🥇
New proposals are always welcomed in order to improve the IONOSCTL!
In order to integrate a new feature in the IONOS Cloud CLI, it is important to understand commands and flags management in the tool.
IONOS Cloud CLI uses cobra framework for creating commands.
It is recommended that the commands in IONOS Cloud CLI to have the following template:
ionosctl <service-namespace> <resource-name> <verb>Example: ionosctl k8s cluster list -> <service-namespace> is k8s, <resource-name> is cluster, <verb> is list.
In order to create a new command, IONOS Cloud CLI uses a core package, which is a wrapper around cobra commands. The wrapper for the cobra.Command is defined in pkg/core/command_builder.go file, in CommandBuilder structure and used in the NewCommand() function (pkg/core/command_runner.go file).
The CommandBuilder structure contains fields similar with the cobra.Command fields (e.g.: short description, long description, example, aliases). Any fields that we want to set at the cobra.Command level (e.g. deprecated) should be added in the CommandBuilder structure and handled in the NewCommand() function.
The cobra.Command.Use of a command is defined in the CommandBuilder.Verb field.
The cobra.Command.PreRun of a command is defined in the CommandBuilder.PreRun field. PreRun function is mostly used for validations of the flags set, before running the command.
The cobra.Command.Run of a command is defined in the CommandBuilder.Run field. Run function is used to indicate what the command is excepted to do, when is used.
IONOS Cloud CLI uses viper framework for managing flags. Flags are a way to display options to the user, in a Command Line Interface tool.
IONOS Cloud CLI has a core package, which is a wrapper around viper support for adding flags.
In the CommandBuilder structure, there are the Namespace, Resource and Verb fields. These fields are determining the command "levels" or "namespaces". In order to make sure we have correctly managed a flag, using viper, we save the flags values with an unique identifier per command.
For a global flag (global flag refers to a flag set at multiple commands, set at a parent command level), the unique identifier has the following format: <verb>.<flag-name>.
For a local flag (local flag refers to a flag set at the command level, set at a child command level), the unique identifier has the following format: <namespace>.<resource>.<verb>.<flag-name>.
Methods such as core.GetGlobalFlagName() and core.GetFlagName() return the expected unique identifiers for flags.
Regarding options for a flag (e.g. deprecated, required), use core.<Option-Name>FlagOption() when adding the flag. Flag options are and they will be defined in the pkg/core/flag.go file.
First step for integration of a new service is to add a wrapper for IONOS Cloud service.
- in the
servicesdirectory, add a new directory named<service-name> - in the
services/<service-name>directory, create a new directory namedresources. Theresourcespackage will contain interfaces for the IONOS Cloud SDKs Go functions, and units tests for that and auto-generated mocks. Each resource methods and interface will be stored in a<resource-name>.gofile. - in the
tools/<service-name>directory, add regenerate_mocks.sh` file - specifying the source files from which mocks will be generated (e.q. mockgen -source .go > mocks/Service.go) - in the
tools/<service-name>directory, add .mk file - adding the command for updating the mocks - in the
services/<service-name>directory, addconstants.gofile - containing constants for default values and for options names and short names for the service commands - in the
services/<service-name>directory, addservices.gofile - initializing all the services defined in the<service-name>/resourcespackage. Add theServicesstruct defined in this file into thepkg/core/command_runner.gofile, in theCommandConfigstructure and initialize it in theNewCommandCfg()function. - in the
services/<service-name>directory, addtest.gofile - initializing mocks for all the services defined in the<service-name>/resourcespackage. Add theResourceMocksstruct defined in this file into thepkg/core/test.gofile, in theResourcesMocksTeststructure and initialize it in theinitMockResources()andinitMockServices()function.
Note: For a new service integration, a corresponding SDK Go for the new service will be used. Add it into the go.mod file and use make vendor.update to update the dependencies.
The next step is to add commands for the new service resources.
- in the
commandsdirectory, add a new directory named<service-name> - in the
commands/<service-name>directory, add<resource-name>.gofile which will contain the commands for that resource. ThePreRunandRunimplementatios will be added. - in the
commands/<service-name>directory, addcompleterdirectory, which contains functions for the completion support of the CLI (e.g.: for filters - if supported by the API, for resources ids) - in the
commands/<service-name>directory, addquerydirectory, which contains functions for the query options of the CLI, for list commands (e.g.: for filters, for depth options) - in the
commands/<service-name>directory, addwaiterdirectory, which contains functions for the waiting options of the CLI, for create/update/delete commands, waiting for requests - if supported by the API, and waiting for AVAILABLE or ACTIVE state, for deletion of the resources, etc. - finally, call
<resource-name>Cmd()function in thecommands/root.gofile, in theaddCommands()function, in order for the commands to be available with theionosctlroot command.
In order to keep a consistent behaviour over the ionosctl commands, please be aware of the following recommendations:
- for the
listcommands, add query parameters support (if available in the API), add options like--depth,--filters,--max-results, etc. - for the
listcommands, add--no-headersoption - this option is especially useful in Bash Scripting - for the
listcommands, it is recommended to have--alloption, for resources that are dependent on other resources (e.g. nodepools, using--all- the user should see a list of all nodepools managed under his account, from all clusters) - for the
getcommands, add--no-headersoption - this option is especially useful in Bash Scripting - for the
create,add,attachcommands, add--wait-for-requestoption - if this is implemented at the API level - for the
updatecommands, add--wait-for-requestoption - if this is implemented at the API level - for the
delete,remove,detachcommands, add--wait-for-requestoption - if this is implemented at the API level - for the
deletecommands, it is recommended to have--alloption, to delete all resources (TBD, regarding consistency between this option and list --all option) - for the
deletecommands, it is recommended to have--wait-for-deletionoption, to wait until the resource no longer exists and a 404 HTTP Status Code is returned - for debug support, add logs in the commands for the properties set
- for easier usage of the tool, consider adding aliases for commands and flags
- for easier usage of the tool, consider adding completion functions for flags
- for easier usage of the tool, for options that involve unit measures, consider supporting multiple unit measures (e.g. for RAM size, MB, GB, etc)
Right now, IONOS Cloud CLI has support for unit tests, using mocks, validating if the options set by the user are correctly sent to the API. Add unit tests for services, for commands, completer, waiter functions.
In the tools/<service-name> directory, in the .mk file add the commands for testing.
In order to generate mocks use:
make mocks_updateNote: when using mocks in unit tests, make sure to specify the expected calls in the correct order, and to give the expected parameters according to what has been set as flags (using viper.Set). The unit tests will run at every CI, and release and they will not create resources on IONOS Cloud.
In order to run the tests use:
make testThe documentation for each command is generated based on the input added in commands/<service-name>/<resource-name>.go file. Usage, short, long descriptions, examples, aliases, options, descriptions for options - are all included in the generated documentation.
In the tools/<service-name> directory, add doc.go file that writes the documentation, that generates the documentation.
In order to generate documentation for a new service, we might need to add updates in the tools/regenerate_doc.sh. The tool generates the documentation and moves, copies the file in specific <service-entire-name> directories. Also, in the tools/<service-name> directory, in the .mk file we need to add the command for updating the documentation.
In order to generate/update documentation use:
make docs_updateAfter generating the files for a new service, update the docs/summary.md file to link the files.
- "format" go imports aligned with best practices, using
goimports -l -wbefore merging (you can usesudo apt install golang-golang-x-toolsfor installation of the tool) - use
feat/fix/doc/testas prefix for new commits and for names in PRs - this helps keeping a clean history of the commits and on the updates when releasing a new version of the tool; - for PRs, use
squash and mergeoption - this helps keeping a clean history of the commits; - for PRs, try to make a PR for a change, not adding multiple changes to the same PR - this helps keeping a transparent history of the updates to the users and other developers as well;
- make sure to follow SonarCloud recommendations when possible;
- consistency over the code base and documents is highly encouraged.
Happy Developing! 🎉
Make sure your repo exists in $GOPATH
Make sure Go Modules integration is enabled in settings (Preferences / Settings | Go | Go Modules),
Disable GOPATH indexing (Preferences / Settings | Go | GOPATH | Index entire GOPATH).
use "Sync Dependencies of ..." quick option on any import