Package plugin is used to write plugin providers in Go. It provides the framework for the rest of the plugin provider code, along with a set of interfaces that you app can satisfy to implement whatever custom behaviour the plugin provider needs to implement.
A plugin provider is an executable that provides extensions to the base functionality in dr-provision. This can be done in several different ways, depending on the functionality the plugin provider needs to implement:
1. Injecting custom content bundles into dr-provision to provide
additional tasks, params, etc.
2. Implementing additional per-object Actions that can be used for
a wide variety of things.
3. Providing additional files in the files/ space of the static file server.
4. Listening to the event stream from dr-provision to take action
whenever any number of selected events happen.
5. Define new object types that dr-provision will store and manage.
github.com/digitalrebar/provision/cmds/incrementer provides a fully functional implementation of a basic plugin provider that you can use as an example and as a base for implementing your own plugin providers.
github.com/digitalrebar/provision-plugins contains several production ready plugin provider implementations that you can use as a reference for implementing more advanced behaviours.
At a higher level, a plugin provider is an application that has 3 ways of being invoked:
1. plugin_provider define
When invoked with a single argument of define, the plugin provider must print the models.PluginProvider definition for the plugin provider in JSON format on stdout.
2. plugin_provider unpack /path/to/filespace/for/this/provider
When invoked with unpack /path, the plugin provider must unpack any embedded assets (extra executables and other artifacts like that) into the path passed in as the argument. Note that this does not include the embedded content pack, which is emitted as part of the define command.
3. plugin_provider listen /path/to/client/socket /path/to/server/socket
When invoked with listen, the plugin client must open an HTTP client connection on the client socket to post events and status updates back to dr-provision, and listen with an HTTP server on the server socket to receive action requests, stop requests, and events from dr-provision. Once both sockets are opened up and the plugin provider is ready to be configured, it should emit `READY!` followed by a newline on stdout. In all cases, the following environment variables will be set when the plugin provider is executed: RS_ENDPOINT will be a URL to the usual dr-provision API endpoint RS_TOKEN will be a long-lived token with superuser access rights RS_FILESERVER will be a URL to the static file server RS_WEBROOT will be the filesystem path to static file server space The plugin provider will be executed with its current directory set to a scratch directory it can use to hold temporary files.
Once the plugin provider is ready, its HTTP server should listen on the following paths:
POST /api-plugin/v4/config When a JSON object containing the Params field from the Plugin object this instance of the plugin provider is backing is POSTed to this API endpoint, the plugin should configure itself accordingly. This is the first call made into the plugin provider when it starts, and it can be called any time afterwards. POST /api-plugin/v4/stop When this API endpoint is POSTed to, the plugin provider should cleanly shut down. POST /api-plugin/v4/action When a JSON object containing a fully filled out models.Action is POSTed to this API endpoint, the plugin provider should take the appropriate action and return the results of the action. This endpoint must be able to handle all of the actions listed in the AvailableActions section of the definition that the define command returned. POST /api-plugin/v4/publish (DEPRECATED, use api.EventStream instead) When a JSON object containing a fully filled out models.Event is POSTed to this API endpoint, the plugin provider should handle the event as appropriate. Events will only be published to this endpoint if the plugin provider definition HasPublish flag is true. This endpoint is deprecated, as it is synchronous and can cause performance bottlenecks and potentially deadlocks, along with not being filterable on the server side. Using an api.EventStream is a better solution.
The HTTP client can POST back into dr-provision using the following paths on the client socket:
POST /api-plugin-server/v4/publish The body should be a JSON serialized models.Event, which will be broadcast to all interested parties. POST /api-plugin-server/v4/leaving This will cause dr-provision to cleanly shut down the plugin provider. The body does not matter. POST /api-plugin-server/v4/log The body should be a JSON serialized logger.Line structure, which will be added to the global dr-provision log.
InitApp initializes the plugin system and makes the base actions available in cobra CLI. It provides default implementations of the define, unpack, and listen commands, which will be backed by all the interfaces that whatever is passed in as pc satisfy.
Leaving allows the plugin to inform DRP that it is about to exit.
Publish allows the plugin provider to generate events back to DRP.
PluginActor is an optional interface that your plugin should implement if you plan on handling Actions. If the PluginProvider definition has a non-empty list of AvailableActions, then the Action method must be available and able to handle all of the Actions in that list.
Action takes a logger and a fully-filled out Action, and returns the results of that action along with a non-nil models.Error if an error occurred while performing the action.
PluginConfig is a mandatory interface that your plugin must implement. Config will be called with the fully expanded Params on the plugin object whenever this instance of the Plugin is started or whenever those Params change.
Config takes three arguments: a logger, an API client with superuser rights, and a map of all the params on the Plugin being configured. It should return a non-nil models.Error if the Config call fails for any reason.
PluginEventSelector is an optional interface that your plugin can implement to specify what events the plugin is interested in receiving from dr-provision. If this interface is implemented, then the HasPublish field in the PluginProvider definition must be false.
SelectEvents returns a slice of strings that define the events the Plugin wishes to receive.
PluginPublisher is an optional interface that your plugin can implement if it is interested in receiving events from dr-provision. There are a couple of things to be aware of when implementing a Publish method:
1. If you are implementing a Publish method, you should also implement a
SelectEvents method, and set the HasPublish flag on your PluginProvider definition to false. This will allow dr-provision to only send you the specific events you are interested in, and it will prevent your plugin from being able to bottleneck (or even deadlock) dr-provision.
2. If you choose to not implement a SelectEvents method, the HasPublish
flag on your PluginProvider definition must be set to true, and your Publish method will receive all the events dr-provision emits synchronously. It is therefore your responsibility handle taking action on the events in such a way that you do not cause a deadlock or a performance bottleneck.
Publish takes a logger and the event that was recieved, and returns a non-nil models.Error if there was an error handling the event.
PluginStop is an optional interface that your plugin can implement to provide custom behaviour whenever the plugin is stopped. Stop will be called when a plugin needs to stop operating. If implement the PluginStop interface, it will be called before the default stop action takes place.
Stop takes one argument, a logger.
PluginUnpacker is an optional interface that your plugin can implement if it needs to unpack additional assets into the static file space.
Unpack takes a logger and the location on the local filesystem any embedded assets should be unpacked into. It returns an error if there was a problem unpacking assets.
PluginValidator is an optional interface that your plugin can implement if it needs to check that it can run in the environment it was executed in. Validate is a good method to implement to test for other executables, etc. that the Plugin may rely on to operate.
Validate takes a logger and an API client with superuser permissions, and returns the results of validating the environment and a non-nil models.Error if the plugin cannot be used in the current environment.