Skip to content

Commit

Permalink
Merge pull request #167 from kmadel/issue50
Browse files Browse the repository at this point in the history
JENKINS-27652 - Workflow Support #50
  • Loading branch information
kmadel committed Mar 14, 2016
2 parents 2ab6210 + 073c97e commit 5149c5a
Show file tree
Hide file tree
Showing 13 changed files with 499 additions and 1 deletion.
78 changes: 77 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.567</version>
<version>1.609.2</version>
</parent>

<artifactId>slack</artifactId>
Expand All @@ -27,6 +27,9 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<workflow.version>1.11</workflow.version>
<hamcrest.version>1.3</hamcrest.version>
<powermock.version>1.6.2</powermock.version>
</properties>

<licenses>
Expand All @@ -44,6 +47,11 @@
</scm>

<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>junit</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down Expand Up @@ -100,6 +108,74 @@
<artifactId>jna</artifactId>
<version>3.2.2</version>
</dependency>
<!-- for workflow support -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>${workflow.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency> <!-- StepConfigTester -->
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<classifier>tests</classifier>
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-reflect</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
156 changes: 156 additions & 0 deletions src/main/java/jenkins/plugins/slack/workflow/SlackSendStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package jenkins.plugins.slack.workflow;

import hudson.AbortException;
import hudson.Extension;
import hudson.Util;
import hudson.model.TaskListener;
import jenkins.model.Jenkins;
import jenkins.plugins.slack.Messages;
import jenkins.plugins.slack.SlackNotifier;
import jenkins.plugins.slack.SlackService;
import jenkins.plugins.slack.StandardSlackService;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import javax.annotation.Nonnull;
import javax.inject.Inject;

/**
* Workflow step to send a Slack channel notification.
*/
public class SlackSendStep extends AbstractStepImpl {

private final @Nonnull String message;
private String color;
private String token;
private String channel;
private String teamDomain;
private boolean failOnError;


@Nonnull
public String getMessage() {
return message;
}

public String getColor() {
return color;
}

@DataBoundSetter
public void setColor(String color) {
this.color = Util.fixEmpty(color);
}

public String getToken() {
return token;
}

@DataBoundSetter
public void setToken(String token) {
this.token = Util.fixEmpty(token);
}

public String getChannel() {
return channel;
}

@DataBoundSetter
public void setChannel(String channel) {
this.channel = Util.fixEmpty(channel);
}

public String getTeamDomain() {
return teamDomain;
}

@DataBoundSetter
public void setTeamDomain(String teamDomain) {
this.teamDomain = Util.fixEmpty(teamDomain);
}

public boolean isFailOnError() {
return failOnError;
}

@DataBoundSetter
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}

@DataBoundConstructor
public SlackSendStep(@Nonnull String message) {
this.message = message;
}

@Extension
public static class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(SlackSendStepExecution.class);
}

@Override
public String getFunctionName() {
return "slackSend";
}

@Override
public String getDisplayName() {
return Messages.SlackSendStepDisplayName();
}
}

public static class SlackSendStepExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {

private static final long serialVersionUID = 1L;

@Inject
transient SlackSendStep step;

@StepContextParameter
transient TaskListener listener;

@Override
protected Void run() throws Exception {

//default to global config values if not set in step, but allow step to override all global settings
Jenkins jenkins;
//Jenkins.getInstance() may return null, no message sent in that case
try {
jenkins = Jenkins.getInstance();
} catch (NullPointerException ne) {
listener.error(Messages.NotificationFailedWithException(ne));
return null;
}
SlackNotifier.DescriptorImpl slackDesc = jenkins.getDescriptorByType(SlackNotifier.DescriptorImpl.class);
String team = step.teamDomain != null ? step.teamDomain : slackDesc.getTeamDomain();
String token = step.token != null ? step.token : slackDesc.getToken();
String channel = step.channel != null ? step.channel : slackDesc.getRoom();
String color = step.color != null ? step.color : "";

//placing in console log to simplify testing of retrieving values from global config or from step field; also used for tests
listener.getLogger().println(Messages.SlackSendStepConfig(step.teamDomain == null, step.token == null, step.channel == null, step.color == null));

SlackService slackService = getSlackService(team, token, channel);
boolean publishSuccess = slackService.publish(step.message, color);
if (!publishSuccess && step.failOnError) {
throw new AbortException(Messages.NotificationFailed());
} else if (!publishSuccess) {
listener.error(Messages.NotificationFailed());
}
return null;
}

//streamline unit testing
SlackService getSlackService(String team, String token, String channel) {
return new StandardSlackService(team, token, channel);
}

}

}
7 changes: 7 additions & 0 deletions src/main/resources/jenkins/plugins/slack/Messages.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Localization for config pages
SlackSendStepDisplayName=Send Slack Message

# Messages to display in the build logs
NotificationFailed=Slack notification failed. See Jenkins logs for details.
NotificationFailedWithException=Slack notification failed with exception: {0}
SlackSendStepConfig=Slack Send Workflow step configured values from global config - teamDomain: {0}, token: {1}, channel: {2}, color: {3}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry field="message" title="Message">
<f:textbox/>
</f:entry>
<f:entry field="color" title="Color">
<f:textbox/>
</f:entry>
<f:advanced>
<f:entry field="channel" title="Channel">
<f:textbox />
</f:entry>
<f:entry field="token" title="Integration Token">
<f:textbox />
</f:entry>
<f:entry field="teamDomain" title="Team Domain">
<f:textbox />
</f:entry>
<f:entry field="failOnError">
<f:checkbox title="Fail On Error" default="false"/>
</f:entry>
</f:advanced>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
Allows overriding the Slack Plugin channel specified in the global configuration.<br>
<code>slackSend channel: "#channel-name", message: "Build Started: ${env.JOB_NAME} ${env.BUILD_NUMBER}"</code>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
An <b>optional</b> value that can either be one of <b>good</b>, <b>warning</b>, <b>danger</b>, or any <b>hex color code</b> (eg. #439FE0).
This value is used to color the border along the left side of the message attachment.<br>
<code>slackSend color: "#439FE0", message: "Build Started: ${env.JOB_NAME} ${env.BUILD_NUMBER}"</code>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
If set to true, then the step will abort the Workflow run if there is an error sending message.<br>
<code>hipchatSend failOnError: true, message: "Build Started: ${env.JOB_NAME} ${env.BUILD_NUMBER}"</code>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div>
This is the main text in a message attachment, and can contain standard message markup.
The content will automatically collapse if it contains 700+ characters or 5+ linebreaks, and will display a "Show more..." link to expand the content.
Message may include global variables, for example environment and currentBuild variables:<br>
<code>
slackSend "started ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
</code>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Allows overriding the Slack Plugin Integration Team Domain specified in the global configuration.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Allows overriding the Slack Plugin Integration Token specified in the global configuration.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div>
Simple step for sending a Slack message to specified channel.<br>
Use the advanced settings to override the Slack Plugin global configuration to include: <code>token</code> and <code>channel</code>.<br>
Please see the Slack Plugin global configuration for more details on the fields.

Usage Example:<br>
<code>
slackSend "Build Started - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
</code>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package jenkins.plugins.slack.workflow;


import hudson.model.Result;
import jenkins.plugins.slack.Messages;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.StepConfigTester;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;

public class SlackSendStepIntegrationTest {
@Rule
public JenkinsRule jenkinsRule = new JenkinsRule();

@Test
public void configRoundTrip() throws Exception {
SlackSendStep step1 = new SlackSendStep("message");
step1.setColor("good");
step1.setChannel("#channel");
step1.setToken("token");
step1.setTeamDomain("teamDomain");
step1.setFailOnError(true);

SlackSendStep step2 = new StepConfigTester(jenkinsRule).configRoundTrip(step1);
jenkinsRule.assertEqualDataBoundBeans(step1, step2);
}

@Test
public void test_global_config_override() throws Exception {
WorkflowJob job = jenkinsRule.jenkins.createProject(WorkflowJob.class, "workflow");
//just define message
job.setDefinition(new CpsFlowDefinition("slackSend(message: 'message', teamDomain: 'teamDomain', token: 'token', channel: '#channel', color: 'good');", true));
WorkflowRun run = jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0).get());
//everything should come from step configuration
jenkinsRule.assertLogContains(Messages.SlackSendStepConfig(false, false, false, false), run);
}

@Test
public void test_fail_on_error() throws Exception {
WorkflowJob job = jenkinsRule.jenkins.createProject(WorkflowJob.class, "workflow");
//just define message
job.setDefinition(new CpsFlowDefinition("slackSend(message: 'message', teamDomain: 'teamDomain', token: 'token', channel: '#channel', color: 'good', failOnError: true);", true));
WorkflowRun run = jenkinsRule.assertBuildStatus(Result.FAILURE, job.scheduleBuild2(0).get());
//everything should come from step configuration
jenkinsRule.assertLogContains(Messages.NotificationFailed(), run);
}
}
Loading

0 comments on commit 5149c5a

Please sign in to comment.