snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,34 @@
# Zulip Ansible Tower integration
Get Ansible Tower notifications in Zulip!
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. Go to **Notifications** in your Ansible Tower or AWX Admin Portal,
and select **Add**.
1. Set **Name** to a name of your choice, such as `Zulip`. Select the
organization you'd like to be notified about, and set **Type** to
**Webhook**. Set **Target URL** to the URL generated above, and
click **Save**.
1. Go to **Organizations**, and find the organization you selected
when adding the webhook notification. Click the pencil icon to edit
the organization.
1. Select **Notifications**, and then select the events you would like
to be notified about, and click **Save**.
{end_tabs}
{!congrats.md!}
![](/static/images/integrations/ansibletower/001.png)
### Related documentation
{!webhooks-url-specification.md!}

View File

@@ -0,0 +1,11 @@
{
"status": "failed",
"name": "shell: uname -r",
"started": "2018-09-13T19:58:18.972357+00:00",
"traceback": "",
"friendly_name": "AdHoc Command",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/command/2726",
"finished": "2018-09-13T19:58:32.465169+00:00",
"id": 2726
}

View File

@@ -0,0 +1,11 @@
{
"status": "successful",
"name": "shell: uname -r",
"started": "2018-09-13T19:58:18.972357+00:00",
"traceback": "",
"friendly_name": "AdHoc Command",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/command/2726",
"finished": "2018-09-13T19:58:32.465169+00:00",
"id": 2726
}

View File

@@ -0,0 +1,11 @@
{
"status": "failed",
"name": "AWX - Inventory Update",
"started": "2018-09-13T19:49:13.628571+00:00",
"traceback": "",
"friendly_name": "Inventory Update",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/inventory/2724",
"finished": "2018-09-13T19:49:18.236014+00:00",
"id": 2724
}

View File

@@ -0,0 +1,11 @@
{
"status": "successful",
"name": "AWX - Inventory Update",
"started": "2018-09-13T19:49:13.628571+00:00",
"traceback": "",
"friendly_name": "Inventory Update",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/inventory/2724",
"finished": "2018-09-13T19:49:18.236014+00:00",
"id": 2724
}

View File

@@ -0,0 +1,29 @@
{
"id": 1,
"name": "Demo Job Template",
"url": "https://towerhost/#/jobs/playbook/1",
"created_by": "admin",
"started": "2020-01-23T09:15:45.741213+00:00",
"finished": "2020-01-23T09:15:49.729882+00:00",
"status": "successful",
"traceback": "",
"inventory": "Demo Inventory",
"project": "Demo Project",
"playbook": "hello_world.yml",
"credential": "Demo Credential",
"limit": "",
"extra_vars": "{}",
"hosts": {
"localhost": {
"failed": false,
"changed": 0,
"dark": 0,
"failures": 0,
"ok": 2,
"processed": 1,
"skipped": 0,
"rescued": 0,
"ignored": 0
}
}
}

View File

@@ -0,0 +1,28 @@
{
"status": "failed",
"credential": null,
"name": "System - Updates - Ubuntu",
"started": "2018-09-13T19:05:18.276217+00:00",
"extra_vars": "{}",
"traceback": "",
"friendly_name": "Job",
"created_by": "admin",
"project": "AWX - Ansible Playbooks",
"url": "http://awx.example.co.uk/#/jobs/playbook/2722",
"finished": "2018-09-13T19:05:29.927509+00:00",
"hosts": {
"chat.example.co.uk": {
"skipped": 0,
"ok": 0,
"changed": 0,
"dark": 1,
"failed": true,
"processed": 1,
"failures": 0
}
},
"playbook": "system-updates/run-apt-updates.yml",
"limit": "os-ubuntu",
"id": 2722,
"inventory": "Production Servers"
}

View File

@@ -0,0 +1,64 @@
{
"status": "failed",
"credential": null,
"name": "System - Updates - Ubuntu",
"started": "2018-09-13T19:05:18.276217+00:00",
"extra_vars": "{}",
"traceback": "",
"friendly_name": "Job",
"created_by": "admin",
"project": "AWX - Ansible Playbooks",
"url": "http://awx.example.co.uk/#/jobs/playbook/2722",
"finished": "2018-09-13T19:05:29.927509+00:00",
"hosts": {
"chat.example.co.uk": {
"skipped": 0,
"ok": 0,
"changed": 0,
"dark": 1,
"failed": true,
"processed": 1,
"failures": 0
},
"ipa.example.co.uk": {
"skipped": 0,
"ok": 0,
"changed": 0,
"dark": 1,
"failed": true,
"processed": 1,
"failures": 0
},
"mail.example.co.uk": {
"skipped": 0,
"ok": 0,
"changed": 0,
"dark": 1,
"failed": true,
"processed": 1,
"failures": 0
},
"devops.example.co.uk": {
"skipped": 0,
"ok": 0,
"changed": 0,
"dark": 1,
"failed": true,
"processed": 1,
"failures": 0
},
"gitlab.example.co.uk": {
"skipped": 0,
"ok": 0,
"changed": 0,
"dark": 1,
"failed": true,
"processed": 1,
"failures": 0
}
},
"playbook": "system-updates/run-apt-updates.yml",
"limit": "os-ubuntu",
"id": 2722,
"inventory": "Production Servers"
}

