Skip to content

Dev: Completion and Validation Architecture

Charlie Drage edited this page Feb 1, 2022 · 1 revision

Completion 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.

Architecture

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.

Create a new completion handler

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.

Register the completion handler

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.


Validation Architecture

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.

Architecture

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.

Default validators

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.

Creating a validator

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.