Skip to content

Commit

Permalink
Merge pull request #3 from christopherthielen/patch-3
Browse files Browse the repository at this point in the history
Alter wording around Plugin Development and Bootstrap
  • Loading branch information
Brandon Powell committed Dec 19, 2019
2 parents 8716508 + 18a3c2d commit 482cd64
Showing 1 changed file with 76 additions and 31 deletions.
107 changes: 76 additions & 31 deletions rfc/plugins-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
|-|-|
| **Status** | **Proposed**, Accepted, Implemented, Obsolete |
| **RFC Num** | [#74](https://github.com/spinnaker/community/pull/74) |
| **Author(s)** | Brandon Powell (`@bpowell`), Cameron Motevasselani (`@link108`), Clay Mccoy (`@claymccoy`) | Chris Thielen (`@christopherthielen`)
| **Author(s)** | Brandon Powell (`@bpowell`), Cameron Motevasselani (`@link108`), Clay Mccoy (`@claymccoy`), Chris Thielen (`@christopherthielen`) |
| **SIG / WG** | Platform SIG |
| **Obsoletes** | [Google Doc](https://docs.google.com/document/d/16WmRSziTJsSBZ1kuKUfVleLAYMIxmfvz/edit) |

## Motivation
Spinnaker is used by many companies, each having wildly different needs. Many enterprise companies have heavily customized Spinnaker improve the experience based on their needs. Some examples of extensions seen in the real world are:

1. Custom stages
2. Modifying header and footer - show screenshot
3. Custom search filters - show screenshot
4. Custom routes - show screenshot
5. Custom details panels - show screenshot
2. Modifying header and footer
3. Custom search filters
4. Custom routes
5. Custom details panels
6. Adding labels or other information to sections in the infrastructure tab

However, the process of customization necessitates a custom build of the spinnaker services and UI. Adding a build step greatly increases the operational costs and complexity of running spinnaker. Smaller companies with fewer resources likely lack the engineering and ops bandwidth to do this.
Expand All @@ -26,63 +26,108 @@ This design doc references the [Plugins RFC](./plugins.md).

This doc focuses on:
* [Loading Plugin code into Deck](#Loading-Plugin-code-into-Deck)
* [Bootstrapping Deck](#Bootstraping-Deck)
* [Extension points](#Extension-Points)
* [Plugin development](#Plugin-development)
* [Bootstrapping Deck](#bootstrapping-deck)

## Loading Plugin code into Deck

### Gathering plugin metadata
In order for Deck to know which plugins it can load, it must have access to some configuration information, including the name of the plugins that are enabled and where to download the plugin resources (JavaScript bundles, CSS, images, etc). This Plugin Configuration will be fetched from Front50 via Gate.
In order for Deck to know which plugins it can load, it must have access to some configuration information, including the name of the plugins that are enabled and where to download the plugin resources (JavaScript bundles, CSS, images, etc).
This Plugin Configuration will be fetched from Front50 via Gate.
For local development, plugin configuration will be loaded from `settings.js` and merged with the configuration from Front50.

Please see the current [Plugins RFC](./plugins.md) for details around Front50 as the source of truth for plugin metadata. Until the Front50 work is completed, a stop-gap solution using `settings.js` to locate the plugin resources should suffice.
Please see the current [Plugins RFC](./plugins.md) for details around Front50 as the source of truth for plugin metadata.
Until the Front50 work is completed, `settings.js` will be used.

### Loading plugin resources
The JavaScript Plugin resources will be fetched and loaded by Deck using native Javascript `import`. Plugin resources must be served from a server that is configured to be [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) aware.
The JavaScript Plugin resources will be fetched and loaded by Deck using native Javascript `import`.
Plugin resources must be served from a server that is configured to be [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) aware.

Some potential servers may be:
- An artifact store such as Artifactory
- Deck's standard Docker image. Plugin resources would have to be located on-instance [1]
- A proxy service that serves Deck resources as well as resources from a storage service such as S3 (this is the case at Netflix)
- Front50 itself, via Gate. This seems like a promising option.

[1] Halyard could configure an init container which would download plugin resources into the container. The resources would be served by served by Apache in the same way that the standard resources are served. Note: if such an init container were created, it could potentially serve the same purpose to front-load plugin assets for backend services.

## Bootstrapping Deck
Plugins will be able to use shared library code which is provided by Deck itself (see [Plugin Structure](#plugin-structure) below). These shared libraries should be loaded before the plugins themselves. Because of this, plugins should be loaded after Deck has exposed the shared libraries. The order of operations likely should be:

- Load Deck assets
- Deck Bootstrap
- Load plugin manifest
- Load plugin assets
- Register plugin extensions
- Start Deck (initialze the router)

### Plugin Initialization

During Deck's bootstrap it should inspect the object the plugin exports. It should find and register each well-known extension. For example, Deck will inspect the plugin object and look for the `stages` property and register each as a Deck stage.
[1] Halyard could configure an init container which would download plugin resources into the container. The resources would be served by Apache in the same way that the standard resources are served. Note: if such an init container were created, it could potentially serve the same purpose to front-load plugin assets for backend services.

## Extension Points

Deck should provides a set of well defined extension points and expose them via the Plugin API object. A number of extension points exist today. Examples include:
Deck should provide a set of well defined extension points.
A number of extension points exist today.
Examples include:

- Stage Registry
- Cloud provider Registry
- Data Source Registry
- Application tabs/routes
- Component Overrides

Today, these extension points are imperative, e.g., `Registry.pipeline.registerStage(stage)`.
Deck will define a Plugin interface, `IDeckPlugin`, which will have declarative fields for each extension point.
Some extension points (such as application tabs) may not support a purely declarative model.
For such extension points, we may define initialization callback signatures that allow the plugin to initialize those Extensions.

## Plugin Development

Plugin developers should have a development experience that closely resembles development of Deck itself. Spinnaker should provide a plugin quick-start, such as a github repository that developers can clone. This quick start should provide:
Plugin developers should have a development experience that closely resembles development of Deck itself.
Spinnaker should provide a plugin quick-start, such as a github repository that developers can clone.
This quick start should provide:

- A standard build system
- A standard build system (Typescript, Rollup, etc)
- Templates for creating specific extension points, i.e., an example stage
- A curated set of transitive dependencies (3rd party libraries) included in core Deck
- A set of reusable UI components that spinnaker itself is built upon
- A set of linter rules which match the code style in core Deck.
- A kork-like `@spinnaker/plugins` package (name TBD) which includes:
- The interfaces that the plugin should conform to
- A curated set of transitive dependencies (3rd party libraries) shared with core Deck
- A set of reusable UI components (that spinnaker itself is built upon)
- A set of linter rules which match the code style in core Deck.

The build system will build a plugin as one or more ES6 module(s).
Deck will import these ES6 modules using native browser `import()`.
The plugin should provide an entry point for these module(s) at `/index.js`.
This file should export the plugin object using a named export of `plugin`.
The plugin object must conform to the plugin interface specified by `@spinnaker/plugins`.
This interface will have properties for each extension point which the plugin can populate.
For example:

```js
import { fancyStage } from './fancyStage';
export const plugin = {
stages: [fancyStage],
}
```

The build system should ideally support code splitting to enable lazy loading of portions of plugin code.
The build system should not include the transitive dependencies that are already loaded by core Deck.
Instead, it should share those curated dependencies with the plugin at runtime from the existing code loaded into core deck.

## Bootstrapping Deck

The build system will ideally build a plugin as ES6 module(s). Deck will import these ES6 modules using native browser `import()`. The build system should ideally support code splitting to enable lazy loading of portions of plugin code. The build system should not include the transitive dependencies that are already loaded by core Deck. Instead, it should share those curated dependencies at runtime from the existing code loaded into core deck.
Plugins will be able to use shared library code which is provided by Deck itself.
These shared libraries should be loaded before the plugins themselves.
Because of this, plugins should be loaded after Deck has exposed the shared libraries.
The order of operations likely should be:

- Load Deck assets
- Deck Bootstrap
- Load plugin manifest
- Load each plugin's entrypoint as a module
- Register extensions from the plugin(s)
- Start Deck (initialize the router)

During Deck's bootstrap it should inspect the `plugin` object of each plugin module.
It should iterate each extension found and register it with Deck.
For example, Deck will inspect the plugin object and look for the `stages` property and register each as a Deck stage.

```js
import(pluginUrl).then(pluginModule => {
const { plugin } = pluginModule;
plugin.stages?.forEach(stage => Registry.pipeline.registerStage(stage);
}).catch(loadError => {
// Error handling behavior TBD
});
```
## Known Unknowns
* Where are resources downloaded from? How do we ensure end users can download plugin resources?
Expand Down

0 comments on commit 482cd64

Please sign in to comment.