The Tanzu CLI was built to be extensible across teams and cohesive across SKUs. To this end, the Tanzu CLI provides tools to make creating and compiling new plugins straightforward.
Before embarking on developing a plugin, the developer should be familiar with the following materials:
- The key concepts related to the Tanzu CLI
- The CLI plugin architecture
- The Tanzu CLI Styleguide, which describes the user interaction best practices to be followed. It is highly recommended that anyone interested in developing a Tanzu CLI plugin be familiarized with the recommendations.
- The build stage and design stage style guide checklists are useful resources to refer to maximize UX consistency in the plugin being developed.
This document will primarily focus on setting up a development environment to build and publish plugins.
For plugin projects implemented based on the legacy Tanzu CLI codebase, some minor adjustments will have to be made to account for the new code dependencies and plugin publishing process. See the transition guide for more details.
The Tanzu Plugin Runtime (also referred to as "runtime library" or simply "runtime") is a library the plugin implementation should integrate with to implement the plugin contract.
The Tanzu Core CLI and runtime library are written in the Go programming language. While the plugin architecture technically does not prevent the development of plugins using other programming languages, at the moment the only supported means of plugin development is that which integrates with the released version of the runtime library.
The minimum supported Go version is 1.18.
You will need Docker to be installed on your development system.
Note for Mac developers on Apple Silicon machines: Tanzu CLI does not yet officially support arm64-based binaries. To develop on these machines ensure that the amd64 version of the Go toolchain is installed.
Some CLI functionality essential for plugin development are available as Tanzu CLI plugins. These are termed "admin plugins" for the rest of the document.
The easiest way to bootstrap a new plugin project is to make use of the
builder
admin plugin. This plugin provides commands to construct scaffolding
for plugins and plugin commands along with removing the need to write boilerplate
code. Use one of the following method to install the builder plugin.
tanzu plugin install builder
For more details on the builder plugin, see the command reference
tanzu builder init <repo-name>
either specify the --repo-type
or be prompted to choose between GitLab or
GitHub type repository. The choice will determine the type of skeleton CI
configuration file generated.
cd <repo-name> && tanzu builder cli add-plugin <plugin-name>
will add a main
package for the new plugin. You should now adjust the
newly created main
package to implement the functionality of your new plugin.
You will notice in the generated main.go
file, that CLI plugins have to instantiate a
Plugin Descriptor
import (
"github.com/vmware-tanzu/tanzu-plugin-runtime/plugin/buildinfo"
"github.com/vmware-tanzu/tanzu-plugin-runtime/plugin"
)
var descriptor = plugin.PluginDescriptor{
Name: "helloworld",
Description: "Hello world plugin",
Target: types.TargetUnknown, // <<<FIXME! set the Target of the plugin to one of {TargetGlobal,TargetK8s,TargetTMC}
Version: buildinfo.Version,
BuildSHA: buildinfo.SHA,
Group: plugin.ManageCmdGroup, // set group
}
func main() {
p, err := plugin.NewPlugin(&descriptor)
//...
}
Create an initial commit.
git add -A
git commit -m "Initialize plugin repository"
git tag v0.0.1 # TAG the repository if it has no tag.
Configure the go modules
# Configure the go module for tanzu-plugin-runtime to point to the latest release
# This step should not required once we have `v0.90.0` tagged for the repository
# because go get automatically will get the latest official tag
go get github.com/vmware-tanzu/[email protected]
# Download and configure modules and update the go.mod and go.sum files
make gomod
git add -A
git commit -m "Configure go.mod and go.sum"
At this point, the source repository does not yet have any specific commands implemented, but yet a fully functional plugin (with some common commands included) should already be buildable.
The builder
plugin also provides functionality to build, install or publish
the plugins. These capabilities are most easily accessed through Makefile
targets already set up for the same purposes. All plugin-related tooling has been
added using the plugin-tooling.mk
file.
# Building all plugins within the repository
make plugin-build-local
# Building only a single plugin
make plugin-build-local PLUGIN_NAME="<plugin-name>"
# Building multiple plugins at a time
make plugin-build-local PLUGIN_NAME="{plugin-name-1,plugin-name-2}"
This will build plugin artifacts under ./artifacts
with plugins organized under: artifacts/plugins/<OS>/<ARCH>/<TARGET>
# Installing all plugins from a local source using the makefile target
make plugin-install-local
# Installing plugins from local source using the tanzu-cli command
tanzu plugin install --local ./artifacts/plugins/${HOSTOS}/${HOSTARCH} [pluginname|all]
Users can also use the below make target to build and install plugins at once.
# Combined Build and Install target is also available
make plugin-build-install-local
Your plugin is now available for you through the Tanzu CLI. You can confirm
this by running tanzu plugin list
which will now show your plugin.
Plugins are installed into $XDG_DATA_HOME
, (read more about the XDG Base Directory Specification here.
The next steps are to write the plugin code to implement what the plugin is meant to do.
The scaffolded code creates a Plugin object to which additional sub cobra.Command can be added.
Every CLI plugin should have a nested test executable. The executable should
utilize the test framework found in pkg/v1/test/cli
.
Tests are written to ensure the stability of the commands and are compiled
alongside the plugins. Tests can be run by the admin test
plugin of the Tanzu
CLI.
Since every plugin is required a README.md
document that explains its basic
usage, a basic one is generated in the top-level directory of the repository as
well.
Edit the file as appropriate.
To publish one or more built plugins to a target repository, one would need to
- Specify the repository with the env var PLUGIN_PUBLISH_REPOSITORY (the default location is localhost:5001/test/v1/tanzu-cli/plugins), which is where the local test repository is deployed
- Have push access to said repository location
make plugin-publish-packages
one can also combine the building and publishing of the plugins with
make plugin-build-and-publish-packages
# Initialize empty SQLite database
make inventory-init
# Add plugin entry to the SQLite database
make inventory-plugin-add
will update the inventory database image at the plugin repository with the new plugin entries
Assuming the default location was used in the publishing step.
The following CLI configuration can be set to instruct the CLI to query it
tanzu config set env.TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY localhost:5001/test/v1/tanzu-cli/plugins/plugin-inventory:latest
tanzu config set env.TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST localhost:5001/test/v1/tanzu-cli/plugins/plugin-inventory:latest
Once set, plugin lifecycle commands like tanzu plugin search
will interact with the test repository as well.
Note: as the configuration variable implies, the
TANZU_CLI_ADDITIONAL_PLUGIN_DISCOVERY_IMAGES_TEST_ONLY
setting is meant for
plugin development and testing only. The use of this setting in the production setting
is not supported.
Note: the TANZU_CLI_PLUGIN_DISCOVERY_IMAGE_SIGNATURE_VERIFICATION_SKIP_LIST
configuration
variable instructs the CLI to skip the inventory database signature verification. This is
needed because the inventory database image in the local registry is not signed since it is
only used for testing. The signature verification should not be skipped for remote registries
as it would introduce a security risk.
As a plugin developer, you will want to test your plugin manually at some point. During the development cycle, you can easily build and install the latest version of your plugin as described in the section on building a plugin.
If you are using "contexts" while doing testing of your plugin and that your plugin is context-scoped
you may find that your new plugin version is being overwritten with an older version by the CLI.
When a plugin is context-scoped a context may recommend a specific version of that plugin for the CLI
to use. In such a case, if you want to test your new version and prevent the CLI from overwriting it
with the version recommended by the context you can enable the TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS
variable by doing:
tanzu config set env.TANZU_CLI_STANDALONE_OVER_CONTEXT_PLUGINS true
This variable will instruct the CLI that any installation done with tanzu plugin install <pluginName>
should take precedence over any installation coming from a context.
Plugin tests can be run by installing the admin test
plugin.
Currently, we only support testing plugins built locally.
Note: The test
admin functionality has been deprecated and no future enhancements are planned for this plugin.
Steps to test a plugin:
- Bootstrap a new plugin
- Build a plugin binary
- Run below command
tanzu test fetch -l ~/${PLUGIN_NAME}/artifacts/plugins/${HOSTOS}/${HOSTARCH}
tanzu test plugin PLUGIN_NAME
Example: helloworld
plugin
tanzu test fetch -l ~/helloworld/artifacts/plugins/darwin/amd64
[i] Installing plugin 'helloworld:v0.0.1' with target 'global'
[i] Installing test plugin for 'helloworld:v0.0.1'
❯ tanzu test plugin helloworld
---
[i] testing helloworld
[i] cleaning up
[ok] ok: successfully tested helloworld
CLI commands should, to the extent possible, utilize the Plugin UX component library for interactive features like prompts or table printing to achieve consistency across different plugin usage.
Commands should be written in such a way as to return as quickly as possible. When a request is not expected to return immediately, as is often the case with declarative commands, the command should return immediately with an exit code indicating the server's response.
The completion notice should include an example of the get
command the user
would need to poll the resource to check the state/status of the
operation.
Shell completion (or "command-completion" or "tab completion") is the ability for the program to automatically fill in partially typed commands, arguments, flags, and flag values. The Tanzu CLI provides an integrated solution for shell completion which will automatically take care of completing commands and flags for your plugin. To make the completions richer, a plugin can add logic to also provide shell completion for its arguments and flag values; these are referred to as "custom completions".
Please refer to the Cobra project's documentation on Customizing completions to learn how to make your plugin more user-friendly using shell completion.
The tanzu configuration files reside in
XDG_DATA_HOME/.config/tanzu, which will typically be ~/.config/tanzu/
on most systems.
(See XDG for more details)
For more details on the APIs available to retrieve or set various CLI configuration settings, refer to Plugin UX component library
Besides XDG_DATA_HOME/.config/tanzu
, the following directories are also used
to exclusively store data and artifacts specific to Tanzu CLI:
- XDG_DATA_HOME/tanzu_cli: where plugins are installed
- your home directory/.cache/tanzu: contains the catalog recording information about installed plugins, as well as plugin inventory information.
Cleaning out the above-mentioned directories should restore the CLI to a pristine state.
When the Tanzu CLI executes a plugin, it passes the outer environment to the plugin, and also injects some additional environment variables.
Currently, the only additional environment variable passed to the plugin is
TANZU_BIN
which provides the path to the tanzu
command (as executed by the user).
If a plugin needs to trigger a Tanzu CLI operation, it can do so by externally calling
the tanzu
binary specified by the TANZU_BIN
variable.
For example, the plugin can use the following to perform a tanzu plugin sync
:
cliPath := os.Getenv("TANZU_BIN")
command := exec.Command(cliPath, "plugin", "sync")
command.Stdout = os.Stdout
command.Stderr = os.Stderr
command.Run()
It is highly recommended that plugin authors follow the same process used by the Core CLI to announce and implement deprecation of specific plugin functionality.
For more details on the deprecation policy and process please refer to the Deprecation document.