-
Notifications
You must be signed in to change notification settings - Fork 929
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add documentation for notification plugins #360
- Loading branch information
Showing
5 changed files
with
504 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
% Notification Plugin Development | ||
% Greg Schueler | ||
% April 18, 2013 | ||
|
||
## About Rundeck Notifications | ||
|
||
Notifications are actions that are performed when a Job starts or finishes. | ||
|
||
Currently there are three conditions that can trigger notifications: | ||
|
||
* `onstart` - the Job started | ||
* `onsuccess` - the Job completed without error | ||
* `onfailure` - the Job failed or was aborted | ||
|
||
Rundeck has two built-in notification types that can be configured for Jobs: | ||
|
||
1. Send an email to a list of addresses | ||
2. POST XML to a list of URLs | ||
|
||
We now support plugins as well. | ||
|
||
## Plugin execution | ||
|
||
When a notification is defined for a Job, and the associated trigger occurs, your plugin will be executed | ||
and passed in two sets of Map data: | ||
|
||
1. Configuration data - the user-supplied configuration for the plugin | ||
2. Execution data - information about the Job and Execution for the notification | ||
|
||
### Configuration data | ||
|
||
The Configuration data is fully custom depending on your plugin, and is described in the [Plugin configuration properties](#plugin-configuration-properties) section. | ||
|
||
### Execution data | ||
|
||
The execution data is included as a Map containing the following keys and values: | ||
|
||
`id` | ||
: ID of the execution | ||
`href` | ||
: URL to the execution output view | ||
`status` | ||
: Execution state ('running','failed','aborted','succeeded') | ||
`user` | ||
: User who started the job | ||
`dateStarted` | ||
: Start time (java.util.Date) | ||
`dateStartedUnixtime` | ||
: Start time as milliseconds since epoch (long) | ||
`dateStartedW3c` | ||
: Start time as a W3C formatted String | ||
`description` | ||
: Summary string for the execution | ||
`argstring` | ||
: Argument string for any job options | ||
`project` | ||
: Project name | ||
`loglevel` | ||
: Loglevel string ('ERROR','WARN','INFO','VERBOSE','DEBUG') | ||
|
||
The following values may be available after the job is finished (not available for `onstart` trigger): | ||
|
||
`failedNodeListString` | ||
: Comma-separated list of any nodes that failed, if present | ||
`failedNodeList` | ||
: Java List of any node names that failed, if present | ||
`dateEnded` | ||
: End time (java.util.Date) | ||
`dateEndedUnixtime` | ||
: End time as milliseconds since epoch (long) | ||
`dateEndedW3c` | ||
: End time as W3C formatted string | ||
`abortedby` | ||
: User who aborted the execution | ||
|
||
* `job` information is in a `job` entry and contains another Map: | ||
|
||
`id` | ||
: Job ID | ||
`href` | ||
: URL to Job view page | ||
`name` | ||
: Job name | ||
`group` | ||
: Job group | ||
`project` | ||
: Project name | ||
`description` | ||
: Job Description | ||
`averageDuration` | ||
: Average job duration in Milliseconds, if available | ||
|
||
## Plugin configuration properties | ||
|
||
Each plugin can define a set of "configuration" properties which allow users to specify input that the plugin can | ||
use when it operates. | ||
|
||
Currently, Notification plugins support "Instance"-scoped configuration properties. This simply means that | ||
any configuration variables you define in your plugin are exposed in the Rundeck GUI when the user adds or modifies | ||
the plugin within a Job. | ||
|
||
We plan to support more configuration scopes in the future. | ||
|
||
## Types of Notification plugins | ||
|
||
Rundeck supports two development modes for Notification plugins: | ||
|
||
1. Java-based development deployed as a Jar file. | ||
2. Groovy-based deployed as a single `.groovy` script. | ||
|
||
Currently "script-based" plugins (shell scripts, that is) are not supported. | ||
|
||
## Java plugins | ||
|
||
Java-based plugins can be developed just as any other Rundeck plugin, as described in the chapter [Plugin Development - Java Plugin Development](#java-plugin-development). | ||
|
||
These plugin classes should implement the `com.dtolabs.rundeck.plugins.notification.NotificationPlugin` interface: | ||
|
||
public interface NotificationPlugin { | ||
/** | ||
* Post a notification for the given trigger, dataset, and configuration | ||
* @param trigger event type causing notification | ||
* @param executionData execution data | ||
* @param config notification configuration | ||
*/ | ||
public boolean postNotification(String trigger,Map executionData,Map config); | ||
} | ||
|
||
To define configuration properties for your plugin, you use the same mechanisms as for Workflow Steps, described under the chapter [Workflow Step Plugin Development - Plugin Descriptions](workflow-step-plugin-development.html#plugin-descriptions). | ||
|
||
The simplest way to do this is to use [Description Annotations](workflow-step-plugin-development.html#description-annotations). Here is an example class annotated to describe it to the Rundeck GUI: | ||
|
||
@Plugin(service="Notification", name="example") | ||
@PluginDescription(title="Example Plugin", description="An example Plugin for Rundeck Notifications.") | ||
public class ExampleNotificationPlugin implements NotificationPlugin{ | ||
|
||
@PluginProperty(name = "test" ,title = "Test String", description = "a description") | ||
private String test; | ||
|
||
public boolean postNotification(String trigger, Map executionData, Map config) { | ||
System.err.printf("Trigger %s fired for %s, configuration: %s\n",trigger,executionData,config); | ||
System.err.printf("Local field test is: %s\n",test); | ||
return true; | ||
} | ||
} | ||
|
||
## Groovy plugins | ||
|
||
For Notifications, we introduce a new way to develop plugins, using a simple Groovy-based DSL. This gives you a simpler way to develop plugins, but still provides the power of Java. | ||
|
||
To create a Groovy based plugin, create a file named `MyNotificationPlugin.groovy` in the plugins directory for Rundeck. | ||
|
||
You must restart rundeck to make the plugin available the first time, but you can subsequently update the .groovy script without restarting Rundeck. | ||
|
||
### Groovy DSL | ||
|
||
Within the Groovy script, you define your plugin by calling the `rundeckPlugin` method, and pass it both the Class of the type of plugin, and a Closure used to build the plugin object. | ||
|
||
import com.dtolabs.rundeck.plugins.notification.NotificationPlugin | ||
rundeckPlugin(NotificationPlugin){ | ||
//plugin definition goes here... | ||
} | ||
|
||
In this case we use the same `NotificationPlugin` interface used for Java plugins. Currently this is the only supported type that can be used. | ||
|
||
#### Definition | ||
|
||
Within the definition section you can define your plugin's Description to be shown in the Rundeck GUI, as well as | ||
configuration properties to present to the user. | ||
|
||
*Properties* | ||
|
||
Set these properties to change the GUI display of your plugin: | ||
|
||
title='My Plugin' | ||
description='Does some action' | ||
|
||
*Configuration* | ||
|
||
Use a `configuration` closure to define configuration properties: | ||
|
||
configuration{ | ||
//property definitions go here... | ||
} | ||
|
||
*Property Definitions* | ||
|
||
User configuration properties can be defined in a few ways. To define a property within the `configuration` section, you can use either of these forms: | ||
|
||
1. method call form, specifying the attributes of the property: | ||
|
||
myproperty (title: "My Property", description: "Something", type: 'Integer') | ||
|
||
2. assignment form. This form guesses the data type and sets the defaultValue, but does not add any other attributes. | ||
|
||
myproperty2="default value" | ||
//the above is equivalent to: | ||
myproperty2(defaultValue:"default value", type: 'String') | ||
|
||
myproperty3=["value","another","text"] | ||
//the above is equivalent to: | ||
myproperty3(type:'FreeSelect',values:["value","another","text"]) | ||
|
||
Each property has several attributes you can define, but only `name` and `type` are required: | ||
|
||
* `name` - the unique identifier for this property | ||
* `type` - the data type to use for the property, defaults to String. Available types: | ||
* `String` - user can enter text | ||
* `Integer`, `Long` - user can enter a number | ||
* `Boolean` - user is shown a checkbox | ||
* `Select` or `FreeSelect` - user can choose from a list. With `FreeSelect`, the user can also type in any value | ||
* `title` - a user-readable string to describe the property | ||
* `description` - a string describing the property | ||
* `required` - whether the property is required to have a value | ||
* `defaultValue` - any default value for the property | ||
|
||
In addition to these properties, for `Select` or `FreeSelect` type, you can define: | ||
|
||
* `values` - list of string values the user can select from | ||
|
||
To define a validation check for a property, use the first form and supply a closure. The implicit `it` variable will be the value of the property to check, and your closure should return `true` if the value is valid. | ||
|
||
phone_number(title: "Phone number"){ | ||
it.replaceAll(/[^\d]/,'')==~/^\d{10}$/ | ||
} | ||
|
||
### Notification handlers | ||
|
||
For a `NotificationPlugin`, you can define custom handlers for each of the notification triggers (`onsuccess`, `onfailure`, and `onstart`). | ||
|
||
Simply define a closure with the given trigger name, and return a true value if your action was successful: | ||
|
||
onstart{ Map execution, Map configuration -> | ||
//perform an action using the execution and configuration | ||
println "Job ${execution.job.name} has been started by ${execution.user}..." | ||
return true | ||
} | ||
onsuccess{ Map execution, Map configuration -> | ||
//perform an action using the execution and configuration | ||
println "Success! Job ${execution.job.name} worked fine." | ||
return true | ||
} | ||
onfailure{ Map execution, Map configuration -> | ||
//perform an action using the execution and configuration | ||
println "Oh No! Job ${execution.job.name} didn't work out." | ||
return true | ||
} | ||
|
||
If your closure returns a `false` value, then Rundeck will log an error in the server log. | ||
|
||
### Example | ||
|
||
Here is a minimal example: | ||
|
||
**MinimalNotificationPlugin.groovy**: | ||
|
||
import com.dtolabs.rundeck.plugins.notification.NotificationPlugin; | ||
|
||
rundeckPlugin(NotificationPlugin){ | ||
onstart { | ||
println("success: data ${execution}") | ||
true | ||
} | ||
|
||
onfailure { | ||
println("failure: data ${execution}") | ||
true | ||
} | ||
|
||
onsuccess { | ||
println("job start: data ${execution}") | ||
true | ||
} | ||
} | ||
|
||
Here is a full example showing plugin GUI metadata, configuration properties, and | ||
alternate closure parameter lists: | ||
|
||
**MyNotificationPlugin.groovy**: | ||
|
||
import com.dtolabs.rundeck.plugins.notification.NotificationPlugin; | ||
|
||
rundeckPlugin(NotificationPlugin){ | ||
title="Example Plugin" | ||
description="An example" | ||
|
||
configuration{ | ||
|
||
test1 title:"Test1", description:"Simple string" | ||
//Validation can be added with a closure | ||
test2(title:'Test2',description:"Matches a regex"){ | ||
it=~/^\d+$/ | ||
} | ||
//required select value, becomes a Select type | ||
test3 values: ["a","b","c"], required:true | ||
//if not required, becomes a FreeSelect | ||
test4 values: ["a","b","c"] | ||
//If type is not specified, the defaultValue will be used to guess | ||
test5 defaultValue: 3 //becomes Integer type | ||
test6 defaultValue:true //becomes Boolean type | ||
|
||
//these properties are assigned default values and automatically typed | ||
test7=123 | ||
test8="abc" | ||
test9=true | ||
test10=false | ||
test11=["x","y","z"] //becomes a FreeSelect | ||
|
||
//redefining the same property will modify it | ||
test11 title:"My Select Field", description:"Free Select field", defaultValue:"y", required:true | ||
} | ||
|
||
onstart { Map executionData,Map config -> | ||
println("script, success: data ${executionData}, config: ${config}") | ||
true | ||
} | ||
|
||
onfailure { Map executionData -> | ||
//Single argument, the configuration properties are available automatically | ||
println("script, failure: data ${executionData}, test1: ${test1}, test2: ${test2} test3: ${test3}") | ||
true | ||
} | ||
|
||
onsuccess { | ||
//with no args, there is a "configuration" and an "execution" variable in the context | ||
println("script, start: data ${execution}, test1: ${configuration.test1}, test2: ${configuration.test2} test3: ${configuration.test3}") | ||
true | ||
} | ||
} | ||
|
Oops, something went wrong.