-
Notifications
You must be signed in to change notification settings - Fork 243
Dev: Completion and Validation Architecture
odo provides smart completion of command names, arguments and flags when typing commands. The completion architecture allows for completion handlers (i.e. pieces of code that provide the suggestions to the shell running the command) to be written entirely in go. This document describes the architecture, how to develop new completion handlers and how to activate/deactivate completions for odo.
The completion architecture relies on the posener/complete project. posener/complete relies on providing implementations of their Predictor
interface:
type Predictor interface {
Predict(Args) []string
}
These implementations are then bound to a complete.Command
which describes how commands are supposed to be completed by the shell.
While it is possible to create an external application simply for the purpose of providing completions, it made more sense to have odo itself deal with exposing possible completions. In this case, we need to hook into the cobra
command lifecycle and expose completion information before cobra
takes over. This happens in the main entry point for the odo application. Completion information is provided by the createCompletion
function which walks the cobra
command tree and creates the complete.Command
tree as it goes along, attaching completion handler to commands (for arguments) and flags.
In order to provide this information, odo allows commands to register completion handlers for their arguments and flags using completion.RegisterCommandHandler
and completion.RegisterCommandFlagHandler
respectively. These functions will adapt the contextualized predictor that we expose to commands to the posener/complete Predictor
interface internally.
completion.ContextualizedPredictor
is a function which should return an array of possible completion strings based on the given arguments. It is defined as:
type ContextualizedPredictor func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) []string
This function should be put in pkg/odo/util/completion/completionhandlers.go so that it can be reused across commands.
While filtering is done by posener/complete
itself, automatically removing all completions not prefixed by what you’ve typed already, it might be useful to use the values provided by parsedArgs
to help optimize things to avoid repeating possible completions for example.
The command should register the appropriate completion handler.
-
For argument completion handler:
completion.RegisterCommandHandler(command *cobra.Command, predictor ContextualizedPredictor)
-
For flag completion handler:
completion.RegisterCommandFlagHandler(command *cobra.Command, flag string, predictor ContextualizedPredictor)
Registering the completion handler will make it available for main.createCompletion
which will then automatically create the completion information from it.
User-specified input needs to be validated as early as possible, i.e. before being sent to the remote server so that the user can benefit from fast feedback. odo therefore defines an input validation architecture in order to validate user-specified values.
structs holding user-specified values can extend the Validatable
type (through embedding) to provide metadata to the validation system. In particular, Validatable
fields allow the developer to specify whether a particular value needs to required and which kind of values it accepts so that the validation system can perform basic validation.
Additionally, the developer can specify an array of Validator functions that also need to be applied to the input value. When odo interacts with the user and expects the user to provide values for some parameters, the developer of the associated command can therefore decorate their data structure meant to receive the user input with the Validatable
type so that odo can automatically perform validation of provided values. This is used in conjunction with the survey library we use, to deal with user interaction which allows developer to specify a Validator function to validate values provided by users.
Developers can use the GetValidatorFor
function to have odo automatically create an appropriate validator for the expected value based on metadata provided via Validatable
.
odo provides default validators in the validation package to validate that a value can be converted to an int
(IntegerValidator
), that the value is a valid Kubernetes name (NameValidator
) or a so-called NilValidator
which is a noop validator used as a default validator when none is provided or can be inferred from provided metadata. More validators could be provided, in particular, validators based on Validatable.Type
, see validators.go for all the validators currently implemented by odo.
Validators are defined as follows:
type Validator func(interface{}) error
Therefore, providing new validators is as easy as providing a function taking an interface{}
as parameter and returning a non-nil error if validation failed for any reason. If the value is deemed valid, the function then returns nil
.
If a plugin system is developed for odo, new validators could be a provided through plugins.