From 76bc5f57e1f9bb58d0c5931687c4bbea1b30fdc5 Mon Sep 17 00:00:00 2001 From: Luke Last Date: Mon, 11 Oct 2021 12:09:02 -0400 Subject: [PATCH 1/7] Move PipelineStatus verb method to the enum so it can be shared. --- .../gocd/slack/PipelineListener.java | 2 +- .../gocd/slack/SlackPipelineListener.java | 20 ++----------------- .../gocd/slack/ruleset/PipelineStatus.java | 16 +++++++++++++++ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/in/ashwanthkumar/gocd/slack/PipelineListener.java b/src/main/java/in/ashwanthkumar/gocd/slack/PipelineListener.java index 156b6d5..32b5eb7 100644 --- a/src/main/java/in/ashwanthkumar/gocd/slack/PipelineListener.java +++ b/src/main/java/in/ashwanthkumar/gocd/slack/PipelineListener.java @@ -9,7 +9,7 @@ import java.util.List; abstract public class PipelineListener { - private Logger LOG = Logger.getLoggerFor(PipelineListener.class); + private static final Logger LOG = Logger.getLoggerFor(PipelineListener.class); protected Rules rules; public PipelineListener(Rules rules) { diff --git a/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java b/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java index 7b3f7a2..f66f88d 100644 --- a/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java +++ b/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java @@ -23,7 +23,7 @@ public class SlackPipelineListener extends PipelineListener { public static final int DEFAULT_MAX_CHANGES_PER_MATERIAL_IN_SLACK = 5; - private Logger LOG = Logger.getLoggerFor(SlackPipelineListener.class); + private static final Logger LOG = Logger.getLoggerFor(SlackPipelineListener.class); private final Slack slack; @@ -79,7 +79,7 @@ public void onCancelled(PipelineRule rule, GoNotificationMessage message) throws } private SlackAttachment slackAttachment(PipelineRule rule, GoNotificationMessage message, PipelineStatus pipelineStatus) throws URISyntaxException { - String title = String.format("Stage [%s] %s %s", message.fullyQualifiedJobName(), verbFor(pipelineStatus), pipelineStatus).replaceAll("\\s+", " "); + String title = String.format("Stage [%s] %s %s", message.fullyQualifiedJobName(), pipelineStatus.verb(), pipelineStatus).replaceAll("\\s+", " "); SlackAttachment buildAttachment = new SlackAttachment("") .fallback(title) .title(title, message.goServerUrl(rules.getGoServerHost())); @@ -190,22 +190,6 @@ private Stage pickCurrentStage(Stage[] stages, GoNotificationMessage message) { throw new IllegalArgumentException("The list of stages from the pipeline (" + message.getPipelineName() + ") doesn't have the active stage (" + message.getStageName() + ") for which we got the notification."); } - private String verbFor(PipelineStatus pipelineStatus) { - switch (pipelineStatus) { - case BROKEN: - case FIXED: - case BUILDING: - return "is"; - case FAILED: - case PASSED: - return "has"; - case CANCELLED: - return "was"; - default: - return ""; - } - } - private void updateSlackChannel(String slackChannel) { LOG.debug(String.format("Updating target slack channel to %s", slackChannel)); // by default post it to where ever the hook is configured to do so diff --git a/src/main/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineStatus.java b/src/main/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineStatus.java index e1f7143..f1ceb2e 100644 --- a/src/main/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineStatus.java +++ b/src/main/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineStatus.java @@ -81,6 +81,22 @@ public void handle(PipelineListener listener, PipelineRule rule, GoNotificationM } }; + public String verb() { + switch (this) { + case BROKEN: + case FIXED: + case BUILDING: + return "is"; + case FAILED: + case PASSED: + return "has"; + case CANCELLED: + return "was"; + default: + return ""; + } + } + public boolean matches(String state) { return this == ALL || this == PipelineStatus.valueOf(state.toUpperCase()); } From 0df37af8c0119151b1efc601eea277b7639bff10 Mon Sep 17 00:00:00 2001 From: Luke Last Date: Mon, 11 Oct 2021 20:36:31 -0400 Subject: [PATCH 2/7] Move pickCurrentStage so it's shareable. --- .../gocd/slack/GoNotificationMessage.java | 14 ++++++++++++++ .../gocd/slack/SlackPipelineListener.java | 12 +----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/in/ashwanthkumar/gocd/slack/GoNotificationMessage.java b/src/main/java/in/ashwanthkumar/gocd/slack/GoNotificationMessage.java index e716aab..28992aa 100644 --- a/src/main/java/in/ashwanthkumar/gocd/slack/GoNotificationMessage.java +++ b/src/main/java/in/ashwanthkumar/gocd/slack/GoNotificationMessage.java @@ -221,4 +221,18 @@ public List fetchChanges(Rules rules) server.getPipelineInstance(pipeline.name, Integer.parseInt(pipeline.counter)); return pipelineInstance.rootChanges(server); } + + public Stage pickCurrentStage(Stage[] stages) { + for (Stage stage : stages) { + if (getStageName().equals(stage.name)) { + return stage; + } + } + throw new IllegalArgumentException("The list of stages from the pipeline (" + + getPipelineName() + + ") doesn't have the active stage (" + + getStageName() + + ") for which we got the notification."); + } + } diff --git a/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java b/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java index f66f88d..d6766fb 100644 --- a/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java +++ b/src/main/java/in/ashwanthkumar/gocd/slack/SlackPipelineListener.java @@ -88,7 +88,7 @@ private SlackAttachment slackAttachment(PipelineRule rule, GoNotificationMessage // Describe the build. try { Pipeline details = message.fetchDetails(rules); - Stage stage = pickCurrentStage(details.stages, message); + Stage stage = message.pickCurrentStage(details.stages); buildAttachment.addField(new SlackAttachment.Field("Triggered by", stage.approvedBy, true)); if (details.buildCause.triggerForced) { buildAttachment.addField(new SlackAttachment.Field("Reason", "Manual Trigger", true)); @@ -180,16 +180,6 @@ private List createConsoleLogLinks(String host, Pipeline pipeline, Stage return consoleLinks; } - private Stage pickCurrentStage(Stage[] stages, GoNotificationMessage message) { - for (Stage stage : stages) { - if (message.getStageName().equals(stage.name)) { - return stage; - } - } - - throw new IllegalArgumentException("The list of stages from the pipeline (" + message.getPipelineName() + ") doesn't have the active stage (" + message.getStageName() + ") for which we got the notification."); - } - private void updateSlackChannel(String slackChannel) { LOG.debug(String.format("Updating target slack channel to %s", slackChannel)); // by default post it to where ever the hook is configured to do so From 00cb2b93bd88c0b42e14b434d53f9b3279930aca Mon Sep 17 00:00:00 2001 From: Luke Last Date: Thu, 28 Oct 2021 19:19:06 -0400 Subject: [PATCH 3/7] Add support for Microsoft Teams using a webhook. --- .../gocd/teams/CardHttpContent.java | 27 ++++++ .../gocd/teams/MessageCardSchema.java | 89 ++++++++++++++++++ .../ashwanthkumar/gocd/teams/TeamsCard.java | 36 ++++++++ .../gocd/teams/TeamsPipelineListener.java | 90 +++++++++++++++++++ .../gocd/teams/TeamsWebhook.java | 38 ++++++++ .../gocd/slack/ruleset/PipelineRuleTest.java | 1 - .../ashwanthkumar/gocd/teams/TeamsTest.java | 43 +++++++++ 7 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/main/java/in/ashwanthkumar/gocd/teams/CardHttpContent.java create mode 100644 src/main/java/in/ashwanthkumar/gocd/teams/MessageCardSchema.java create mode 100644 src/main/java/in/ashwanthkumar/gocd/teams/TeamsCard.java create mode 100644 src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java create mode 100644 src/main/java/in/ashwanthkumar/gocd/teams/TeamsWebhook.java create mode 100644 src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java diff --git a/src/main/java/in/ashwanthkumar/gocd/teams/CardHttpContent.java b/src/main/java/in/ashwanthkumar/gocd/teams/CardHttpContent.java new file mode 100644 index 0000000..d0ba930 --- /dev/null +++ b/src/main/java/in/ashwanthkumar/gocd/teams/CardHttpContent.java @@ -0,0 +1,27 @@ +package in.ashwanthkumar.gocd.teams; + +import com.google.api.client.http.AbstractHttpContent; +import com.google.api.client.json.Json; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +/** + * Serialize a {@link TeamsCard} for the Google HTTP Client. + */ +public class CardHttpContent extends AbstractHttpContent { + private final TeamsCard card; + + protected CardHttpContent(TeamsCard card) { + super(Json.MEDIA_TYPE); + this.card = card; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + try (var osw = new OutputStreamWriter(out)) { + osw.write(card.toString()); + } + } +} diff --git a/src/main/java/in/ashwanthkumar/gocd/teams/MessageCardSchema.java b/src/main/java/in/ashwanthkumar/gocd/teams/MessageCardSchema.java new file mode 100644 index 0000000..dcd4116 --- /dev/null +++ b/src/main/java/in/ashwanthkumar/gocd/teams/MessageCardSchema.java @@ -0,0 +1,89 @@ +package in.ashwanthkumar.gocd.teams; + +import com.google.gson.annotations.SerializedName; +import in.ashwanthkumar.gocd.slack.ruleset.PipelineStatus; + +import java.util.ArrayList; +import java.util.List; + +/** + * These objects create the MessageCard JSON sent to Teams using {@link com.google.gson.Gson}. + * More details: + * https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference + */ +public class MessageCardSchema { + @SerializedName("@type") + String type = "MessageCard"; + String themeColor = Color.NONE.getHexCode(); + String title = ""; + /** + * Not sure what this does, but a summary or text field is required. + */ + String summary = "GoCD build update"; + List sections = new ArrayList<>(); + List potentialAction = new ArrayList<>(); + + public enum Color { + NONE(""), + RED("990000"), + GREEN("009900"); + + private final String hexCode; + + Color(String hexCode) { + this.hexCode = hexCode; + } + + public static Color findColor(PipelineStatus status) { + switch (status) { + case PASSED: + case FIXED: + return Color.GREEN; + case FAILED: + case BROKEN: + return Color.RED; + default: + return Color.NONE; + } + } + + public String getHexCode() { + return this.hexCode; + } + } + + public static class Fact { + String name = ""; + String value = ""; + + public Fact(String name, String value) { + this.name = name; + this.value = value; + } + } + + public static class FactSection { + List facts = new ArrayList<>(); + } + + public static class OpenUriAction { + @SerializedName("@type") + String type = "OpenUri"; + String name = ""; + List targets = new ArrayList<>(); + + public OpenUriAction(String name, String uri) { + this.name = name; + this.targets.add(new MessageCardSchema.Target(uri)); + } + } + + public static class Target { + String os = "default"; + String uri = ""; + + public Target(String uri) { + this.uri = uri; + } + } +} diff --git a/src/main/java/in/ashwanthkumar/gocd/teams/TeamsCard.java b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsCard.java new file mode 100644 index 0000000..1958276 --- /dev/null +++ b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsCard.java @@ -0,0 +1,36 @@ +package in.ashwanthkumar.gocd.teams; + +import com.google.gson.Gson; + +/** + * Populate the values of a Message Card for Teams. + */ +public class TeamsCard { + private final MessageCardSchema.FactSection factSection = new MessageCardSchema.FactSection(); + private final MessageCardSchema schema = new MessageCardSchema(); + + public TeamsCard() { + this.schema.sections.add(this.factSection); + } + + public void setTitle(String title) { + this.schema.title = title; + } + + public void addFact(String name, String value) { + this.factSection.facts.add(new MessageCardSchema.Fact(name, value)); + } + + @Override + public String toString() { + return new Gson().toJson(schema); + } + + public void setColor(MessageCardSchema.Color color) { + this.schema.themeColor = color.getHexCode(); + } + + public void addLinkAction(String name, String uri) { + this.schema.potentialAction.add(new MessageCardSchema.OpenUriAction(name, uri)); + } +} diff --git a/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java new file mode 100644 index 0000000..d84614e --- /dev/null +++ b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java @@ -0,0 +1,90 @@ +package in.ashwanthkumar.gocd.teams; + +import com.thoughtworks.go.plugin.api.logging.Logger; +import in.ashwanthkumar.gocd.slack.GoNotificationMessage; +import in.ashwanthkumar.gocd.slack.PipelineListener; +import in.ashwanthkumar.gocd.slack.jsonapi.Pipeline; +import in.ashwanthkumar.gocd.slack.jsonapi.Stage; +import in.ashwanthkumar.gocd.slack.ruleset.PipelineRule; +import in.ashwanthkumar.gocd.slack.ruleset.PipelineStatus; +import in.ashwanthkumar.gocd.slack.ruleset.Rules; + +import java.io.IOException; +import java.net.URISyntaxException; + +/** + * To enable this for Teams support add the following line to your config: + * listener = "in.ashwanthkumar.gocd.teams.TeamsPipelineListener" + * + * @see in.ashwanthkumar.gocd.slack.SlackPipelineListener + */ +public class TeamsPipelineListener extends PipelineListener { + private static final Logger LOG = Logger.getLoggerFor(TeamsPipelineListener.class); + private final TeamsWebhook teams; + + public TeamsPipelineListener(Rules rules) { + super(rules); + teams = new TeamsWebhook(rules.getProxy()); + } + + private String getWebhook(PipelineRule rule) { + final String ruleWebhook = rule.getWebhookUrl(); + if (ruleWebhook != null && !ruleWebhook.isEmpty()) { + return ruleWebhook; + } else { + return this.rules.getWebHookUrl(); + } + } + + private void sendMessage(PipelineRule rule, GoNotificationMessage message, PipelineStatus status) + throws GoNotificationMessage.BuildDetailsNotFoundException, URISyntaxException, IOException { + final TeamsCard card = new TeamsCard(); + card.setColor(MessageCardSchema.Color.findColor(status)); + card.addLinkAction("Details", message.goServerUrl(rules.getGoServerHost())); + card.setTitle(String.format("Stage [%s] %s %s", + message.fullyQualifiedJobName(), + status.verb(), + status) + .replaceAll("\\s+", " ")); + + Pipeline details = message.fetchDetails(rules); + Stage stage = message.pickCurrentStage(details.stages); + + card.addFact("Triggered by", stage.approvedBy); + card.addFact("Reason", details.buildCause.triggerMessage); + card.addFact("Label", details.label); + + teams.send(getWebhook(rule), card); + } + + + @Override + public void onBuilding(PipelineRule rule, GoNotificationMessage message) throws Exception { + sendMessage(rule, message, PipelineStatus.BUILDING); + } + + @Override + public void onPassed(PipelineRule rule, GoNotificationMessage message) throws Exception { + sendMessage(rule, message, PipelineStatus.PASSED); + } + + @Override + public void onFailed(PipelineRule rule, GoNotificationMessage message) throws Exception { + sendMessage(rule, message, PipelineStatus.FAILED); + } + + @Override + public void onBroken(PipelineRule rule, GoNotificationMessage message) throws Exception { + sendMessage(rule, message, PipelineStatus.BROKEN); + } + + @Override + public void onFixed(PipelineRule rule, GoNotificationMessage message) throws Exception { + sendMessage(rule, message, PipelineStatus.FIXED); + } + + @Override + public void onCancelled(PipelineRule rule, GoNotificationMessage message) throws Exception { + sendMessage(rule, message, PipelineStatus.CANCELLED); + } +} diff --git a/src/main/java/in/ashwanthkumar/gocd/teams/TeamsWebhook.java b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsWebhook.java new file mode 100644 index 0000000..b42bc37 --- /dev/null +++ b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsWebhook.java @@ -0,0 +1,38 @@ +package in.ashwanthkumar.gocd.teams; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.thoughtworks.go.plugin.api.logging.Logger; + +import java.io.IOException; +import java.net.Proxy; +import java.util.Objects; + +/** + * Sends post requests to a Teams channels incoming webhook. + * Uses Google HTTP Client. + */ +public class TeamsWebhook { + private static final Logger LOG = Logger.getLoggerFor(TeamsWebhook.class); + private final HttpRequestFactory requestFactory; + + public TeamsWebhook(Proxy proxy) { + requestFactory = new NetHttpTransport.Builder() + .setProxy(proxy) + .build() + .createRequestFactory(); + } + + public void send(String webhookUrl, TeamsCard card) throws IOException { + Objects.requireNonNull(webhookUrl); + Objects.requireNonNull(card); + LOG.debug("Using webhook: " + webhookUrl); + LOG.debug("Sending Card: " + card); + requestFactory.buildPostRequest( + new GenericUrl(webhookUrl), + new CardHttpContent(card) + ) + .execute(); + } +} diff --git a/src/test/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineRuleTest.java b/src/test/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineRuleTest.java index f7cf56a..29d9c42 100644 --- a/src/test/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineRuleTest.java +++ b/src/test/java/in/ashwanthkumar/gocd/slack/ruleset/PipelineRuleTest.java @@ -2,7 +2,6 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import in.ashwanthkumar.utils.collections.Lists; import in.ashwanthkumar.utils.collections.Sets; import org.junit.Test; diff --git a/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java b/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java new file mode 100644 index 0000000..52a5e47 --- /dev/null +++ b/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java @@ -0,0 +1,43 @@ +package in.ashwanthkumar.gocd.teams; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class TeamsTest { + + private TeamsCard buildCard() { + TeamsCard card = new TeamsCard(); + card.setTitle("title"); + card.setColor(MessageCardSchema.Color.GREEN); + card.addFact("k", "v"); + card.addLinkAction("name", "uri"); + return card; + } + + @Test + public void testCardHttpContent() throws IOException { + TeamsCard card = buildCard(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CardHttpContent(card).writeTo(baos); + final String result = baos.toString(); + + Assert.assertEquals(card.toString(), result); + } + + @Test + public void testCardToString() { + TeamsCard card = buildCard(); + String result = card.toString(); + + String expected = ("{'@type':'MessageCard','themeColor':'009900','title':'title'," + + "'summary':'GoCD build update','sections':[{'facts':[{'name':'k'," + + "'value':'v'}]}],'potentialAction':[{'@type':'OpenUri','name':'name'," + + "'targets':[{'os':'default','uri':'uri'}]}]}") + .replace('\'', '"'); + + Assert.assertEquals(expected, result); + } +} From 2c7faa5818cceba359583a7ad124e2c104a70472 Mon Sep 17 00:00:00 2001 From: Luke Last Date: Thu, 28 Oct 2021 19:19:21 -0400 Subject: [PATCH 4/7] Bump version to 2.1.0-beta --- pom.xml | 2 +- src/main/resources/plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4aa0ff5..1dc6ab1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ in.ashwanthkumar gocd-slack-notifier - 2.0.2 + 2.1.0-beta jar diff --git a/src/main/resources/plugin.xml b/src/main/resources/plugin.xml index 66609a9..34a1dec 100644 --- a/src/main/resources/plugin.xml +++ b/src/main/resources/plugin.xml @@ -2,7 +2,7 @@ Slack Notification Plugin - 2.0.2 + 2.1.0-beta 20.1.0 Plugin to send build notifications to slack From c9bb4d850c0a19567f8cfee34e981b2c1475ca0d Mon Sep 17 00:00:00 2001 From: Luke Last Date: Wed, 3 Nov 2021 16:26:21 -0400 Subject: [PATCH 5/7] Catch any errors fetching the message details, log it and keep going. --- .../gocd/teams/TeamsPipelineListener.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java index d84614e..16fb0ec 100644 --- a/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java +++ b/src/main/java/in/ashwanthkumar/gocd/teams/TeamsPipelineListener.java @@ -37,7 +37,7 @@ private String getWebhook(PipelineRule rule) { } private void sendMessage(PipelineRule rule, GoNotificationMessage message, PipelineStatus status) - throws GoNotificationMessage.BuildDetailsNotFoundException, URISyntaxException, IOException { + throws URISyntaxException, IOException { final TeamsCard card = new TeamsCard(); card.setColor(MessageCardSchema.Color.findColor(status)); card.addLinkAction("Details", message.goServerUrl(rules.getGoServerHost())); @@ -47,12 +47,16 @@ private void sendMessage(PipelineRule rule, GoNotificationMessage message, Pipel status) .replaceAll("\\s+", " ")); - Pipeline details = message.fetchDetails(rules); - Stage stage = message.pickCurrentStage(details.stages); - - card.addFact("Triggered by", stage.approvedBy); - card.addFact("Reason", details.buildCause.triggerMessage); - card.addFact("Label", details.label); + try { + Pipeline details = message.fetchDetails(rules); + Stage stage = message.pickCurrentStage(details.stages); + card.addFact("Triggered by", stage.approvedBy); + card.addFact("Reason", details.buildCause.triggerMessage); + card.addFact("Label", details.label); + } catch (Exception ex) { + card.addFact("Internal Error", "Problem with build details; see server log"); + LOG.warn("Problem with build details", ex); + } teams.send(getWebhook(rule), card); } From feff2004185b56233a4d5aa850351ee722780a84 Mon Sep 17 00:00:00 2001 From: Luke Last Date: Tue, 23 Nov 2021 13:29:58 -0500 Subject: [PATCH 6/7] Add a unit test for the Teams listener. --- .../in/ashwanthkumar/gocd/teams/TeamsTest.java | 16 ++++++++++++++++ .../resources/configs/test-config-teams.conf | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/test/resources/configs/test-config-teams.conf diff --git a/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java b/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java index 52a5e47..b72f532 100644 --- a/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java +++ b/src/test/java/in/ashwanthkumar/gocd/teams/TeamsTest.java @@ -1,5 +1,8 @@ package in.ashwanthkumar.gocd.teams; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import in.ashwanthkumar.gocd.slack.ruleset.Rules; import org.junit.Assert; import org.junit.Test; @@ -40,4 +43,17 @@ public void testCardToString() { Assert.assertEquals(expected, result); } + + @Test + public void testTeamsListener() { + Config config = ConfigFactory.parseResources("configs/test-config-teams.conf") + .withFallback(ConfigFactory.load(getClass().getClassLoader())) + .getConfig("gocd.slack"); + Rules rules = Rules.fromConfig(config); + + Assert.assertEquals(TeamsPipelineListener.class, rules.getPipelineListener().getClass()); + Assert.assertEquals("https://example.com/default", rules.getWebHookUrl()); + Assert.assertEquals("https://example.com/pipeline-override", + rules.getPipelineRules().get(0).getWebhookUrl()); + } } diff --git a/src/test/resources/configs/test-config-teams.conf b/src/test/resources/configs/test-config-teams.conf new file mode 100644 index 0000000..261b0f3 --- /dev/null +++ b/src/test/resources/configs/test-config-teams.conf @@ -0,0 +1,17 @@ +gocd.slack { + login = "foo" + password = "foo-bar" + server-host = "https://go-instance:8153/" + + # Teams specific configuration + listener = "in.ashwanthkumar.gocd.teams.TeamsPipelineListener" + webhookUrl = "https://example.com/default" + + pipelines = [{ + name = ".*" + stage = ".*" + state = "failed" + # Optionally send these messages to another channel using a different webhook. + webhookUrl = "https://example.com/pipeline-override" + }] +} From 584ede85b18fb7ea8991f62d6cd6d439723fe6f5 Mon Sep 17 00:00:00 2001 From: Luke Last Date: Tue, 23 Nov 2021 13:32:31 -0500 Subject: [PATCH 7/7] Update readme to mention MS Teams support. --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 529d201..04dadab 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,33 @@ gocd.slack { - `proxy.port` - Proxy Port - `proxy.type` - `socks` or `http` are the only accepted values. +### Teams Configuration + +To send notifications to Microsoft Teams instead of Slack you need to configure the `listener` setting as shown in the example below. +The other difference is that the channel setting is not used, +instead with Teams you create an incoming webhook for each channel you want to send messages to. + +```hocon +gocd.slack { + # Tell the plugin you are using Microsoft Teams instead of Slack. + listener = "in.ashwanthkumar.gocd.teams.TeamsPipelineListener" + + # Determines the Team and Channel to send notifications to unless overridden by a pipeline rule. + webhookUrl = "https://xxx.webhook.office.com/webhookb2/xxx/IncomingWebhook/xxx/xxx" + + # The channel setting is not used, only the webhookUrl. + + pipelines = [{ + # The channel setting is ignored. + + # Optionally override the default webhook to send notifications to a different channel. + webhookUrl = "https://example.com" + + # The rest of the configuration functions the same. + }, +} +``` + ## Pipeline Rules By default the plugin pushes a note about all failed stages across all pipelines to Slack. You have fine grain control over this operation. ```hocon