Skip to content

Commit

Permalink
docs: add tutorial for external plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Eileen-Yu committed Jul 6, 2023
1 parent a632400 commit 8983a03
Showing 1 changed file with 155 additions and 0 deletions.
155 changes: 155 additions & 0 deletions docs/book/src/plugins/external-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Extending Kubebuilder with external plugins

## Overview

You can extend the Kubebuilder functionality by introducing external plugin.

An external plugin is an executable that provides the same kubebuilder plugin features. Since it is an executable, such customized plugin can be implemented in any language.

The Kubebuilder CLI loads the external plugin in the specified path and interact the plugin through the executable's `stdin` & `stdout`.

## When is it useful?

- If you want to create helpers or addons on top of the scaffolds done by the Kubebuilder internal plugins.

- If you design customized layouts and want to take advantage of functions from Kubebuilder library.

- If you are looking for implementing plugins by a different language other than `Go`.

## How to write it?

The inter-process communication between your external plugin and Kubebuilder is through the standard I/O.

You can write your external plugin in any language as long as it implements the `PluginRequest` and `PluginResponse` interfaces.

`PluginRequest` holds all the information Kubebuilder receives from the CLI and the plugins that were executed before it.
The marshaled `PluginRequest` (which should be a `JSON` object) would be sent over `stdin` to the external plugin by Kubebuilder.

```go
// PluginRequest contains all information kubebuilder received from the CLI
// and plugins executed before it.
type PluginRequest struct {
// APIVersion defines the versioned schema of PluginRequest that is being sent from Kubebuilder.
// Initially, this will be marked as alpha (v1alpha1).
APIVersion string `json:"apiVersion"`

// Args holds the plugin specific arguments that are received from the CLI
// which are to be passed down to the external plugin.
Args []string `json:"args"`

// Command contains the command to be executed by the plugin such as init, create api, etc.
Command string `json:"command"`

// Universe represents the modified file contents that gets updated over a series of plugin runs
// across the plugin chain. Initially, it starts out as empty.
Universe map[string]string `json:"universe"`
}
```

`PluginResponse` is what the plugin constructs with the updated universe and serialized and sent back to Kubebuilder through `stdout`.

```go
// PluginResponse is returned to kubebuilder by the plugin and contains all files
// written by the plugin following a certain command.
type PluginResponse struct {
// APIVersion defines the versioned schema of the PluginResponse that is back sent back to Kubebuilder.
// Initially, this will be marked as alpha (v1alpha1)
APIVersion string `json:"apiVersion"`

// Command holds the command that gets executed by the plugin such as init, create api, etc.
Command string `json:"command"`

// Metadata contains the plugin specific help text that the plugin returns to Kubebuilder when it receives
// `--help` flag from Kubebuilder.
Metadata plugin.SubcommandMetadata `json:"metadata"`

// Universe in the PluginResponse represents the updated file contents that was written by the plugin.
Universe map[string]string `json:"universe"`

// Error is a boolean type that indicates whether there were any errors due to plugin failures.
Error bool `json:"error,omitempty"`

// ErrorMsgs contains the specific error messages of the plugin failures.
ErrorMsgs []string `json:"errorMsgs,omitempty"`

// Flags contains the plugin specific flags that the plugin returns to Kubebuilder when it receives
// a request for a list of supported flags from Kubebuilder
Flags []Flag `json:"flags,omitempty"`
}
```

<aside class="note">
<h1>Caution</h1>

When writing your own external plugin, you **should not** directly echo or print anything to the stdout.

This is because Kubebuilder and your plugin are communicating with each other via `stdin` and `stdout` using structured `JSON` data.
Any additional information sent to stdout (such as debug messages or logs) that's not part of the expected PluginResponse JSON structure may cause parsing errors when Kubebuilder tries to read and decode the response from your plugin.

If you need to include logs or debug messages while developing your plugin, consider writing these messages to a log file instead.

</aside>

## How to use it?

### Prerequisites

- You have built and installed your Kubebuilder binary.

- You have built and installed your external plugin as an **executable / binary**.

- Your plugin is put in a path following the GVK schema:
```sh
/path1/path2/${name}/${version}
```

### Usage:

The external plugin supports the same subcommands as kubebuilder already provides:
- `init`: project initialization.
- `create api`: scaffold Kubernetes API definitions.
- `create webhook`: scaffold Kubernetes webhooks.


Also, if the plugin is put in a configured path, you need to use a env variable `$EXTERNAL_PLUGINS_PATH` to tell Kubebuilder where to search for the plugin binary.

Otherwise, Kubebuilder would search for the plugins in a default path based on your OS:
1. On Linux:
```sh
$XDG_CONFIG_HOME/kubebuilder/plugins/${name}/${version}
```

2. On OSX
```sh
~/Library/Application Support/kubebuilder/plugins/${name}/${version}
```

**Examples**
```sh
# Initialize a new project with the external plugin named `sampleplugin`
EXTERNAL_PLUGINS_PATH=/path1/path2/sampleplugin/v1/sampleplugin kubebuilder init --plugins sampleplugin/v1

# Create a new API with the above external plugin with a customized flag `number`
EXTERNAL_PLUGINS_PATH=/path1/path2/sampleplugin/v1/sampleplugin kubebuilder create api --plugins sampleplugin/v1 --number 2

# Create a webhook with the above external plugin with a customized flag `hooked`
EXTERNAL_PLUGINS_PATH=/path1/path2/sampleplugin/v1/sampleplugin kubebuilder create webhook --plugins sampleplugin/v1 --hooked

# Display help information of the `create api` subcommand of the external plugin
EXTERNAL_PLUGINS_PATH=/path1/path2/sampleplugin/v1/sampleplugin kubebuilder create api --plugins sampleplugin/v1 --help

# Create new APIs with external plugins v1 and v2 by respecting the plugin chaining order
EXTERNAL_PLUGINS_PATH=/path1/path2/sampleplugin/v1/sampleplugin kubebuilder create api --plugins sampleplugin/v1,sampleplugin/v2

# Create new APIs with the go/v4 plugin and then pass those files to the external plugin by respecting the plugin chaining order
EXTERNAL_PLUGINS_PATH=/path1/path2/sampleplugin/v1/sampleplugin kubebuilder create api --plugins go/v4,sampleplugin/v1
```


## Further resources

- Check the [design proposal of the external plugin](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md)
- Check the [plugin implementation](https://github.com/kubernetes-sigs/kubebuilder/pull/2338)
- A [sample external plugin written in Go](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1)
- A [sample external plugin written in Python](https://github.com/rashmigottipati/POC-Phase2-Plugins)
- A [sample external plugin written in JavaScript](https://github.com/Eileen-Yu/kb-js-plugin)

0 comments on commit 8983a03

Please sign in to comment.