Skip to content

Commit

Permalink
Add documentation for notification plugins #360
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed Apr 19, 2013
1 parent 80022b9 commit d6a8350
Show file tree
Hide file tree
Showing 5 changed files with 504 additions and 9 deletions.
12 changes: 10 additions & 2 deletions docs/en/developer/02-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ If you have created a plugin for Rundeck 1.4 or earlier, you will need to update
### Metadata

* the `Rundeck-Plugin-Version` for Java plugins has updated to `1.1`. If a plugin specifies the earlier `1.0`, Rundeck 1.5 will not load it.
*

### Java Interfaces

Expand Down Expand Up @@ -143,7 +142,7 @@ Then include the jar files in the Plugin's jar contents:
lib/somejar-1.2.jar
lib/anotherjar-1.3.jar

### Available Services:
### Available Services

* `NodeExecutor` - executes a command on a node
* `FileCopier` - copies a file to a node
Expand All @@ -157,6 +156,11 @@ Workflow Step services (described more in the [Workflow Step Plugin Development]
* `WorkflowNodeStep` - runs a single step for each node in a workflow
* `RemoteScriptNodeStep` - generates a script or command to execute remotely for each node in a workflow

Notification services

* `Notification` - performs an action after a Job state trigger.
See the chapter about [Notification Plugin Development](notification-plugin-development.html).

## Provider Lifecycle

Provider classes are instantiated when needed by the Framework object, and the
Expand Down Expand Up @@ -303,6 +307,10 @@ More information is available in the Javadoc.

Refer to the section: [Workflow Step Plugin Development](workflow-step-plugin-development.html).

### Notification Providers

Refer to the section: [Notification Plugin Development](notification-plugin-development.html).

## Script Plugin Development

Script plugins can provide the same services as Java plugins, but they do so
Expand Down
335 changes: 335 additions & 0 deletions docs/en/developer/04-notification-plugins.md
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
}
}

Loading

0 comments on commit d6a8350

Please sign in to comment.