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.
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.json
and taito-config.sh
files to the root directory of your extension (my-extension
). Minimal package.json
and taito-config.sh
contents 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.txt
Always 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 taitoglobalextensions or taitoextensions_ definition and the plugin to your taitoglobalplugins or taitoplugins_ 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"
.bats
files under taito-cli/plugins
as an example. You can run your unit tests with the taito unit
command.autocomplete
and descriptions
to 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"
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_target_env=canary
but taito_env=prod
).TODO: update the list of environment variables
Values of the following environment variables are set depending on debug mode:
true
or false
/dev/tty
or /dev/null
Values of the following environment variables are set depending on verbose mode:
true
or false
/dev/tty
or /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
These variable names are meant for communication between plugins.
Secrets:
TODO: add documentation
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:
Taito CLI library provides reusable functions for your plugin implementation. Some examples below.
TODO: Examples for Python
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"
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
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.
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.
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: