Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bot-user support #258

Merged
merged 4 commits into from
Dec 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ Create a new ***Secret text*** credential:
Select that credential as the value for the ***Integration Token Credential ID*** field:
![image](https://cloud.githubusercontent.com/assets/983526/17971458/ec296bf6-6aa8-11e6-8d19-06d9f1c9d611.png)


#### Bot user option
This plugin supports sending notifications via bot users. You can enable bot user support from both
global and project configurations. If the notification will be sent to a user via direct message,
default integration sends it via @slackbot, you can use this option if you want to send messages via a bot user.
You need to provide credentials of the bot user for integration token credentials to use this feature.

#### Jenkins Pipeline Support

Includes [Jenkins Pipeline](https://github.com/jenkinsci/workflow-plugin) support as of version 2.0:
Expand Down
38 changes: 32 additions & 6 deletions src/main/java/jenkins/plugins/slack/SlackNotifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class SlackNotifier extends Notifier {
private String teamDomain;
private String authToken;
private String authTokenCredentialId;
private boolean botUser;
private String room;
private String sendAs;
private boolean startNotification;
Expand Down Expand Up @@ -91,6 +92,10 @@ public String getAuthTokenCredentialId() {
return authTokenCredentialId;
}

public boolean getBotUser() {
return botUser;
}

public String getSendAs() {
return sendAs;
}
Expand Down Expand Up @@ -192,7 +197,7 @@ public void setCustomMessage(String customMessage) {
}

@DataBoundConstructor
public SlackNotifier(final String teamDomain, final String authToken, final String room, final String authTokenCredentialId,
public SlackNotifier(final String teamDomain, final String authToken, final boolean botUser, final String room, final String authTokenCredentialId,
final String sendAs, final boolean startNotification, final boolean notifyAborted, final boolean notifyFailure,
final boolean notifyNotBuilt, final boolean notifySuccess, final boolean notifyUnstable, final boolean notifyBackToNormal,
final boolean notifyRepeatedFailure, final boolean includeTestSummary, CommitInfoChoice commitInfoChoice,
Expand All @@ -201,6 +206,7 @@ public SlackNotifier(final String teamDomain, final String authToken, final Stri
this.teamDomain = teamDomain;
this.authToken = authToken;
this.authTokenCredentialId = StringUtils.trim(authTokenCredentialId);
this.botUser = botUser;
this.room = room;
this.sendAs = sendAs;
this.startNotification = startNotification;
Expand All @@ -227,8 +233,10 @@ public SlackService newSlackService(AbstractBuild r, BuildListener listener) {
teamDomain = getDescriptor().getTeamDomain();
}
String authToken = this.authToken;
boolean botUser = this.botUser;
if (StringUtils.isEmpty(authToken)) {
authToken = getDescriptor().getToken();
botUser = getDescriptor().getBotUser();
}
String authTokenCredentialId = this.authTokenCredentialId;
if (StringUtils.isEmpty(authTokenCredentialId)) {
Expand All @@ -251,7 +259,7 @@ public SlackService newSlackService(AbstractBuild r, BuildListener listener) {
authTokenCredentialId = env.expand(authTokenCredentialId);
room = env.expand(room);

return new StandardSlackService(teamDomain, authToken, authTokenCredentialId, room);
return new StandardSlackService(teamDomain, authToken, authTokenCredentialId, botUser, room);
}

@Override
Expand Down Expand Up @@ -279,6 +287,7 @@ public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
private String teamDomain;
private String token;
private String tokenCredentialId;
private boolean botUser;
private String room;
private String sendAs;

Expand All @@ -300,6 +309,10 @@ public String getTokenCredentialId() {
return tokenCredentialId;
}

public boolean getBotUser() {
return botUser;
}

public String getRoom() {
return room;
}
Expand Down Expand Up @@ -337,6 +350,7 @@ public SlackNotifier newInstance(StaplerRequest sr, JSONObject json) {
String teamDomain = sr.getParameter("slackTeamDomain");
String token = sr.getParameter("slackToken");
String tokenCredentialId = json.getString("tokenCredentialId");
boolean botUser = "true".equals(sr.getParameter("slackBotUser"));
String room = sr.getParameter("slackRoom");
boolean startNotification = "true".equals(sr.getParameter("slackStartNotification"));
boolean notifySuccess = "true".equals(sr.getParameter("slackNotifySuccess"));
Expand All @@ -350,7 +364,7 @@ public SlackNotifier newInstance(StaplerRequest sr, JSONObject json) {
CommitInfoChoice commitInfoChoice = CommitInfoChoice.forDisplayName(sr.getParameter("slackCommitInfoChoice"));
boolean includeCustomMessage = "on".equals(sr.getParameter("includeCustomMessage"));
String customMessage = sr.getParameter("customMessage");
return new SlackNotifier(teamDomain, token, room, tokenCredentialId, sendAs, startNotification, notifyAborted,
return new SlackNotifier(teamDomain, token, botUser, room, tokenCredentialId, sendAs, startNotification, notifyAborted,
notifyFailure, notifyNotBuilt, notifySuccess, notifyUnstable, notifyBackToNormal, notifyRepeatedFailure,
includeTestSummary, commitInfoChoice, includeCustomMessage, customMessage);
}
Expand All @@ -360,14 +374,15 @@ public boolean configure(StaplerRequest sr, JSONObject formData) throws FormExce
teamDomain = sr.getParameter("slackTeamDomain");
token = sr.getParameter("slackToken");
tokenCredentialId = formData.getJSONObject("slack").getString("tokenCredentialId");
botUser = "true".equals(sr.getParameter("slackBotUser"));
room = sr.getParameter("slackRoom");
sendAs = sr.getParameter("slackSendAs");
save();
return super.configure(sr, formData);
}

SlackService getSlackService(final String teamDomain, final String authToken, final String authTokenCredentialId, final String room) {
return new StandardSlackService(teamDomain, authToken, authTokenCredentialId, room);
SlackService getSlackService(final String teamDomain, final String authToken, final String authTokenCredentialId, final boolean botUser, final String room) {
return new StandardSlackService(teamDomain, authToken, authTokenCredentialId, botUser, room);
}

@Override
Expand All @@ -378,15 +393,18 @@ public String getDisplayName() {
public FormValidation doTestConnection(@QueryParameter("slackTeamDomain") final String teamDomain,
@QueryParameter("slackToken") final String authToken,
@QueryParameter("tokenCredentialId") final String authTokenCredentialId,
@QueryParameter("slackBotUser") final boolean botUser,
@QueryParameter("slackRoom") final String room) throws FormException {
try {
String targetDomain = teamDomain;
if (StringUtils.isEmpty(targetDomain)) {
targetDomain = this.teamDomain;
}
String targetToken = authToken;
boolean targetBotUser = botUser;
if (StringUtils.isEmpty(targetToken)) {
targetToken = this.token;
targetBotUser = this.botUser;
}
String targetTokenCredentialId = authTokenCredentialId;
if (StringUtils.isEmpty(targetTokenCredentialId)) {
Expand All @@ -396,7 +414,7 @@ public FormValidation doTestConnection(@QueryParameter("slackTeamDomain") final
if (StringUtils.isEmpty(targetRoom)) {
targetRoom = this.room;
}
SlackService testSlackService = getSlackService(targetDomain, targetToken, targetTokenCredentialId, targetRoom);
SlackService testSlackService = getSlackService(targetDomain, targetToken, targetTokenCredentialId, targetBotUser, targetRoom);
String message = "Slack/Jenkins plugin: you're all set on " + DisplayURLProvider.get().getRoot();
boolean success = testSlackService.publish(message, "good");
return success ? FormValidation.ok("Success") : FormValidation.error("Failure");
Expand All @@ -411,6 +429,7 @@ public static class SlackJobProperty extends hudson.model.JobProperty<AbstractPr

private String teamDomain;
private String token;
private boolean botUser;
private String room;
private boolean startNotification;
private boolean notifySuccess;
Expand All @@ -428,6 +447,7 @@ public static class SlackJobProperty extends hudson.model.JobProperty<AbstractPr
@DataBoundConstructor
public SlackJobProperty(String teamDomain,
String token,
boolean botUser,
String room,
boolean startNotification,
boolean notifyAborted,
Expand All @@ -443,6 +463,7 @@ public SlackJobProperty(String teamDomain,
String customMessage) {
this.teamDomain = teamDomain;
this.token = token;
this.botUser = botUser;
this.room = room;
this.startNotification = startNotification;
this.notifyAborted = notifyAborted;
Expand All @@ -468,6 +489,11 @@ public String getToken() {
return token;
}

@Exported
public boolean getBotUser() {
return botUser;
}

@Exported
public String getRoom() {
return room;
Expand Down
92 changes: 57 additions & 35 deletions src/main/java/jenkins/plugins/slack/StandardSlackService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;

import org.jenkinsci.plugins.plaincredentials.StringCredentials;
Expand All @@ -13,9 +15,11 @@
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;

import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
Expand All @@ -24,8 +28,6 @@
import jenkins.model.Jenkins;
import hudson.ProxyConfiguration;

import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.lang.StringUtils;

public class StandardSlackService implements SlackService {
Expand All @@ -36,13 +38,15 @@ public class StandardSlackService implements SlackService {
private String teamDomain;
private String token;
private String authTokenCredentialId;
private boolean botUser;
private String[] roomIds;

public StandardSlackService(String teamDomain, String token, String authTokenCredentialId, String roomId) {
public StandardSlackService(String teamDomain, String token, String authTokenCredentialId, boolean botUser, String roomId) {
super();
this.teamDomain = teamDomain;
this.token = token;
this.authTokenCredentialId = StringUtils.trim(authTokenCredentialId);
this.botUser = botUser;
this.roomIds = roomId.split("[,; ]+");
}

Expand All @@ -53,46 +57,64 @@ public boolean publish(String message) {
public boolean publish(String message, String color) {
boolean result = true;
for (String roomId : roomIds) {
String url = "https://" + teamDomain + "." + host + "/services/hooks/jenkins-ci?token=" + getTokenToUse();
logger.fine("Posting: to " + roomId + " on " + teamDomain + " using " + url +": " + message + " " + color);
HttpClient client = getHttpClient();
PostMethod post = new PostMethod(url);
JSONObject json = new JSONObject();

try {
JSONObject field = new JSONObject();
field.put("short", false);
field.put("value", message);

JSONArray fields = new JSONArray();
fields.put(field);

JSONObject attachment = new JSONObject();
attachment.put("fallback", message);
attachment.put("color", color);
attachment.put("fields", fields);
JSONArray mrkdwn = new JSONArray();
mrkdwn.put("pretext");
mrkdwn.put("text");
mrkdwn.put("fields");
attachment.put("mrkdwn_in", mrkdwn);
JSONArray attachments = new JSONArray();
attachments.put(attachment);
//prepare attachments first
JSONObject field = new JSONObject();
field.put("short", false);
field.put("value", message);

JSONArray fields = new JSONArray();
fields.put(field);

JSONObject attachment = new JSONObject();
attachment.put("fallback", message);
attachment.put("color", color);
attachment.put("fields", fields);
JSONArray mrkdwn = new JSONArray();
mrkdwn.put("pretext");
mrkdwn.put("text");
mrkdwn.put("fields");
attachment.put("mrkdwn_in", mrkdwn);
JSONArray attachments = new JSONArray();
attachments.put(attachment);

PostMethod post;
String url;
//prepare post methods for both requests types
if (!botUser) {
url = "https://" + teamDomain + "." + host + "/services/hooks/jenkins-ci?token=" + getTokenToUse();
post = new PostMethod(url);
JSONObject json = new JSONObject();

json.put("channel", roomId);
json.put("attachments", attachments);
json.put("link_names", "1");

post.addParameter("payload", json.toString());
post.getParams().setContentCharset("UTF-8");

} else {
url = "https://slack.com/api/chat.postMessage?token=" + getTokenToUse() +
"&channel=" + roomId +
"&link_names=1" +
"&as_user=true";
try {
url += "&attachments=" + URLEncoder.encode(attachments.toString(), "utf-8");
} catch (UnsupportedEncodingException e) {
logger.log(Level.ALL, "Error while encoding attachments: " + e.getMessage());
}
post = new PostMethod(url);
}
logger.fine("Posting: to " + roomId + " on " + teamDomain + " using " + url + ": " + message + " " + color);
HttpClient client = getHttpClient();
post.getParams().setContentCharset("UTF-8");

try {
int responseCode = client.executeMethod(post);
String response = post.getResponseBodyAsString();
if(responseCode != HttpStatus.SC_OK) {
if (responseCode != HttpStatus.SC_OK) {
logger.log(Level.WARNING, "Slack post may have failed. Response: " + response);
result = false;
}
else {
logger.fine("Posting succeeded");
} else {
logger.info("Posting succeeded");
}
} catch (Exception e) {
logger.log(Level.WARNING, "Error posting to Slack", e);
Expand Down Expand Up @@ -139,7 +161,7 @@ protected HttpClient getHttpClient() {
// and
// http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/examples/BasicAuthenticationExample.java?view=markup
client.getState().setProxyCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
new UsernamePasswordCredentials(username, password));
}
}
}
Expand Down
Loading