10. Custom plugins
Before implementing your own custom plugin, you should browse through existing core plugins and extensions, as they already might provide what you are looking for.
The basics
You can implement your plugin with almost any programming language. The only requirement is that your plugin provides taito command implementations as executable files. Bash is a good choice for a simple implementation that gets the job done by calling an existing command line tool. Python or JavaScript are preferred for more complex stuff, especially, if you need to implement complex interactions with a REST or GraphQL API.
See project-and-extension as an example. It is a project that uses a custom extension that is located in the project subdirectory. TODO: examples for Python and JavaScript.
This is how you implement your own custom plugin:
-
First create a directory that works as a Taito CLI extension. It is basically a collection of plugins:
my-extension/ my-plugin/ another-plugin/ -
Add
package.jsonandtaito-config.shfiles to the root directory of your extension (my-extension). Minimalpackage.jsonandtaito-config.shcontents for supporting unit tests:{ "scripts": { "unit": "taito -- find . -name \"*.bats\" -type f -prune -exec bats '{}' +" } }#!/bin/bash -e # shellcheck disable=SC2034 taito_image="ghcr.io/taitounited/taito-cli:cli" taito_plugins="npm" -
Add some executable commands to one of the plugins as executable files. Include execution priority (00-99) at the end of each command filename. The execution priority defines the execution order of a command implementation between enabled plugins. The plugin directory may also contain additional libraries and resources in additional subdirectories as in the example (
lib/,resources/). Optionally you can add also plugin documentation in help.txt, trouble.txt and README.md files. TODO: command name conventions.my-plugin/ lib/ my-lib.bash resources/ my-script.sql my-command-50 env-apply-00 env-apply-99 help.txt README.md trouble.txtAlways remember to call the next command of the command chain at some point during command execution (usually at the end) unless you want to stop the command chain execution:
taito::call_next "${@}". TODO: Example for Python and “any language”.Do not call another command implementation directly from another. It’s error prone; you’ll easily mess up the command chain execution, and also clarity of user friendly info messages. Place the common logic shared by multiple commands in a separate library instead.
-
Optionally you can also add pre and post hooks to your plugin. These will be called before and after any other commands despite the command name.
my-plugin/ hooks/ pre-50 post-50 -
Add the extension directory to your taito_global_extensions or taito_extensions definition and the plugin to your taito_global_plugins or taito_plugins definition. You can reference extension either by file path or git url.
taito_extensions="git@github.com:JohnDoe/my-extension.git" taito_plugins="my-plugin" -
Implement unit tests for your commands with bats. See
.batsfiles undertaito-cli/pluginsas an example. You can run your unit tests with thetaito unitcommand. -
Optionally provide autocomplete and descriptions support for you commands by adding
autocompleteanddescriptionsto the root folder of your extension. See autocomplete and descriptions as an example.
Now you should be able to call taito my command. And when you call taito env apply, your env-apply-00 and env-apply-99 commands will be called before and after all env-apply implementations defined by other enabled plugins. And if you defined also pre and post hooks, they will be called before and after any commands despite the command name.
Note that you can also add a project specific extension to your project subdirectory and reference it like this in taito-config.sh:
taito_extensions="./scripts/my-extension"
taito_plugins="my-plugin"
Tips
Environment variables
Common variables
All settings defined in taito-config.sh are visible for plugins. See configuration chapter for more info. Additionally some environment variables are defined by Taito CLI. The following are the most important:
- taito_project_path: Path to project root directory.
- taito_plugin_path: Path to root directory of the current plugin.
- taito_cli_path: Path to Taito CLI root directory.
- taito_target: Command target (e.g. admin, client, server, database, storage, …)
- taito_target_env: The target environment (e.g. local, demo, dev, test, uat, stag, canary, prod)
- taito_env: The resource environment. Usually the same as taito_target_env, but may differ in some scenarios (e.g.
taito_target_env=canarybuttaito_env=prod).
TODO: update the list of environment variables
Output in verbose and debug mode
Values of the following environment variables are set depending on debug mode:
- taito_debug:
trueorfalse - taito_dout:
/dev/ttyor/dev/null
Values of the following environment variables are set depending on verbose mode:
- taito_verbose:
trueorfalse - taito_vout:
/dev/ttyor/dev/null
You can use these environment variables to provide additional output in verbose or debug mode. For example:
echo "Additional debug output" > "${taito_dout}"
echo "Additional verbose output" > "${taito_vout}"
(taito::executing_start; kubectl get pods) # The command will printed in verbose mode
Standardized variable names
These variable names are meant for communication between plugins.
Secrets:
TODO: add documentation
Command chains and passing data with environment variables
When a given command name matches to multiple commands, all commands are chained in series by the execution priority so that each command calls the next. Passing data between commands implementations works simply by exporting environment variables. To avoid naming conflicts between plugins, use your plugin name as a prefix for your exported environment variables. Or if the purpose is to pass data between different plugins, try to come up with some good standardized variable names.
Here is an example how chaining could be used e.g. to implement secret rotation by integrating an external secret manager:
- Pre-hook of a secret manager plugin gathers all secrets that need to be rotated (e.g. database passwords) and generates new secrets for them.
- A database plugin deploys the new database passwords to database.
- The kubectl plugin deploys the secrets to Kubernetes and executes a rolling restart for the pods that use them.
- Post-hook of the secret manager plugin stores the new secrets to a secure location using some form of encryption, or just updates the secret timestamps if the secrets need not be stored.
Taito CLI library
Taito CLI library provides reusable functions for your plugin implementation. Some examples below.
TODO: Examples for Python
User interaction
Display output:
taito::print_plugin_title # Use this in your pre and post hooks
taito::print_title "This is a title"
taito::print_note_start
echo "NOTE: This is a multiline note."
echo "Another line."
taito::print_note_end
Confirm operation:
if taito::confirm "Execute this operation" yes; then
echo "Execute this"
fi
Open url on web browser:
taito::open_browser "https://mydomain.com/path"
Command execution flow
Call next item on the command execution chain:
echo "Executed before next"
taito::call_next "${@}"
echo "Executed after next"
Skip execution if user does not confirm operation:
taito::confirm "Execute this operation" no || taito::skip_to_next "${@}"
echo "This is executed only if user confirms the execution"
taito::call_next "${@}"
Skip execution if current target is not a container or storage:
taito::skip_if_not "container storage" "${@}"
echo "This is executed only for container or storage"
taito::call_next "${@}"
Execute for all targets of type database:
for db in ${taito_databases[@]}; do
taito::export_database_config "${db}"
if [[ ${database_type:-} == "pg" ]]; then
taito::expose_db_user_credentials "${db}" # TODO: add support for param
echo "PostgreSQL database:"
echo "- Instance: ${database_instance}"
echo "- Host: ${database_host}"
echo "- Port: ${database_port}"
echo "- Name: ${database_name}"
echo "- Username: ${database_app_username}"
echo "- Password: ${database_app_password}"
fi
done
Running commands on host
If your plugin needs to run some commands on host machine, execute taito::execute_on_host COMMANDS to run them immediately in the background. Alternatively you can use the taito::execute_on_host_fg COMMANDS to run the commands on foreground after the taito container has exited. Note that if some of the commands might require user input, you must run the commands on foreground.
Currently this mechanism is used e.g. for executing docker commands on host and launching browser.
Committing changes to the Taito CLI container image
If your plugin needs to save some data permanently on the container image, execute taito::commit_changes. This asks host to commit changes permanently on the container image.
Overriding existing commands
If you need to alter default behaviour of an existing plugin in some way, you can override a single command implementation of a plugin without disabling the whole plugin:
- Create a plugin that provides an alternative implementation for the command
- Create a pre-hook that removes the original command implementation from command chain (TODO reusable Taito CLI function for this)