Plugin Creator Guide

Plugins enable operators to extend Spinnaker with custom functionality.

What plugins do

Plugins enable operators to extend Spinnaker with custom functionality. Use cases include fetching credentials from a custom authorization service, adding a wait stage to a pipeline, updating a Jira ticket, and sending Echo events to third-party tools.

The goal of this guide is to help with the core services side of plugin development: understand how plugins work and how to create new extension points for plugin developers to use.

Plugins are available in Spinnaker 1.20.6+ configured with Halyard 1.36+.

Why there is a plugin framework

Spinnaker was originally written with extensibility in mind. Netflix wrote huge amounts of custom code atop OSS Spinnaker, decorating existing functionality or replacing entire areas to suit their needs. This was done either by consuming the OSS projects as libraries and laying custom code on top, or wiring it together via Spring configuration (or something else in Deck-land).

This is well and good, but by making the method of extension application configuration classes, the contract for extensions is essentially the entire codebase. Good for getting things done fast, but bad for creating clear domain contracts, which leads to a mixing of core service code and integrations. Over time, this extension pattern manifests itself in making the core services heavy and difficult to maintain.

Realizing this, Netflix started an early initiative called Lean Core, Fat Ecosystem . Plugins were the first major manifestation of this initiative: To take the already-built functionality in Spinnaker and start breaking it out into composable, separately distributable binaries.

Terminology

  • Extension Point
    • An interface defined by one of the Spinnaker services for adding specific functionality.
  • Extension
    • An implementation of an Extension Point.
  • Plugin
    • A collection of Extensions for a single Spinnaker service.
  • Bundle
    • A collection of related Plugins that span Spinnaker services making up a complete feature.
  • SDK
    • Libraries offered by the plugin framework and Spinnaker services to assist common use cases.
  • TCK
    • Test harnesses and utilities to help plugin developers assert Extension functionality.

What should be an Extension Point

An Extension Point should be made at an intersection between a service’s core functionality and what it considers an integration. An integration is value added to a service, but not value that impacts the core functionality offerings of the service: The differentiator is that a service cannot function without its core functionality, whereas an integration, as critical as it may be for a particular configuration of that service, is ultimately optional.

Let’s take Orca as an example: Individual Pipeline Stages and SpEL Functions are two integrations. Service code that enables the use of Stages, such as the pipeline engine itself, is a core feature that isn’t an extension point.

When we look at a service under the lens of Lean Core, Fat Ecosystem, we want to have stable core services that only change for the purpose of enabling or fixing core functionality. Most service deployments for Spinnaker today are for integrations.

So, for a Spinnaker service, if there is an integration, it should be enabled by one or more Extension Points. Going back to the Orca Stage example, a Stage is actually comprised of multiple Extension Points: There is Task, which is responsible for performing a small, discrete action, as well as StageDefinitionBuilder, which is responsible for defining how various Task classes interact with each other, and when.

Extension Points should be small and composable and, when used in concert with each other, enable larger value for Spinnaker than the sum of its parts.

Plugin types

Frontend (Deck) plugins

Frontend plugins provide a way to change the behavior of Deck, Spinnaker’s UI service. You can add configuration and validation for new stages provided by Orca plugins, override existing components with your own implementation, or add new Kubernetes kind definitions for custom resources in your environment. Spinnaker loads plugins at runtime through Gate.

You can write plugins in any JavaScript-compatible language, but the development tooling is designed for JavaScript and TypeScript.

The following are examples of Deck features that you can override:

The following projects demonstrate adding new stages to Spinnaker:

See the Frontend Plugin Development guide for more information.

ExtensionPoint plugins

Spinnaker uses the Plugin Framework for Java (PF4J) to indicate an extension point interface to a service. You can create a plugin that implements the methods declared in an extension point. Creating a plugin based on an extension point has a number of advantages:

  • It’s the easiest - use the @Extension annotation and implement the methods declared in your chosen extension point
  • Spinnaker loads the plugin in an isolated classpath
  • It has the least amount of maintenance work
  • Updates to Spinnaker are not likely to break your plugin

Finding an extension point

An extension point is an interface that extends org.pf4j.ExtensionPoint and is located in the api module of a service. The following list provides a sample of what these extension points look like in Orca and Echo:

Example ExtensionPoint Plugin

The pf4jStagePlugin creates a custom pipeline stage that waits a specified number of seconds before signaling success. Consult the Test a Pipeline Stage Plugin guide for how to test this plugin using a local Spinnaker environment.

Interface Plugins

The second way you can create a plugin is to implement a regular Java interface that you find in a service. Your plugin uses the PF4J @Extension annotation but does not extend org.pf4j.ExtensionPoint.

Advantages:

  • Spinnaker loads the plugin in an isolated classpath

Disadvantages:

  • Requires a moderate knowledge of Spinnaker’s architecture and code
  • Plugin can break if the service’s interface changes

Example Interface Plugin

The pf4jPluginWithoutExtensionPoint plugin extends the functionality of Kork’s SecretEngine . SecretEngine is a regular Java interface that does not import any PF4J classes. pf4jPluginWithoutExtensionPoint’s SillySecretEngine implements SecretEngine and uses the @Extension annotation to identify itself as a PF4J plugin. See the plugin project’s README and code for details on how this plugin works.

Spring Plugins

When you can’t find an org.pf4j.ExtensionPoint to use or a Java interface to implement, you can create a plugin using Spring. This is should be done as a last resort, since the disadvantages outweigh the advantages.

Advantages:

  • Full control

Disadvantages:

  • Requires an expert knowledge of Spinnaker’s architecture and codebase
  • Requires working knowledge of Spring
  • High maintenance; plugin can break when Spinnaker dependencies and functionality change

Example Spring Plugin

The Spring Example Plugin does not use a PF4J extension point or dependencies. It uses Spring components and was created to test various use cases. See the project for details.

Next steps

Last modified July 8, 2021: fix(link): add redirect from old site URL (#116) (9a368aa)