View File

@@ -0,0 +1,28 @@
{
"status": "successful",
"credential": "AWX - SSH",
"name": "System - Deploy - Zabbix Agent",
"started": "2018-09-11T19:19:10.153888+00:00",
"extra_vars": "{}",
"traceback": "",
"friendly_name": "Job",
"created_by": "None",
"project": "AWX - Ansible Playbooks",
"url": "http://awx.example.co.uk/#/jobs/playbook/2674",
"finished": "2018-09-11T19:21:41.187605+00:00",
"hosts": {
"chat.example.co.uk": {
"skipped": 28,
"ok": 16,
"changed": 0,
"dark": 0,
"failed": false,
"processed": 1,
"failures": 0
}
},
"playbook": "system-zabbix-agent/deploy-zabbix-agent.yml",
"limit": "!system-awx",
"id": 2674,
"inventory": "Production Servers"
}

View File

@@ -0,0 +1,64 @@
{
"status": "successful",
"credential": "AWX - SSH",
"name": "System - Deploy - Zabbix Agent",
"started": "2018-09-11T19:19:10.153888+00:00",
"extra_vars": "{}",
"traceback": "",
"friendly_name": "Job",
"created_by": "None",
"project": "AWX - Ansible Playbooks",
"url": "http://awx.example.co.uk/#/jobs/playbook/2674",
"finished": "2018-09-11T19:21:41.187605+00:00",
"hosts": {
"gitlab.example.co.uk": {
"skipped": 29,
"ok": 15,
"changed": 0,
"dark": 0,
"failed": false,
"processed": 1,
"failures": 0
},
"chat.example.co.uk": {
"skipped": 28,
"ok": 16,
"changed": 0,
"dark": 0,
"failed": false,
"processed": 1,
"failures": 0
},
"devops.example.co.uk": {
"skipped": 31,
"ok": 13,
"changed": 0,
"dark": 0,
"failed": false,
"processed": 1,
"failures": 0
},
"ipa.example.co.uk": {
"skipped": 31,
"ok": 13,
"changed": 0,
"dark": 0,
"failed": false,
"processed": 1,
"failures": 0
},
"mail.example.co.uk": {
"skipped": 31,
"ok": 13,
"changed": 0,
"dark": 0,
"failed": false,
"processed": 1,
"failures": 0
}
},
"playbook": "system-zabbix-agent/deploy-zabbix-agent.yml",
"limit": "!system-awx",
"id": 2674,
"inventory": "Production Servers"
}

View File

@@ -0,0 +1,11 @@
{
"status": "failed",
"name": "AWX - Project Update",
"started": "2018-09-11T19:26:52.035205+00:00",
"traceback": "",
"friendly_name": "Project Update",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/project/2678",
"finished": "2018-09-11T19:26:56.193385+00:00",
"id": 2678
}

View File

@@ -0,0 +1,11 @@
{
"status": "successful",
"name": "AWX - Project Update",
"started": "2018-09-11T19:26:43.861738+00:00",
"traceback": "",
"friendly_name": "Project Update",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/project/2677",
"finished": "2018-09-11T19:26:50.966798+00:00",
"id": 2677
}

View File

@@ -0,0 +1,11 @@
{
"status": "failed",
"name": "Cleanup Job Details",
"started": "2018-09-13T18:57:15.891742+00:00",
"traceback": "",
"friendly_name": "System Job",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/system/2721",
"finished": "2018-09-13T19:00:39.919286+00:00",
"id": 2721
}

View File

@@ -0,0 +1,11 @@
{
"status": "successful",
"name": "Cleanup Job Details",
"started": "2018-09-13T18:57:15.891742+00:00",
"traceback": "",
"friendly_name": "System Job",
"created_by": "admin",
"url": "http://awx.example.co.uk/#/jobs/system/2721",
"finished": "2018-09-13T19:00:39.919286+00:00",
"id": 2721
}

