Skip to content

Commit

Permalink
Merge in changes from nikos/snsnotify-plugin master branch.
Browse files Browse the repository at this point in the history
  • Loading branch information
nikos committed Aug 25, 2014
2 parents 7868b00 + 073c543 commit c1d4492
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 57 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
target
work

# IntelliJ project files
*.ipr
*.iml
*.iws
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
snsnotify-plugin
================

Sends build notifications to an AWS SNS topic as post-build step with
customizable subject and message payload.

1. `git clone https://github.com/jenkinsci/snsnotify-plugin`
2. `cd snsnotify-plugin`
3. `mvn hpi:run` compile and start Jetty with the Plugin ready to be used

Create an installable artifact:

1. `mvn clean install`
2. (wait for mvn to download the internet)
3. Manage Jenkins > Plugins > Advanced > Upload ```./target/snsnotify.hpi```
4. Restart Jenkins ([$JENKINS_URL]/restart)

Now, login to AWS and do the following:

1. Create an SNS Topic, subscribe a target SQS queue (or create subscription via email etc.)
2. Right-click on targeted SQS queue to confirm subscription

Finally, back to Jenkins...

1. Manage Jenkins > Configure Jenkins to use AWS creds and newly created Topic ARN
2. As part of your job: add post-build step for SNS notification, optionally configure subject and message (you can make use of build and environment variables, which do get substituted), resp. override Topic ARN (if you do not want to stick with globally configured one).
10 changes: 9 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.553</version>
<version>1.565.1</version>
</parent>

<artifactId>snsnotify</artifactId>
Expand All @@ -20,6 +20,14 @@
<id>mikewatt</id>
<name>Michael Watt</name>
</developer>
<developer>
<id>jkelabora</id>
<name>Julian Kelabora</name>
</developer>
<developer>
<id>nikos</id>
<name>Niko Schmuck</name>
</developer>
</developers>

<scm>
Expand Down
150 changes: 95 additions & 55 deletions src/main/java/org/jenkinsci/plugins/snsnotify/AmazonSNSNotifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,120 @@
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.PublishRequest;
import hudson.Launcher;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import java.io.IOException;
import java.util.logging.Logger;

