-
Notifications
You must be signed in to change notification settings - Fork 6
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
services
directory, add a new directory named<service-name>
- in the
services/<service-name>
directory, create a new directory namedresources
. Theresources
package 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>.go
file. - 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.go
file - containing constants for default values and for options names and short names for the service commands - in the
services/<service-name>
directory, addservices.go
file - initializing all the services defined in the<service-name>/resources
package. Add theServices
struct defined in this file into thepkg/core/command_runner.go
file, in theCommandConfig
structure and initialize it in theNewCommandCfg()
function. - in the
services/<service-name>
directory, addtest.go
file - initializing mocks for all the services defined in the<service-name>/resources
package. Add theResourceMocks
struct defined in this file into thepkg/core/test.go
file, in theResourcesMocksTest
structure 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
commands
directory, add a new directory named<service-name>
- in the
commands/<service-name>
directory, add<resource-name>.go
file which will contain the commands for that resource. ThePreRun
andRun
implementatios will be added. - in the
commands/<service-name>
directory, addcompleter
directory, 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, addquery
directory, 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, addwaiter
directory, 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.go
file, in theaddCommands()
function, in order for the commands to be available with theionosctl
root command.
In order to keep a consistent behaviour over the ionosctl
commands, please be aware of the following recommendations:
- for the
list
commands, add query parameters support (if available in the API), add options like--depth
,--filters
,--max-results
, etc. - for the
list
commands, add--no-headers
option - this option is especially useful in Bash Scripting - for the
list
commands, it is recommended to have--all
option, 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
get
commands, add--no-headers
option - this option is especially useful in Bash Scripting - for the
create
,add
,attach
commands, add--wait-for-request
option - if this is implemented at the API level - for the
update
commands, add--wait-for-request
option - if this is implemented at the API level - for the
delete
,remove
,detach
commands, add--wait-for-request
option - if this is implemented at the API level - for the
delete
commands, it is recommended to have--all
option, to delete all resources (TBD, regarding consistency between this option and list --all option) - for the
delete
commands, it is recommended to have--wait-for-deletion
option, 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_update
Note: 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 test
The 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_update
After 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 -w
before merging (you can usesudo apt install golang-golang-x-tools
for installation of the tool) - use
feat/fix/doc/test
as 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 merge
option - 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