View File

@@ -0,0 +1,170 @@
from zerver.lib.test_classes import WebhookTestCase
class AnsibletowerHookTests(WebhookTestCase):
def test_ansibletower_project_update_successful_message(self) -> None:
"""
Tests if ansibletower project update successful notification is handled correctly
"""
expected_topic_name = "AWX - Project Update"
expected_message = (
"Project Update: [#2677 AWX - Project Update]"
"(http://awx.example.co.uk/#/jobs/project/2677) was successful."
)
self.check_webhook("project_update_successful", expected_topic_name, expected_message)
def test_ansibletower_project_update_failed_message(self) -> None:
"""
Tests if ansibletower project update failed notification is handled correctly
"""
expected_topic_name = "AWX - Project Update"
expected_message = (
"Project Update: [#2678 AWX - Project Update]"
"(http://awx.example.co.uk/#/jobs/project/2678) failed."
)
self.check_webhook("project_update_failed", expected_topic_name, expected_message)
def test_ansibletower_job_successful_multiple_hosts_message(self) -> None:
"""
Tests if ansibletower job successful multiple hosts notification is handled correctly
"""
expected_topic_name = "System - Deploy - Zabbix Agent"
expected_message = """
Job: [#2674 System - Deploy - Zabbix Agent](http://awx.example.co.uk/#/jobs/playbook/2674) was successful:
* chat.example.co.uk: Success
* devops.example.co.uk: Success
* gitlab.example.co.uk: Success
* ipa.example.co.uk: Success
* mail.example.co.uk: Success
""".strip()
self.check_webhook("job_successful_multiple_hosts", expected_topic_name, expected_message)
def test_ansibletower_job_successful_message(self) -> None:
"""
Tests if ansibletower job successful notification is handled correctly
"""
expected_topic_name = "System - Deploy - Zabbix Agent"
expected_message = """
Job: [#2674 System - Deploy - Zabbix Agent](http://awx.example.co.uk/#/jobs/playbook/2674) was successful:
* chat.example.co.uk: Success
""".strip()
self.check_webhook("job_successful", expected_topic_name, expected_message)
def test_ansibletower_nine_job_successful_message(self) -> None:
"""
Test to see if awx/ansibletower 9.x.x job successful notifications are
handled just as successfully as prior to 9.x.x.
"""
expected_topic_name = "Demo Job Template"
expected_message = """
Job: [#1 Demo Job Template](https://towerhost/#/jobs/playbook/1) was successful:
* localhost: Success
""".strip()
self.check_webhook(
"job_complete_successful_awx_9.1.1", expected_topic_name, expected_message
)
def test_ansibletower_job_failed_message(self) -> None:
"""
Tests if ansibletower job failed notification is handled correctly
"""
expected_topic_name = "System - Updates - Ubuntu"
expected_message = """
Job: [#2722 System - Updates - Ubuntu](http://awx.example.co.uk/#/jobs/playbook/2722) failed:
* chat.example.co.uk: Failed
""".strip()
self.check_webhook("job_failed", expected_topic_name, expected_message)
def test_ansibletower_job_failed_multiple_hosts_message(self) -> None:
"""
Tests if ansibletower job failed notification is handled correctly
"""
expected_topic_name = "System - Updates - Ubuntu"
expected_message = """
Job: [#2722 System - Updates - Ubuntu](http://awx.example.co.uk/#/jobs/playbook/2722) failed:
* chat.example.co.uk: Failed
* devops.example.co.uk: Failed
* gitlab.example.co.uk: Failed
* ipa.example.co.uk: Failed
* mail.example.co.uk: Failed
""".strip()
self.check_webhook("job_failed_multiple_hosts", expected_topic_name, expected_message)
def test_ansibletower_inventory_update_successful_message(self) -> None:
"""
Tests if ansibletower inventory update successful notification is handled correctly
"""
expected_topic_name = "AWX - Inventory Update"
expected_message = (
"Inventory Update: [#2724 AWX - Inventory Update]"
"(http://awx.example.co.uk/#/jobs/inventory/2724) was successful."
)
self.check_webhook("inventory_update_successful", expected_topic_name, expected_message)
def test_ansibletower_inventory_update_failed_message(self) -> None:
"""
Tests if ansibletower inventory update failed notification is handled correctly
"""
expected_topic_name = "AWX - Inventory Update"
expected_message = (
"Inventory Update: [#2724 AWX - Inventory Update]"
"(http://awx.example.co.uk/#/jobs/inventory/2724) failed."
)
self.check_webhook("inventory_update_failed", expected_topic_name, expected_message)
def test_ansibletower_adhoc_command_successful_message(self) -> None:
"""
Tests if ansibletower adhoc command successful notification is handled correctly
"""
expected_topic_name = "shell: uname -r"
expected_message = (
"AdHoc Command: [#2726 shell: uname -r]"
"(http://awx.example.co.uk/#/jobs/command/2726) was successful."
)
self.check_webhook("adhoc_command_successful", expected_topic_name, expected_message)
def test_ansibletower_adhoc_command_failed_message(self) -> None:
"""
Tests if ansibletower adhoc command failed notification is handled correctly
"""
expected_topic_name = "shell: uname -r"
expected_message = (
"AdHoc Command: [#2726 shell: uname -r]"
"(http://awx.example.co.uk/#/jobs/command/2726) failed."
)
self.check_webhook("adhoc_command_failed", expected_topic_name, expected_message)
def test_ansibletower_system_job_successful_message(self) -> None:
"""
Tests if ansibletower system job successful notification is handled correctly
"""
expected_topic_name = "Cleanup Job Details"
expected_message = (
"System Job: [#2721 Cleanup Job Details]"
"(http://awx.example.co.uk/#/jobs/system/2721) was successful."
)
self.check_webhook("system_job_successful", expected_topic_name, expected_message)
def test_ansibletower_system_job_failed_message(self) -> None:
"""
Tests if ansibletower system job failed notification is handled correctly
"""
expected_topic_name = "Cleanup Job Details"
expected_message = (
"System Job: [#2721 Cleanup Job Details]"
"(http://awx.example.co.uk/#/jobs/system/2721) failed."
)
self.check_webhook("system_job_failed", expected_topic_name, expected_message)

