Plugin drivers
Plugin drivers provide the functionality to find, load and interface to the plugins for the Pact test framework. Each language with a Pact implementation requires a plugin driver to work with plugins.
Main plugin driver responsibilities:
- The ability to find plugins.
- Load plugins and extract the plugin manifests that describe what the plugin provides.
- Provide a catalogue of features provided by the plugins.
- Provide a messaging bus to facilitate communication between the language implementation and the plugins.
- Manage the plugin lifecycles.
Locating plugins
Plugins must be stored on the system in a Pact plugins directory, defined either by the PACT_PLUGIN_DIR
environment
variable or in the default $HOME/.pact/plugins
. Each plugin must be in a separate sub-directory (plugin name/version) and contain a plugin
manifest file pact-plugin.json
.
Plugin manifest file
The plugin manifest file describes what the plugin provides and how to load it. It is a JSON file with the following attributes:
Attribute | Description |
---|---|
manifestVersion | Version of the manifest file format. Current is 1 |
pluginInterfaceVersion | Version of the plugin interface the plugin supports. Current is 1 |
name | Name of the plugin |
version | Version of the plugin, following the semver format |
executableType | Executable type of the plugin. Supported types are: exec (executable binary) |
minimumRequiredVersion | Minimum required version of the runtime/interpreter to run the plugin |
entryPoint | The main executable for the plugin |
entryPoints | Optional map of additional entry points. This allows additional entry points for other operating systems (i.e. requiring a .bat file for Windows) |
dependencies | List of system dependencies or plugins required to be able to execute this plugin |
Example of a manifest for a plugin written in Ruby that provides matching CSV files:
{
"manifestVersion": 1,
"pluginInterfaceVersion": 1,
"name": "pact-csv",
"version": "0.0.0",
"executableType": "exec",
"minimumRequiredVersion": "2.7.2",
"entryPoint": "bundle exec main.rb"
}
See CSV Plugin and Protobuf plugin for examples of plugin manifests.
Getting the port of the plugin GRPC server
When the plugin is started (except for the DLL executable type), the plugin will print a JSON message to its standard output that contains the port that the plugin GRPC server is running on. The driver needs to poll the plugin standard output for this message.
The message will have the following attributes:
Attribute | Description |
---|---|
port | The port number the GRPC server for the plugin is listening on |
serverKey | A randomly generated key required to use as a bearer token when communicating with the plugin |
Example:
{"port": 12345, "serverKey": "b37d2d9a9ceb"}
Init request to the plugin
Once the port has been extracted from the plugin standard output, the driver must send a InitPluginRequest
message via GRPC to the plugin. The plugin will respond with a InitPluginResponse
which will contain all the
catalogue entries for the features that the plugin provides. The driver needs to update its catalogue with these
entries and then publish the updated catalogue to all loaded plugins (including the new one).
Feature Catalogue
Each entry in the catalogue is keyed based on the following structure: $providerType/$name?/$type/$key
, where the
different parts are defined by:
Attribute | Description |
---|---|
providerType | Denotes an entry from the core Pact framework (core ) or from a plugin (plugin ) |
name | The name of the plugin (omitted for core entries) |
type | The type of the entry. Valid values are: content-matcher, content-generator, matcher, interaction |
key | Key for the type. It must be unique withing the entries for the plugin. |
For example, a plugin entry for matching CSV bodies would be plugin/csv/content-matcher/csv
.
Each entry can also contain associated data in key/value form.
Catalogue entry types
The following describes the types of entries:
Content Matcher (content-matcher)
Content matchers are responsible for matching request and response bodies and message payloads. Each entry must have
a content-types
associated value with the list of content types the matcher supports (separated by semi-colons ;
).
For example, the entry for a CSV content matcher would be:
{
type: EntryType::ContentMatcher,
key: "csv",
values: {
"content-types": "text/csv;application/csv"
}
}
Content Generator (content-generator)
Content matchers are responsible for constructing request and response bodies and message payloads. Each entry must have
a content-types
associated value with the list of content types the generator supports (separated by semi-colons ;
).
For example, the entry for a CSV content generator would be:
{
type: EntryType::ContentGenerator,
key: "csv",
values: {
"content-types": "text/csv;application/csv"
}
}
Matcher (matcher)
Implementation of a matching rule. Mainly used to match the fields and attributes in the bodies and payloads.
Interaction (interaction)
Provides a type of interaction. The standard interaction types are synchronous request/response (HTTP), asynchronous messages (one off or fire and forget) and synchronous messages (request/response, like gRPC).
Core catalogue entries
The driver must provide the following entries from the Pact framework:
Key | Description |
---|---|
core/interaction/http | Support Http/1.1 interactions (request/response) |
core/interaction/https | Support Http/1.1 + TLS interactions (request/response) |
core/interaction/message | Support message interactions |
core/matcher/v2-regex | V2 spec regex matcher |
core/matcher/v2-type | V2 spec type matcher |
core/matcher/v3-number-type | V3 spec number matcher |
core/matcher/v3-integer-type | V3 spec integer matcher |
core/matcher/v3-decimal-type | V3 spec decimal matcher |
core/matcher/v3-date | V3 spec date matcher |
core/matcher/v3-time | V3 spec time matcher |
core/matcher/v3-datetime | V3 spec DateTime matcher |
core/matcher/v2-min-type | V2 spec minimum type matcher |
core/matcher/v2-max-type | V2 spec maximum type matcher |
core/matcher/v2-minmax-type | V2 spec minimum/maximum type matcher |
core/matcher/v3-includes | V3 spec includes matcher |
core/matcher/v3-null | V3 spec null matcher |
core/matcher/v4-equals-ignore-order | V4 spec ignore array order matcher matcher |
core/matcher/v4-min-equals-ignore-order | V4 spec ignore array order matcher matcher |
core/matcher/v4-max-equals-ignore-order | V4 spec ignore array order matcher matcher |
core/matcher/v4-minmax-equals-ignore-order | V4 spec ignore array order matcher matcher |
core/matcher/v3-content-type | V3 spec content type matcher |
core/matcher/v4-array-contains | V4 spec array contains matcher |
core/matcher/v1-equality | V1 spec equality matcher |
core/content-matcher/xml | Matcher for XML content types |
core/content-matcher/json | Matcher for JSON content types |
core/content-matcher/text | Matcher for Text content types |
core/content-matcher/multipart-form-data | Matcher for Multipart Form POST content types |
core/content-matcher/form-urlencoded | Matcher for URL-encoded Form POST content types |
core/content-generator/json | Generator for JSON payloads |
Plugin driver API
The plugin drivers are required to expose the following API to client language implementations:
Catalogue Manager
The catalogue manager stores all the catalogue entries from the core framework and any loaded plugins.
RegisterPluginEntries(name: String, catalogueList: List<CatalogueEntry<gt;)
Function to add a list of entries for a plugin.
RegisterCoreEntries(entries: List<CatalogueEntry<gt;)
Function to add the list of core framework entries.
Entries
Returns all the catalogue entries
LookupEntry(key: String)
Lookup an entry by key.
FindContentMatcher(contentType: ContentType)
Search the catalogue for a content matcher that supports the given content type.
FindContentGenerator(contentType: ContentType)
Search the catalogue for a content generator that supports the given content type.
RemovePluginEntries(name: String)
Remove all the entries for a plugin. This is needed when a plugin is unloaded.
Plugin Manager
The plugin manager is responsible for finding, loading and unloading plugins. It also provides the interface to call out to the plugin (gRPC stub or channel).
LoadPlugin(plugin: PluginDependency)
Load a plugin given a plugin dependency (name, version and list of dependencies). The plugin manager must keep track of all currently loaded plugins in a global plugin register so that the plugins are not loaded more than once.
Every time a plugin is loaded, send an anonymous event to Google Analytics to track the details of the loaded plugin .
To disable tracking, users can set the pact_do_not_track
system property or environment variable to true
.
The following attributes are sent to GA:
v: 1 // Version of the API
tid: UA-117778936-1 // Property ID
cid: <UUID> // Anonymous Client ID.
an: <NAME> // App name.
aid: <NAME> // App Id
av: <VERSION> // App version.
aip: true // Anonymise IP address
ds: <PLUGIN NAME> // Data source
cd1: <PLUGIN NAME> // Custom Dimension 1: library
cd2: CI or unknown // Custom Dimension 2: context
cd3: <OS>-<ARCH> // Custom Dimension 3: osarch
cd4: <PLUGIN NAME> // Custom Dimension 4: plugin_name
cd5: <PLUGIN VERSION> // Custom Dimension 5: plugin_version
el: Plugin loaded // Event
ec: Plugin // Category
ea: Loaded // Action
ev: 1 // Value
LookupPlugin(plugin: PluginDependency)
Look up a plugin given a plugin dependency (name, version and list of dependencies) in the global plugin register.
LoadPluginManifest(plugin: PluginDependency)
Return the plugin manifest given a plugin dependency (name, version and list of dependencies). Will first look in the global plugin manifest registry and then load the manifest from disk if not found in the registry.
LookupPluginManifest(plugin: PluginDependency)
Lookup the plugin manifest in the global plugin manifest registry.
InitialisePlugin(manifest: PactPluginManifest)
Start the plugin by calling StartPluginProcess
and send the InitPluginRequest message to the plugin. Adds the plugin
to the global plugin register and updates the Catalogue Manager with all entries from the plugin.
StartPluginProcess(manifest: PactPluginManifest)
Starts the plugin using the entries from the manifest, then parsers the port and server key from the plugin process standard output.
ShutdownPlugins
Shut down all plugin processes.
PublishUpdatedCatalogue
Every time the catalogue is updated, this function must be called to publish the updated catalogue to all running plugins.