This guide described the high-level architectural concepts of the Knative CLI client kn
.
It is for all developers who contribute to kn
and want to understand some essential concepts.
For any contributor to kn
, it is highly recommended reading this document.
This guide is not complete and has still many gaps that we are going to fill over timer. Contributions are highly appreciated ;-)
The journey starts at main.go which is the entry point when you call kn
.
You find here the main control flow, which can be roughly divided into three phases:
- Bootstrap is about retrieving essential configuration parameters from command-line flags, and a configuration file.
- Plugin Lookup finds out whether the user wants to execute a plugin or a built-in command
- Execution : Execute a plugin or the
RootCommand
for built-in commands
Only the code in the main
function can use os.Exit
for leaving the process.
It evaluates any error that bubbles up and prints it out as an error message before exiting.
There is no exception to this rule.
Let's talk now about the three phases separately.
The bootstrap performed by config.BootstrapConfig() extracts all the options relevant for config file detection and plugin configuration.
The bootstrap process does not fully parse all arguments but only those that are relevant for starting up and for looking up any plugin.
The configuration can be either provided via a --config
flag or is picked up from a default location.
The default configuration location conforms to the XDG Base Directory Specification and is different for Unix systems and Windows systems.
Viper loads the configuration file (if any) form this location, and the config
package makes all the bootstrap and other configuration information available vial the config.GlobalConfig
singleton (which btw can be easily mocked for unit tests by just replacing it with another implementation of config.Config
interface)
In the next step, a PluginManager
checks whether the given command-line arguments are pointing to a plugin.
All non-flag arguments are extracted and then used to lookup via plugin.PluginManager.FindPlugin() in the plugin directory (and the execution $PATH
if configured) calculated in the Bootstrap phase.
If kn
detects a plugin, it is first validated and then executed.
If not, the flow executes the RootCommand
which carries all builtin kn
commands.
However, before kn
executes a plugin, it is first validated whether it does not clash with a builtin command or command group.
If the validation succeeds, the PluginManager
calls out to the identified plugin via the Plugin.Execute()
interface method.
If kn
does not detect a plugin, the RootCommand
is executed, which is internally dispatched, based on the given commands, to the matching sub-command.
In the case that no sub-command matches the provided command arguments on the command line, kn throws an error.
For any command that is executed by kn
, the configuration can come from two places:
- Arguments and flags as provided on the command line
- Configuration stored in a configuration file
kn
has a bootstrap configuration which consists of these variables:
configFile
is the location of the configuration file which by default is~/.config/kn/config.yaml
on Unix like systems and%APPDATA%\kn
on Windows of now overridden with the environment variableXDG_CONFIG_HOME
. It can be specified on the command line with--config
.pluginsDir
is the location of the directory holdingkn
plugins. By default, this is${configFile}/plugins
. The default can be overridden in with the fieldplugins.directory
in the configuration file.lookupPluginsInPath
is a boolean flag that, when set totrue
, specifies that plugins should also be looked up in the execution$PATH
(default:false
). Useplugins.lookup-path
in the configuration file to override it.
The main flow calls out to config.BootstrapConfig()
to set these three variables by evaluating the location of the configuration file first and then reading in the configuration.
All other configuration that is stored in the configuration is also available after the bootstrap.
To access this information, any other code can access the methods of config.GlobalConfig
which holds an implementation of the config.Config
interface.
For testing purposes, config.GlobalConfig
can be replaced by a mock implementation while running a test. Use config.TestConfig
for simple use cases.
This section will hold some information about how flag parsing of commands is supposed to work, including using helper functions for enforcing the conventions defined in cli conventions
In this section we are going to describe how commands can access the backend via the kn
public API which provides simplified access mechanism to the Knative serving and eventing backends
Here is a rough overview (as of 2020-06) of the directories hierarchy and packages that organized the kn
codebase.
This directory holds the main
package and is the entry point for calling kn
.
The main
package is responsible for the overall flow as described above in the section "Flow".
Potential future CLI commands should be added on the cmd/
level.
The directory kn
holds the code that is specific to the kn
builtin commands.
Everything below pkg/kn
is not supposed to be shared outside of knative/client
.
The following essential packages/directories are stored here:
root
is the package that defines theRootCommand
which is the top-level cobra command. TheRootCommand
contains all other command groups and leaf commands.commands
is a directory that contains allkn
command definitions that are registered at theRootCommand
. Here, we distinguish two kinds of commands:- Command Groups have sub-commands but no code to run on its own. Example:
kn service
- Leaf Commands have no sub-commands and execute the business logic. Example:
kn service create
- Command Groups have sub-commands but no code to run on its own. Example:
config
is the package for all global configuration handling. The global configuration allows access to the kn configuration file and all top-level options that apply to all commands.flags
is the package for all flag-parsing utility methods shared across all commands. Some of its content might end up in thekn public API
packages (see below for more about thekn
public API).plugin
has the code for finding, listing and executing plugins.traffic
has functions for dealing with traffic split.
Besides the pkg/kn
packages, pkg
contains packages that are the current public API of kn
for reuse outside the knative/client
project.
In the future, these packages are grouped more explicitly in a "public API" package (see below for more on information on this).
The following packages provide simplified access to the corresponding backend API and add some extra functionality like synchronous wait for the completion of an operation like creating a Knative service.
serving
interface to Knative Serving with support for synchronous operationseventing
interface for access Knative Eventing backend objects like Triggersdynamic
is a dynamic client for interacting with the backend in a typeless waysources
has the client access code to the built-in sourceApiServerSource
,PingSource
andSinkBinding
.
These packages also determine the API version used for the backend APIs which typically over multiple API versions. All packages also contain a record-replay style mocking library to be used in Mock testing.
Finally, pkg
contains also some miscellaneous package for different purposes.
Some of them might end up in the public API, too.
util
: Kitchen-sink for everything that does not find in any other category. Please try to avoid adding files here except for good reasons.wait
: Helper methods for implementing synchronous calls to backendsprinters
: Helper for printing out CLI output in a kn specific way
You can find the central plugin handling in the package pkg/kn/plugin
.
The entry point here is the PluginManager
, which is responsible for finding plugins by name and for listing all visible plugins.
An interface Plugin
describes a plugin.
This interface has currently a single implementation that uses an exec
system call to run an external plugin command following a naming convention.
Only the main()
flow looks up and evaluates plugins, except the commands from the plugin
group.
Describe here the public API package that we also expose to external clients, like e.g. plugins. This public API package is not available yet but will contain kn
abstraction for accessing backend APIs (serving, eventing, sources, dynamic), the flag-parsing library for enforcing the CLI conventions and any other (utility) code to share with other parties
Describe here the way how our mock testing framework works and that is the preferred way of testing and newly written tests are supposed to use the mock testing style
Fake testing uses the auto-generated fake services provided by the client libraries. Some older tests use these directly but also tests involving the dynamic client. This test couples the code tightly to the external client's API and its version, which we try to avoid so that all our code goes through the client-side APIs that we provide.
Explain Fake testing more in this section