Skip to content

Commit 15af4b0

Browse files
authored
CLI Extensions (#98)
1 parent 91fd05d commit 15af4b0

13 files changed

+195
-0
lines changed

cli/cli-extensions.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)