public class AmazonSNSNotifier extends Notifier {

private final static Logger LOG = Logger.getLogger(AmazonSNSNotifier.class.getName());

private final String projectTopicArn;
private final String subjectTemplate;
private final String messageTemplate;

@DataBoundConstructor
public AmazonSNSNotifier(String projectTopicArn) {
public AmazonSNSNotifier(String projectTopicArn, String subjectTemplate, String messageTemplate) {
this.projectTopicArn = projectTopicArn;
this.subjectTemplate = subjectTemplate;
this.messageTemplate = messageTemplate;
}

public String getProjectTopicArn() {
return projectTopicArn;
}

@Override
public String getSubjectTemplate() {
return subjectTemplate;
}

public String getMessageTemplate() {
return messageTemplate;
}

public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}

@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {

if (build.getResult() == Result.FAILURE || build.getResult() == Result.UNSTABLE) {

String awsAccessKey = getDescriptor().getAwsAccessKey();
String awsSecretKey = getDescriptor().getAwsSecretKey();
String publishTopic = isEmpty(projectTopicArn) ?
getDescriptor().getDefaultTopicArn() : projectTopicArn;

if (isEmpty(publishTopic)) {
listener.error(
"No global or project topic ARN sent; cannot send SNS notification");
return true;
}

if (isEmpty(awsAccessKey) || isEmpty(awsSecretKey)) {
listener.error(
"AWS credentials not configured; cannot send SNS notification");
return true;
}

String snsApiEndpoint = getSNSApiEndpoint(publishTopic);
if (isEmpty(snsApiEndpoint)) {
listener.error(
"Could not determine SNS API Endpoint from topic ARN: " + publishTopic);
return true;
}

String subject = truncate(
String.format("Build %s: %s",
build.getResult().toString(), build.getFullDisplayName()), 100);

String message = Hudson.getInstance().getRootUrl() == null ?
Util.encode("(Global build server url not set)/" + build.getUrl()) :
Util.encode(Hudson.getInstance().getRootUrl() + build.getUrl());

AmazonSNSClient snsClient = new AmazonSNSClient(
new BasicAWSCredentials(awsAccessKey, awsSecretKey));
snsClient.setEndpoint(snsApiEndpoint);

try {
PublishRequest pubReq = new PublishRequest(publishTopic, message, subject);
snsClient.publish(pubReq);
} catch (Exception e) {
listener.error(
"Failed to send SNS notification: " + e.getMessage());
} finally {
snsClient.shutdown();
}
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {

String awsAccessKey = getDescriptor().getAwsAccessKey();
String awsSecretKey = getDescriptor().getAwsSecretKey();
String publishTopic = isEmpty(projectTopicArn) ?
getDescriptor().getDefaultTopicArn() : projectTopicArn;

if (isEmpty(publishTopic)) {
listener.error(
"No global or project topic ARN sent; cannot send SNS notification");
return true;
}

if (isEmpty(awsAccessKey) || isEmpty(awsSecretKey)) {
listener.error(
"AWS credentials not configured; cannot send SNS notification");
return true;
}

String snsApiEndpoint = getSNSApiEndpoint(publishTopic);
if (isEmpty(snsApiEndpoint)) {
listener.error(
"Could not determine SNS API Endpoint from topic ARN: " + publishTopic);
return true;
}

// ~~ prepare subject (incl. variable replacement)
String subject;
if (StringUtils.isEmpty(subjectTemplate)) {
subject = truncate(
String.format("Build %s: %s",
build.getResult().toString(), build.getFullDisplayName()), 100);
} else {
subject = replaceVariables(build, listener, subjectTemplate);
}

// ~~ prepare message (incl. variable replacement)
String message;
if (StringUtils.isEmpty(messageTemplate)) {
message = Hudson.getInstance().getRootUrl() == null ?
Util.encode("(Global build server url not set)/" + build.getUrl()) :
Util.encode(Hudson.getInstance().getRootUrl() + build.getUrl());
} else {
message = replaceVariables(build, listener, messageTemplate);
}

LOG.info("Setup SNS client '" + snsApiEndpoint + "' ...");
AmazonSNSClient snsClient = new AmazonSNSClient(
new BasicAWSCredentials(awsAccessKey, awsSecretKey));
snsClient.setEndpoint(snsApiEndpoint);

try {
String summary = String.format("subject=%s message=%s topic=%s", subject, message, publishTopic);
LOG.info("Publish SNS notification: " + summary + " ...");
PublishRequest pubReq = new PublishRequest(publishTopic, message, subject);
snsClient.publish(pubReq);
listener.getLogger().println("Published SNS notification: " + summary);
} catch (Exception e) {
listener.error(
"Failed to send SNS notification: " + e.getMessage());
} finally {
snsClient.shutdown();
}

return true;
Expand All @@ -108,6 +138,16 @@ private boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}

/**
* Replaces all build and environment variables given by the String tmpl.
*/
private String replaceVariables(AbstractBuild build, BuildListener listener, String tmpl)
throws IOException, InterruptedException {
EnvVars envVars = build.getEnvironment(listener);
String result = Util.replaceMacro(tmpl, build.getBuildVariableResolver());
return Util.replaceMacro(result, envVars);
}

/**
* Determine the SNS API endpoint to make API calls to, based on the region
* included in the topic ARN.
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/index.jelly
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div>
Send failed/unstable build notifications to an Amazon SNS Topic
Send build notifications to an Amazon SNS Topic
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@
<f:textbox />
</f:entry>

<f:entry title="Subject (default: Build {BUILD_RESULT} {BUILD_FULLDISPLAYNAME})" field="subjectTemplate">
<f:textbox />
</f:entry>

<f:entry title="Message (default: {BUILD_URL})" field="messageTemplate">
<f:textbox />
</f:entry>

</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
Specifies the message (payload) as published to the SNS topic.
Build variables can be used and are resolved before message is sent.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
Specifies the subject of the message published to the SNS topic.
Build variables can be used and are resolved before message is sent.
</div>

0 comments on commit c1d4492

Please sign in to comment.