|
| 1 | +# CLI Extensions |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +CLI extensions are separate executables that appear as part of the `temporal` CLI. This is similar to |
| 6 | +[`kubectl` plugins](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) and `git` extensions/commands. |
| 7 | + |
| 8 | +## Usage Examples |
| 9 | + |
| 10 | +### Custom Authenticator |
| 11 | + |
| 12 | +A custom extension can allow |
| 13 | + |
| 14 | + temporal mycompany login |
| 15 | + |
| 16 | +To set some environment config settings on disk so that successive calls to |
| 17 | + |
| 18 | + temporal workflow start --type foo --task-queue some-tq --workflow-id foo-id |
| 19 | + |
| 20 | +automatically work. |
| 21 | + |
| 22 | +### Shortcuts for Common Tasks |
| 23 | + |
| 24 | +A custom extension can add something like |
| 25 | + |
| 26 | + temporal mycompany do-company-thing |
| 27 | + |
| 28 | +And that can do anything programmatically and also get the benefit of having access to the Temporal client. So it could |
| 29 | +access some company resource (e.g. a database) and send an update to a workflow with some value. |
| 30 | + |
| 31 | +### Contributions |
| 32 | + |
| 33 | +Custom extensions allow an ecosystem of CLI extensions to form that do not require Temporal oversight. For example |
| 34 | + |
| 35 | + temporal workflow show-diagram --workflow-id foo-id |
| 36 | + |
| 37 | +Could be an extension that downloads the workflow history and makes a nice visualization of it. |
| 38 | + |
| 39 | +### Cloud CLI |
| 40 | + |
| 41 | +A Temporal Cloud extension will be available (and likely released alongside the traditional CLI) that will allow |
| 42 | +cloud-specific operations like: |
| 43 | + |
| 44 | + temporal cloud namespace list |
| 45 | + |
| 46 | +This can share authentication with the CLI in many ways. It can even support login, so the following two commands could |
| 47 | +work well together: |
| 48 | + |
| 49 | + temporal cloud login --profile cloud |
| 50 | + temporal workflow start --profile cloud --type foo --task-queue some-tq --workflow-id foo-id |
| 51 | + |
| 52 | +These are just usage examples for the purposes of explaining extensions, this may not be what `temporal cloud` extension |
| 53 | +looks like when built. |
| 54 | + |
| 55 | +## Runtime Behavior |
| 56 | + |
| 57 | +### Lookup |
| 58 | + |
| 59 | +Extensions are any executables on the `PATH` with `temporal-` prefix. Extension executable names use `-` for each |
| 60 | +subcommand and `_` for each dash in the command (but still can be called with an underscore). This is similar to |
| 61 | +`kubectl` behavior. |
| 62 | + |
| 63 | +The `PATH` lookup is most-specific to least-specific. For example, running `temporal foo bar-baz qux` will try to match |
| 64 | +the following in order: |
| 65 | + |
| 66 | +* `temporal-foo-bar_baz-qux` |
| 67 | +* `temporal-foo-bar_baz` |
| 68 | +* `temporal-foo` |
| 69 | + |
| 70 | +And the first one found will be executed with all arguments. And since underscores and dashes are the same, |
| 71 | +`temporal-foo-bar_baz` executable is called for both `temporal foo bar-baz qux` and `temporal foo bar_baz qux` though |
| 72 | +help text only shows the former. |
| 73 | + |
| 74 | +Built-in commands cannot be overridden by extensions, but subcommands can be added to existing commands. |
| 75 | + |
| 76 | +### Discovery and Help Text |
| 77 | + |
| 78 | +Running `temporal --help` (or `temporal help`) _does not_ list extensions. But running `temporal help -a` (or `--all`) |
| 79 | +_does_ list all extensions by traversing the `PATH` and getting every `temporal-`-prefixed executable. There is no short |
| 80 | +description for any of these extensions, they are simply shown as available external commands. All extensions on the |
| 81 | +`PATH` are shown as they are, even if they are multiple commands deep. For example `temporal-foo-bar` and |
| 82 | +`temporal-workflow-dosomething-somethingelse` are shown as `foo bar` and `workflow dosomething somethingelse` in the |
| 83 | +list respectively, not as just `foo` or implied as part of the `workflow` command. |
| 84 | + |
| 85 | +Extensions on built-in commands are not shown as part of the parent command's help. |
| 86 | + |
| 87 | +Running `temporal help <command sequence>` is treated as `temporal <command sequence> --help`. So the same style of |
| 88 | +lookup is performed for extensions. |
| 89 | + |
| 90 | +### Invocation and Flags |
| 91 | + |
| 92 | +Temporal CLI supports arbitrarily ordered flags. So `temporal --address foo workflow start`, |
| 93 | +`temporal workflow --address foo start`, and `temporal workflow start --address foo` are all the same. It is still a |
| 94 | +subject of active research on whether flags of _parent_ commands can still be placed before the extension command in the |
| 95 | +CLI. Regardless, flags of the extension command _must_ come after the extension command because CLI does not know |
| 96 | +whether a flag takes an argument or not so it cannot disambiguate. For example, CLI |
| 97 | +`temporal --myflag someval1 someval2` cannot determine whether that is `temporal someval1 someval2 --myflag` or |
| 98 | +`temporal someval2 --myflag someval1`. So the latter forms must be the forms used for extension-specific flags. |
| 99 | + |
| 100 | +All flags of the parent command _should_ be handled properly by the extension. This means even root-level flags. So for |
| 101 | +example, `temporal-foo` _should_ handle root-level flags like `--output`. However, this isn't a _must_ requirement. See |
| 102 | +the helper library section for some helpers. |
| 103 | + |
| 104 | +Currently, the only root-level flag that the parent `temporal` process respects when calling an extension is |
| 105 | +`--command-timeout` (even though it is still passed along). All other root-level flags are up to the extension to |
| 106 | +handle. |
| 107 | + |
| 108 | +Invocation of the extension is done as a subprocess. Stdin, stdout, stderr, exit code, etc are all handled by the |
| 109 | +extension and just relayed through the `temporal` process as is. It is an area of active research on whether interrupt |
| 110 | +signals can be ignored by the parent `temporal` process to be handled by the subprocess, though that is the goal. |
| 111 | + |
| 112 | +### Helper Library |
| 113 | + |
| 114 | +Built-in commands leverage several helpers to be consistent with the CLI ecosystem. Extension commands should be able to |
| 115 | +do the same, within reason, so a helper library will be made available. Originally it was thought that such a helper |
| 116 | +library was not needed and extensions could deal with this themselves, but it is clear that there are too many common |
| 117 | +flags and situations to have to rewrite logic for. |
| 118 | + |
| 119 | +A package on the existing Go CLI will be made available for use programmatically. The package is |
| 120 | +`github.com/temporalio/cli/cliext` and is the only package on the entire library/module that is acceptable for use |
| 121 | +programmatically. The existing `github.com/temporalio/cli/temporalcli` package should not be used and may be moved to |
| 122 | +`internal` as part of this project. |
| 123 | + |
| 124 | +The CLI module is versioned the same as the CLI binary version. The version of CLI used by users can be different than |
| 125 | +the version of CLI module used by extensions, but there may be issues if the CLI version used by a user is newer than |
| 126 | +the one used by an extension mostly due to new flags and Temporal client capabilities. Within reason, the CLI team will |
| 127 | +try to maintain runtime behavior compatibility with extensions using older forms of this library. |
| 128 | + |
| 129 | +Regarding API compatibility, unlike SDKs, there are no guarantees that `github.com/temporalio/cli/cliext` library API |
| 130 | +will remain compatible from one version to the next. CLI team will try its best to retain compatibility, or if it can't, |
| 131 | +will try to have clear compilation breaks and release notes. CLI binary versions are not meant to be construed as |
| 132 | +related to compatibility of this package which technically means a CLI patch could have a compatibility change in this |
| 133 | +library, though CLI team will strive to avoid that at the semver patch level (and only do it at a minor level). |
| 134 | + |
| 135 | +Documentation of this library is in the Godoc of the `github.com/temporalio/cli/cliext` package. Even release notes will |
| 136 | +not mention the library unless there is significant reason such as a compatibility break. |
| 137 | + |
| 138 | +The initial implementation of the library will contain the following utilities: |
| 139 | + |
| 140 | +* Structs and Cobra flag sets for root-level flags and Temporal client flags |
| 141 | +* Ability to create `*slog.Logger` from root-level flags |
| 142 | +* Ability to dial a Temporal client from Temporal client flags |
| 143 | +* Utility to create payloads from raw data |
| 144 | + |
| 145 | +Other items can be added as needed. Extensions can/should also use the Go SDK as needed. |
| 146 | + |
| 147 | +There is not a plan to expose a printer at this time, so extensions will have to handle the possible `output` enumerates |
| 148 | +of `text`, `json`, `jsonl`, and `none` themselves. This is because the current CLI printer has too many quirks and is |
| 149 | +not high quality enough for exposure. It is possible in the future a good printer abstraction can be exposed. |
| 150 | + |
| 151 | +It is an area of active research whether built-in commands will leverage this package or whether both will leverage |
| 152 | +common code independently. |
| 153 | + |
| 154 | +It is an area of active research whether the CLI's YAML-based code generation functionality will be made available to |
| 155 | +extensions. |
| 156 | + |
| 157 | +#### Use of Environment Configuration |
| 158 | + |
| 159 | +CLI supports environment configuration which, as of this writing, is basically just a way to load client configuration |
| 160 | +via config files and environment variables. This is therefore bidirectional. This means that an extension can mutate an |
| 161 | +environment configuration file and subsequent built-in commands can use it. |
| 162 | + |
| 163 | +For example, a command may set an API key in a profile of a config file. Then all commands creating Temporal clients |
| 164 | +will use that API key. |
| 165 | + |
| 166 | +## Example |
| 167 | + |
| 168 | +### Shell Script |
| 169 | + |
| 170 | +On a Unix-style platform, there can a file named `temporal-workflow-do_thing` on a directory in the `PATH` with the |
| 171 | +executable bit set and with the contents (may not be ideal code, just for demo purposes): |
| 172 | + |
| 173 | +```sh |
| 174 | +#!/bin/bash |
| 175 | + |
| 176 | +if [ "$1" == "foo" ]; then |
| 177 | + temporal workflow start --type foo --task-queue some-tq --workflow-id foo-id --id-conflict-policy UseExisting |
| 178 | + exit $? |
| 179 | +elif [ "$1" == "bar" ]; then |
| 180 | + temporal workflow update --name bar --workflow-id foo-id |
| 181 | + exit $? |
| 182 | +else |
| 183 | + echo "Error: Only foo or bar accepted" >&2 |
| 184 | + exit 1 |
| 185 | +fi |
| 186 | +``` |
| 187 | + |
| 188 | +Now `temporal workflow do-thing foo` and `temporal workflow do-thing bar` work as expected. However, this approach is |
| 189 | +usually only good for quick things with limited flexibility. It's not good for general use because it doesn't respect |
| 190 | +any of the root-level flags (e.g. `--output json`) or any of the workflow-level flags (e.g. `--namespace`). Proper |
| 191 | +extensions should likely use the helper library. |
| 192 | + |
| 193 | +### Go-based |
| 194 | + |
| 195 | +TODO |
0 commit comments