View File

@@ -0,0 +1,104 @@
import operator
from django.http import HttpRequest, HttpResponse
from zerver.decorator import webhook_view
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import WildValue, check_bool, check_int, check_string
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.models import UserProfile
ANSIBLETOWER_DEFAULT_MESSAGE_TEMPLATE = "{friendly_name}: [#{id} {name}]({url}) {status}."
ANSIBLETOWER_JOB_MESSAGE_TEMPLATE = """
{friendly_name}: [#{id} {name}]({url}) {status}:
{hosts_final_data}
""".strip()
ANSIBLETOWER_JOB_HOST_ROW_TEMPLATE = "* {hostname}: {status}\n"
@webhook_view("AnsibleTower")
@typed_endpoint
def api_ansibletower_webhook(
request: HttpRequest,
user_profile: UserProfile,
*,
payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
body = get_body(payload)
topic_name = payload["name"].tame(check_string)
check_send_webhook_message(request, user_profile, topic_name, body)
return json_success(request)
def extract_friendly_name(payload: WildValue) -> str:
tentative_job_name = payload.get("friendly_name", "").tame(check_string)
if not tentative_job_name:
url = payload["url"].tame(check_string)
segments = url.split("/")
tentative_job_name = segments[-3]
if tentative_job_name == "jobs":
tentative_job_name = "Job"
return tentative_job_name
def get_body(payload: WildValue) -> str:
friendly_name = extract_friendly_name(payload)
if friendly_name == "Job":
hosts_data = []
for host, host_data in payload["hosts"].items():
if host_data["failed"].tame(check_bool):
hoststatus = "Failed"
else:
hoststatus = "Success"
hosts_data.append(
{
"hostname": host,
"status": hoststatus,
}
)
if payload["status"].tame(check_string) == "successful":
status = "was successful"
else:
status = "failed"
return ANSIBLETOWER_JOB_MESSAGE_TEMPLATE.format(
name=payload["name"].tame(check_string),
friendly_name=friendly_name,
id=payload["id"].tame(check_int),
url=payload["url"].tame(check_string),
status=status,
hosts_final_data=get_hosts_content(hosts_data),
)
else:
if payload["status"].tame(check_string) == "successful":
status = "was successful"
else:
status = "failed"
data = {
"name": payload["name"].tame(check_string),
"friendly_name": friendly_name,
"id": payload["id"].tame(check_int),
"url": payload["url"].tame(check_string),
"status": status,
}
return ANSIBLETOWER_DEFAULT_MESSAGE_TEMPLATE.format(**data)
def get_hosts_content(hosts_data: list[dict[str, str]]) -> str:
hosts_data = sorted(hosts_data, key=operator.itemgetter("hostname"))
hosts_content = ""
for host in hosts_data:
hosts_content += ANSIBLETOWER_JOB_HOST_ROW_TEMPLATE.format(
hostname=host["hostname"],
status=host["status"],
)
return hosts_content