|
| 1 | +title = "SIP 006 - Spin Plugins" |
| 2 | +template = "main" |
| 3 | +date = "2022-08-23T14:53:30Z" |
| 4 | +--- |
| 5 | + |
| 6 | +Summary: A Spin CLI command that will enable plugging in additional functionality and subcommands to Spin. |
| 7 | + |
| 8 | +Owners: karthik.ganeshram@fermyon.com and kate.goldenring@fermyon.com |
| 9 | + |
| 10 | +Created: August 23, 2022 |
| 11 | + |
| 12 | +## Background |
| 13 | + |
| 14 | +The realm of possibilities with Spin continues to grow. However, not every new feature is desired by every user. Instead of needing to modify the Spin codebase, contributors should be able to plug in new functionality or subcommands to Spin via the Spin CLI. This makes Spin easily extensible while keeping it lightweight. |
| 15 | + |
| 16 | +## Proposal |
| 17 | + |
| 18 | +Create a `spin plugin` command, which can be used to install a subcommand that can later be invoked via the Spin CLI. |
| 19 | + |
| 20 | +For the initial proposal, all Spin plugins are expected to be packaged as an executable that will be executed by Spin when the plugin subcommand is invoked. |
| 21 | + |
| 22 | +A [`spin-plugins` repository](#centralized-plugin-manifest-repository) will act as an inventory of available plugins, made by both Spin maintainers and the community. In the repository, a plugin will be defined by a [JSON Spin plugin manifest](#spin-plugin-manifest). Spin will pull down this manifest during installation, which will instruct it on where to find the plugin binary, version, platform compatibility, and more. |
| 23 | + |
| 24 | +### Usage |
| 25 | + |
| 26 | +The `spin plugin` command will have three sub-commands. |
| 27 | + |
| 28 | +```bash |
| 29 | +Commands for working with Spin plugins. |
| 30 | + |
| 31 | +USAGE: |
| 32 | + spin plugin <SUBCOMMAND> |
| 33 | + |
| 34 | +SUBCOMMANDS: |
| 35 | + install Install plugin as described by a remote or local plugin manifest. |
| 36 | + uninstall Uninstall a plugin. |
| 37 | + upgrade Upgrade one or all plugins to the latest or specified version. |
| 38 | +``` |
| 39 | + |
| 40 | +**`spin plugin install`** |
| 41 | + |
| 42 | +The `spin plugin install` subcommand installs a plugin named `$name`. By default, it will look for a plugin manifest named `$name.json` in the `spin-plugin` repository; however, it can be directed to use a local manifest or one at a different remote location using the `--file` or `--url` flag, respectively. |
| 43 | + |
| 44 | +> Note: the plugin `$name` must not match an existing internal Spin command name. For example, `spin plugin install up` would elicit an error. |
| 45 | +
|
| 46 | +```bash |
| 47 | +Install a Spin plugin using a plugin manifest file. |
| 48 | +By default, looks for the plugin manifest named <name>.json |
| 49 | +in the Spin plugins repository https://github.com/fermyon/spin-plugins. |
| 50 | + |
| 51 | +USAGE: |
| 52 | + spin plugin install <name> |
| 53 | + |
| 54 | +OPTIONS: |
| 55 | + -f, --file Path to local plugin manifest. |
| 56 | + -u, --url Address of remote plugin manifest. |
| 57 | + -v, --version Version of plugin to be installed. Defaults to latest. |
| 58 | + -y, --yes Assume yes to all queries. |
| 59 | +``` |
| 60 | + |
| 61 | +If the manifest is found, Spin will check that the plugin is compatible with the current OS, platform, and version of Spin. If so, before installing the plugin, Spin will prompt the user as to whether to trust the source. For example, the following prompt would be displayed for a plugin named `test` with an Apache 2 license and hosted at `https://github.com/fermyon/spin-plugin-test/releases/download/v0.1.0/spin-plugin-test-v0.1.0-macos-aarch64.tar.gz`: |
| 62 | + |
| 63 | +```bash |
| 64 | +Installing plugin `test` with license Apache 2.0 from https://github.com/fermyon/spin-plugin-test/releases/download/v0.1.0/spin-plugin-test-v0.1.0-macos-aarch64.tar.gz |
| 65 | +For more information, reference the plugin metadata at `https://github.com/fermyon/spin-plugins/plugin-manifests/test.json`. |
| 66 | +Are you sure you want to proceed? (y/N) |
| 67 | +``` |
| 68 | + |
| 69 | +The plugin will only be installed if a user enters `y` or `yes` (ignoring capitalization). Otherwise, the command exits. |
| 70 | + |
| 71 | +Spin will reference the plugin manifest in order to fetch the plugin binary and install it into the user’s local data directory under a Spin-managed `plugins` subdirectory. The plugin manifest will be stored within a `manifests` subdirectory. |
| 72 | + |
| 73 | +After installing a plugin, it can be executed directly from the Spin CLI. For example, a plugin named `$name` would be executed by running `spin $name <args>`. Any additional arguments supplied will be passed when executing the associated binary. |
| 74 | + |
| 75 | + |
| 76 | +**`spin plugin uninstall`** |
| 77 | + |
| 78 | +The `spin plugin uninstall` command uninstalls a plugin named `$name`. |
| 79 | + |
| 80 | +```bash |
| 81 | +Uninstall a Spin plugin. |
| 82 | + |
| 83 | +USAGE: |
| 84 | + spin plugin uninstall <name> |
| 85 | +``` |
| 86 | + |
| 87 | +**`spin plugin upgrade`** |
| 88 | + |
| 89 | +The `spin plugin upgrade` command upgrades one or all plugins. If upgrading a single plugin, the desired version can be specified. By default, plugins are upgraded to the latest version in the plugins repository. As with `spin plugin install`, the local path or remote addresses to a plugin manifest can be specified. |
| 90 | + |
| 91 | +```bash |
| 92 | +Upgrade one or all installed Spin plugins. |
| 93 | + |
| 94 | +USAGE: |
| 95 | + spin plugin upgrade [OPTIONS] |
| 96 | + |
| 97 | +OPTIONS: |
| 98 | + -a, --all Upgrade all installed plugins to latest versions (cannot be used with any other option). |
| 99 | + -p, --plugin Name of plugin to upgrade. |
| 100 | + -v, --version Desired version to upgrade the plugin to. Defaults to latest. |
| 101 | + -f, --file Path to local manifest (mutex with `-u`). |
| 102 | + -u, --url Address of remote manifest (mutex with `-f`). |
| 103 | + -d, --downgrade Enables downgrading a plugin to an older specified version. |
| 104 | +``` |
| 105 | + |
| 106 | +The upgrade will fail if the latest or user-specified version of the plugin is not [compatible with the current version of Spin](#plugin-compatibility). |
| 107 | + |
| 108 | +### Spin Plugin Manifest |
| 109 | + |
| 110 | +A Spin plugin is defined by a Spin Plugin Manifest which is a JSON file that conforms with the following [JSON Schema](https://json-schema.org/): |
| 111 | + |
| 112 | +```json |
| 113 | +{ |
| 114 | + "$schema": "https://json-schema.org/draft/2019-09/schema", |
| 115 | + "$id": "https://github.com/fermyon/spin-plugins/json-schema/spin-plugin-manifest-schema-0.1.json", |
| 116 | + "type": "object", |
| 117 | + "title": "spin-plugin-manifest-schema-0.1", |
| 118 | + "required": [ |
| 119 | + "name", |
| 120 | + "description", |
| 121 | + "version", |
| 122 | + "spinCompatibility", |
| 123 | + "license", |
| 124 | + "packages" |
| 125 | + ], |
| 126 | + "properties": { |
| 127 | + "name": { |
| 128 | + "type": "string" |
| 129 | + }, |
| 130 | + "description": { |
| 131 | + "type": "string" |
| 132 | + }, |
| 133 | + "homepage": { |
| 134 | + "type": "string" |
| 135 | + }, |
| 136 | + "version": { |
| 137 | + "type": "string" |
| 138 | + }, |
| 139 | + "spinCompatibility": { |
| 140 | + "type": "string", |
| 141 | + "pattern": "^([><~^*]?[=]?v?(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?(\\.(0|[1-9]\\d*))?(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?)(?:, *([><~^*]?[=]?v?(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?(\\.(0|[1-9]\\d*))?(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?))*$" |
| 142 | + }, |
| 143 | + "license": { |
| 144 | + "type": "string" |
| 145 | + }, |
| 146 | + "packages": { |
| 147 | + "type": "array", |
| 148 | + "minItems": 1, |
| 149 | + "items": { |
| 150 | + "type": "object", |
| 151 | + "required": [ |
| 152 | + "os", |
| 153 | + "arch", |
| 154 | + "url", |
| 155 | + "sha256" |
| 156 | + ], |
| 157 | + "properties": { |
| 158 | + "os": { |
| 159 | + "type": "string", |
| 160 | + "enum": [ |
| 161 | + "linux", |
| 162 | + "osx", |
| 163 | + "windows" |
| 164 | + ] |
| 165 | + }, |
| 166 | + "arch": { |
| 167 | + "type": "string", |
| 168 | + "enum": [ |
| 169 | + "amd64", |
| 170 | + "aarch64" |
| 171 | + ] |
| 172 | + }, |
| 173 | + "url": { |
| 174 | + "type": "string" |
| 175 | + }, |
| 176 | + "sha256": { |
| 177 | + "type": "string" |
| 178 | + } |
| 179 | + }, |
| 180 | + "additionalProperties": false |
| 181 | + } |
| 182 | + } |
| 183 | + }, |
| 184 | + "additionalProperties": false |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +A plugin manifest defines a plugin’s name, version, license, homepage (i.e. GitHub repo), compatible Spin version, and gives a short description of the plugin. It also points to the plugin source for various operating systems and platforms. |
| 189 | + |
| 190 | +The `name` and `spinCompatibility` fields have specific format conventions. |
| 191 | + |
| 192 | +#### Spin Plugin Naming Conventions |
| 193 | + |
| 194 | +The following naming conventions are to be followed for plugins where `$name` is the name of the plugin. |
| 195 | + |
| 196 | +- The `name` field in the plugin manifest must be `$name`. |
| 197 | +- Even if the majority of plugins live within the Spin plugins repository, there is a need to distinguish between plugins that are maintained by Spin vs community plugins. They will be distinguished via the plugin name inside the manifest. The name of community plugins must not have "spin" as a prefix, while plugins maintained by Spin should contain a prefix of `spin-`. |
| 198 | +- Manifests for older versions of the plugin can be retained in the Spin Plugins repository named `$name@$version.json` where `$version` is the value of the `version` field of the manifest. These specific versions can be installed using the `--version` flag. |
| 199 | +- The binary of the plugin must be named `$name` |
| 200 | +- The latest plugin manifest file must be named `$name.json` |
| 201 | +- The license for the plugin must be named `$name.license` |
| 202 | + |
| 203 | +#### Plugin Compatibility |
| 204 | + |
| 205 | +Spin plugins must specify compatible versions of Spin in the `spinCompatibility` field of the manifest. The field is expected to be a list of rules, with each rule being a [comparison operators](https://docs.rs/semver/1.0.13/semver/enum.Op.html) (`=, >, >=, <, <=, ~, ^, *`) along with the compatible version of Spin. The JSON schema validates that the `spinCompatibility` field is a string that matches the following regular expression: `^([><~^*]?[=]?v?(0|[1-9]\d*)(\.(0|[1-9]\d*))?(\.(0|[1-9]\d*))?(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?)(?:, *([><~^*]?[=]?v?(0|[1-9]\d*)(\.(0|[1-9]\d*))?(\.(0|[1-9]\d*))?(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?))*$`. |
| 206 | +For example, specifying `=0.4` means that the plugin is compatible with versions equivalent to `>=0.4.0, <0.5.0`. Multiple rules may be specified (i.e. `>=0.2, <0.5`). |
| 207 | + |
| 208 | +Spin will use the [`semver`](https://docs.rs/semver/1.0.13/semver/struct.VersionReq.html) crate that inspired this syntax to verify that the plugin works on the current version of Spin. If it does not, it will fail to install the plugin and log a message explaining the version mismatch. |
| 209 | + |
| 210 | +#### Centralized Plugin Manifest Repository |
| 211 | + |
| 212 | +- A new GitHub repository https://github.com/fermyon/spin-plugins will act as the index for all the Spin plugin manifests. Having a centralized location for plugin manifests enables future support of a `spin plugin search` subcommand that allows users to search for plugins via the Spin CLI. |
| 213 | +- Creators of new plugins can submit PRs to add a plugin manifest to the repository. |
| 214 | +- Plugin creators are required to test Spin compatibility with their plugin and update the `spinCompatability` field of the manifest over time accordingly. |
| 215 | +- Plugin manifests can be hosted elsewhere and installed via the `--file` or `--url` fields of `spin plugin install`. |
| 216 | + |
| 217 | +## Future design considerations |
| 218 | + |
| 219 | +### Larger scope for Spin Plugins |
| 220 | + |
| 221 | +The concept of Spin plugins is to allow both new subcommands and functionality to be added to Spin. This SIP focuses on the former, enabling users to both install and execute subcommands from the Spin CLI; however, there are cases where it may be useful to install a new Spin feature that is executed by Spin rather than the user. An example of this is Spin triggers. A user may wish to [extend Spin to support a timer trigger](https://spin.fermyon.dev/extending-and-embedding/) that executes components at a configured time interval. Instead of having to understand, modify, and grow the spin codebase, a user could package the trigger as a plugin. After installing the trigger via `spin plugin install`. Spin could invoke it when a Spin manifest references the trigger. |
| 222 | + |
| 223 | +### WebAssembly Plugin Support |
| 224 | +While for now plugins are assumed to be executables, in the future, support for plugging in WebAssembly modules may be desirable. |
| 225 | + |
| 226 | +### Clean versioning and Spin plugin compatibility |
| 227 | + |
| 228 | +The proposed method of using version strings to declare compatibility between a plugin and Spin has several drawbacks. Firstly, this requires plugin creators to stay involved with their contribution, regularly testing and updating the compatibility of their plugin with Spin. One way to make this more hands-off would be to encourage plugin creators to also contribute an integration test. For each new spin release, a workflow in the plugins repository can automatically run these integration tests and bump compatibility versioning on success. This is a strategy taken by [MicroK8s](https://microk8s.io/docs/addons) for its core and community add-ons. |
| 229 | + |
| 230 | +Another issue with using versioning to check for compatibility with Spin is that canary releases of Spin have the same version as the latest release. This means that if a user is using main or canary Spin, when Spin checks its version before installing a plugin, it may incorrectly assume compatibility even though its feature set is beyond that of the latest stable release. Spin templates currently have a workaround for detecting and handling this inconsistency. A more ideal way of assessing compatibility would be via capability checking wherein a plugin could declare what set of features it is compatible with and Spin would assert if those exist. For example, a plugin could be compatible with only a specific version of Spin manifests or only have support for WAGI. While a system like this would be full-proof, it would require deep design. As plugins are developed, a better understanding will come of what capabilities plugins need from Spin. From this, compatibility via compatibilities system could be designed. |
0 commit comments