diff --git a/docs/overview/changelog.md b/docs/overview/changelog.md
index b9526ba3a6..4bb673032e 100644
--- a/docs/overview/changelog.md
+++ b/docs/overview/changelog.md
@@ -30,7 +30,7 @@ in bursts.
**Full feature changelog:**
-- New integrations: ErrBot, Google Code-In, Opbeat, Groove, Raygun,
+- New integrations: ErrBot, GoCD, Google Code-In, Opbeat, Groove, Raygun,
Insping, Dropbox, Front, Intercom, Statuspage.io, Flock and Beeminder.
- The local uploads backend now does the same security checks that the
S3 backend did before serving files to users.
diff --git a/static/images/integrations/gocd/001.png b/static/images/integrations/gocd/001.png
new file mode 100644
index 0000000000..cb6f9d073d
Binary files /dev/null and b/static/images/integrations/gocd/001.png differ
diff --git a/static/images/integrations/logos/gocd.png b/static/images/integrations/logos/gocd.png
new file mode 100644
index 0000000000..8983baacf5
Binary files /dev/null and b/static/images/integrations/logos/gocd.png differ
diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py
index feed9a3ff7..00fd5176ff 100644
--- a/zerver/lib/integrations.py
+++ b/zerver/lib/integrations.py
@@ -315,6 +315,7 @@ WEBHOOK_INTEGRATIONS = [
stream_name='github'
),
WebhookIntegration('gitlab', ['version-control'], display_name='GitLab'),
+ WebhookIntegration('gocd', ['continuous-integration'], display_name='GoCD'),
WebhookIntegration('gogs', ['version-control'], stream_name='commits'),
WebhookIntegration('gosquared', ['marketing'], display_name='GoSquared'),
WebhookIntegration('greenhouse', ['hr'], display_name='Greenhouse'),
diff --git a/zerver/webhooks/gocd/__init__.py b/zerver/webhooks/gocd/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/zerver/webhooks/gocd/doc.md b/zerver/webhooks/gocd/doc.md
new file mode 100644
index 0000000000..97eba14bc3
--- /dev/null
+++ b/zerver/webhooks/gocd/doc.md
@@ -0,0 +1,22 @@
+Zulip supports integration with GoCD and can notify you of
+your build statuses.
+
+1. {!create-stream.md!}
+
+1. {!create-bot-construct-url-indented.md!}
+
+1. Add the following to your `Config.XML` file.
+
+ ```
+
+
+ ...
+
+ ```
+
+ Push this change to your repository. For further information,
+ see [GoCD's documentation](https://docs.gocd.org/current/integration/).
+
+{!congrats.md!}
+
+
diff --git a/zerver/webhooks/gocd/fixtures/build_details.json b/zerver/webhooks/gocd/fixtures/build_details.json
new file mode 100644
index 0000000000..b178832a96
--- /dev/null
+++ b/zerver/webhooks/gocd/fixtures/build_details.json
@@ -0,0 +1,18 @@
+{
+ "build_details": {
+ "_links": {
+ "job": {
+ "href": "https://ci.example.com/go/tab/build/detail/pipelineName/pipelineCounter/stageName/stageCounter/jobName"
+ },
+ "stage": {
+ "href": "https://ci.example.com/go/pipelines/pipelineName/pipelineCounter/stageName/stageCounter"
+ },
+ "pipeline": {
+ "href": "https://ci.example.com/go/tab/pipeline/history/pipelineName"
+ }
+ },
+ "pipeline_name": "pipelineName",
+ "stage_name": "stageName",
+ "job_name": "jobName"
+ }
+}
diff --git a/zerver/webhooks/gocd/fixtures/pipeline.json b/zerver/webhooks/gocd/fixtures/pipeline.json
new file mode 100644
index 0000000000..9a5969aa0f
--- /dev/null
+++ b/zerver/webhooks/gocd/fixtures/pipeline.json
@@ -0,0 +1,59 @@
+{
+ "build_cause": {
+ "approver": "",
+ "material_revisions": [
+ {
+ "modifications": [
+ {
+ "email_address": null,
+ "id": 7225,
+ "modified_time": 1435728005000,
+ "user_name": "Balaji B ",
+ "comment": "my hola mundo changes",
+ "revision": "a788f1876e2e1f6e5a1e91006e75cd1d467a0edb"
+ }
+ ],
+ "material": {
+ "description": "URL: https://github.com/gocd/gocd, Branch: master",
+ "fingerprint": "61e2da369d0207a7ef61f326eed837f964471b35072340a03f8f55d993afe01d",
+ "type": "Git",
+ "id": 4
+ },
+ "changed": true
+ }
+ ],
+ "trigger_forced": false,
+ "trigger_message": "modified by Balaji "
+ },
+ "name": "PipelineName",
+ "natural_order": 1,
+ "can_run": false,
+ "comment": null,
+ "stages": [
+ {
+ "name": "stage1",
+ "approved_by": "changes",
+ "jobs": [
+ {
+ "name": "jsunit",
+ "result": "Passed",
+ "state": "Completed",
+ "id": 1,
+ "scheduled_date": 1398332981981
+ }
+ ],
+ "can_run": false,
+ "result": "Passed",
+ "approval_type": "success",
+ "counter": "1",
+ "id": 1,
+ "operate_permission": false,
+ "rerun_of_counter": null,
+ "scheduled": true
+ }
+ ],
+ "counter": 1,
+ "id": 1,
+ "preparing_to_schedule": false,
+ "label": "14.1.0.1-b14a81825d081411993853ea5ea45266ced578b4"
+}
diff --git a/zerver/webhooks/gocd/fixtures/pipeline_failed.json b/zerver/webhooks/gocd/fixtures/pipeline_failed.json
new file mode 100644
index 0000000000..17c33d94d6
--- /dev/null
+++ b/zerver/webhooks/gocd/fixtures/pipeline_failed.json
@@ -0,0 +1,59 @@
+{
+ "build_cause": {
+ "approver": "anonymous",
+ "material_revisions": [
+ {
+ "modifications": [
+ {
+ "email_address": null,
+ "id": 1998,
+ "modified_time": 1434957613000,
+ "user_name": "User Name ",
+ "comment": "my hola mundo changes",
+ "revision": "f6e7a3899c55e1682ffb00383bdf8f882bcee2141e79a8728254190a1fddcf4f"
+ }
+ ],
+ "material": {
+ "description": "URL: https://github.com/gocd/gocd, Branch: master",
+ "fingerprint": "61e2da369d0207a7ef61f326eed837f964471b35072340a03f8f55d993afe01d",
+ "type": "Git",
+ "id": 4
+ },
+ "changed": true
+ }
+ ],
+ "trigger_forced": false,
+ "trigger_message": "modified by User Name "
+ },
+ "name": "PipelineName",
+ "natural_order": 8,
+ "can_run": false,
+ "comment": null,
+ "stages": [
+ {
+ "name": "stage1",
+ "approved_by": "changes",
+ "jobs": [
+ {
+ "name": "job123",
+ "result": "Failed",
+ "state": "Completed",
+ "id": 21,
+ "scheduled_date": 1436172201081
+ }
+ ],
+ "can_run": false,
+ "result": "Failed",
+ "approval_type": "success",
+ "counter": "1",
+ "id": 21,
+ "operate_permission": false,
+ "rerun_of_counter": null,
+ "scheduled": true
+ }
+ ],
+ "counter": 1,
+ "id": 21,
+ "preparing_to_schedule": false,
+ "label": "6"
+}
diff --git a/zerver/webhooks/gocd/tests.py b/zerver/webhooks/gocd/tests.py
new file mode 100644
index 0000000000..f2da248295
--- /dev/null
+++ b/zerver/webhooks/gocd/tests.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+import urllib
+from typing import Text
+
+from zerver.lib.test_classes import WebhookTestCase
+from zerver.models import get_realm, get_user
+
+class GocdHookTests(WebhookTestCase):
+ STREAM_NAME = 'gocd'
+ URL_TEMPLATE = "/api/v1/external/gocd?stream={stream}&api_key={api_key}"
+ FIXTURE_DIR_NAME = 'gocd'
+ TOPIC = 'https://github.com/gocd/gocd'
+
+ def test_gocd_message(self) -> None:
+ expected_message = (u"Author: Balaji B \n"
+ u"Build status: Passed :thumbs_up:\n"
+ u"Details: [build log](https://ci.example.com"
+ u"/go/tab/pipeline/history/pipelineName)\n"
+ u"Comment: my hola mundo changes")
+
+ self.send_and_test_stream_message(
+ 'pipeline',
+ self.TOPIC,
+ expected_message,
+ content_type="application/x-www-form-urlencoded"
+ )
+
+ def test_failed_message(self) -> None:
+ expected_message = (u"Author: User Name \n"
+ u"Build status: Failed :thumbs_down:\n"
+ u"Details: [build log](https://ci.example.com"
+ u"/go/tab/pipeline/history/pipelineName)\n"
+ u"Comment: my hola mundo changes")
+
+ self.send_and_test_stream_message(
+ 'pipeline_failed',
+ self.TOPIC,
+ expected_message,
+ content_type="application/x-www-form-urlencoded"
+ )
+
+ def get_body(self, fixture_name: Text) -> Text:
+ return self.fixture_data("gocd", fixture_name, file_type="json")
diff --git a/zerver/webhooks/gocd/view.py b/zerver/webhooks/gocd/view.py
new file mode 100644
index 0000000000..dda43b1723
--- /dev/null
+++ b/zerver/webhooks/gocd/view.py
@@ -0,0 +1,58 @@
+# Webhooks for external integrations.
+
+import json
+import os
+
+from typing import Any, Dict, Iterable, Optional, Text
+
+from django.http import HttpRequest, HttpResponse
+from django.utils.translation import ugettext as _
+
+from zerver.decorator import api_key_only_webhook_view
+from zerver.lib.request import REQ, has_request_variables
+from zerver.lib.response import json_error, json_success
+from zerver.lib.webhooks.common import check_send_webhook_message
+from zerver.lib.validator import check_dict, check_string
+from zerver.models import UserProfile
+
+MESSAGE_TEMPLATE = (
+ u'Author: {}\n'
+ u'Build status: {} {}\n'
+ u'Details: [build log]({})\n'
+ u'Comment: {}'
+)
+
+@api_key_only_webhook_view('Gocd')
+@has_request_variables
+def api_gocd_webhook(request: HttpRequest, user_profile: UserProfile,
+ payload: Dict[str, Any]=REQ(argument_type='body'),
+ ) -> HttpResponse:
+
+ modifications = payload['build_cause']['material_revisions'][0]['modifications'][0]
+ result = payload['stages'][0]['result']
+ material = payload['build_cause']['material_revisions'][0]['material']
+
+ if result == "Passed":
+ emoji = ':thumbs_up:'
+ elif result == "Failed":
+ emoji = ':thumbs_down:'
+
+ build_details_file = os.path.join(os.path.dirname(__file__), 'fixtures/build_details.json')
+
+ with open(build_details_file, 'r') as f:
+ contents = json.load(f)
+ build_link = contents["build_details"]["_links"]["pipeline"]["href"]
+
+ body = MESSAGE_TEMPLATE.format(
+ modifications['user_name'],
+ result,
+ emoji,
+ build_link,
+ modifications['comment']
+ )
+ branch = material['description'].split(",")
+ topic = branch[0].split(" ")[1]
+
+ check_send_webhook_message(request, user_profile, topic, body)
+
+ return json_success()