1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-06-18 22:03:04 +00:00

refactor: switch from message_reply_option to create_new_thread

This commit is contained in:
Tom Scholz 2026-06-05 09:20:41 +02:00
parent 6977735ab2
commit 05c456eb9a
2 changed files with 44 additions and 51 deletions

View file

@ -35,20 +35,20 @@ options:
description:
- The text of the message to send.
- 'Emoji must be supplied as Unicode characters (for example V(🚀)). The Chat API does not
render C(:shortcode:) style emoji in plain text messages; they appear as literal text.'
render C(:shortcode:) style emoji in plain text messages as they appear as literal text.'
thread_key:
type: str
description:
- An arbitrary key used to start or reply to a message thread.
- When set, O(message_reply_option) controls the behavior when the thread is not found.
message_reply_option:
type: str
- When set, O(create_new_thread) controls the behavior when the thread is not found.
create_new_thread:
type: bool
default: true
description:
- Behavior when O(thread_key) is supplied but no matching thread exists.
- Controls behavior when O(thread_key) is set but no matching thread exists.
- When V(true), a new thread is started if no matching thread is found.
- When V(false), the message is only posted if a matching thread already exists, otherwise it fails.
- Only used when O(thread_key) is set.
choices:
- REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD
- REPLY_MESSAGE_OR_FAIL
validate_certs:
type: bool
default: true
@ -73,18 +73,18 @@ EXAMPLES = r"""
webhook_url: "https://chat.googleapis.com/v1/spaces/SPACE_ID/messages?key=KEY&token=TOKEN"
text: 'Starting a thread'
thread_key: 'deploy-2026-06-01'
message_reply_option: REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD
create_new_thread: true
# Post each deploy step into a single thread. The first message creates the thread
# with REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD; follow-ups use REPLY_MESSAGE_OR_FAIL so
# they only post if the opening message landed, rather than leaving orphan threads.
# with create_new_thread=true. Follow-ups use create_new_thread=false so they only
# post if the opening message went through, rather than leaving orphan threads.
# Note: webhooks are rate-limited to 1 request per second per space.
- name: Announce deploy start (starts the thread)
community.general.google_chat:
webhook_url: "{{ chat_webhook }}"
text: "🚀 Starting deploy of *{{ app_version | default('latest') }}* to {{ inventory_hostname }}"
thread_key: "{{ deploy_thread }}"
message_reply_option: REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD
create_new_thread: true
delegate_to: localhost
run_once: true
# deploy_thread is defined once for the play, for example:
@ -93,9 +93,9 @@ EXAMPLES = r"""
- name: Report a step into the same thread
community.general.google_chat:
webhook_url: "{{ chat_webhook }}"
text: "✅ Step 1/3 code checked out"
text: "✅ Step 1/3 code checked out"
thread_key: "{{ deploy_thread }}"
message_reply_option: REPLY_MESSAGE_OR_FAIL
create_new_thread: false
delegate_to: localhost
run_once: true
@ -112,16 +112,16 @@ EXAMPLES = r"""
webhook_url: "{{ chat_webhook }}"
text: "🎉 Deploy to {{ inventory_hostname }} complete"
thread_key: "{{ deploy_thread }}"
message_reply_option: REPLY_MESSAGE_OR_FAIL
create_new_thread: false
delegate_to: localhost
run_once: true
rescue:
- name: Report failure into the thread
community.general.google_chat:
webhook_url: "{{ chat_webhook }}"
text: "❌ Deploy to {{ inventory_hostname }} *failed* {{ ansible_failed_task.name }}"
text: "❌ Deploy to {{ inventory_hostname }} *failed* {{ ansible_failed_task.name }}"
thread_key: "{{ deploy_thread }}"
message_reply_option: REPLY_MESSAGE_OR_FAIL
create_new_thread: false
delegate_to: localhost
run_once: true
@ -156,11 +156,14 @@ def build_payload(text, thread_key):
return payload
def build_url(webhook_url, message_reply_option):
if message_reply_option is None:
return webhook_url
def build_url(webhook_url, thread_key, create_new_thread):
params = {}
if thread_key is not None:
params["messageReplyOption"] = (
"REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD" if create_new_thread else "REPLY_MESSAGE_OR_FAIL"
)
sep = "&" if "?" in webhook_url else "?"
return f"{webhook_url}{sep}{urlencode({'messageReplyOption': message_reply_option})}"
return f"{webhook_url}{sep}{urlencode(params)}"
def do_notify(module, url, payload):
@ -188,10 +191,7 @@ def main():
webhook_url=dict(type="str", required=True, no_log=True),
text=dict(type="str", required=True),
thread_key=dict(type="str", no_log=False),
message_reply_option=dict(
type="str",
choices=["REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD", "REPLY_MESSAGE_OR_FAIL"],
),
create_new_thread=dict(type="bool", default=True),
validate_certs=dict(type="bool", default=True),
),
supports_check_mode=True,
@ -201,7 +201,11 @@ def main():
module.exit_json(changed=True)
payload = build_payload(module.params["text"], module.params["thread_key"])
url = build_url(module.params["webhook_url"], module.params["message_reply_option"])
url = build_url(
module.params["webhook_url"],
module.params["thread_key"],
module.params["create_new_thread"],
)
response = do_notify(module, url, payload)

View file

@ -53,12 +53,6 @@ class TestGoogleChatModule(ModuleTestCase):
with self.assertRaises(AnsibleFailJson):
self.module.main()
def test_invalid_message_reply_option(self):
"""Failure when message_reply_option is not one of the valid choices"""
with set_module_args({"webhook_url": WEBHOOK, "text": "test", "message_reply_option": "BOGUS"}):
with self.assertRaises(AnsibleFailJson):
self.module.main()
def test_successful_message(self):
"""tests sending a plain message"""
with set_module_args({"webhook_url": WEBHOOK, "text": "test"}):
@ -115,7 +109,7 @@ class TestGoogleChatModule(ModuleTestCase):
assert call_data["thread"]["threadKey"] == "deploy-1"
assert result.exception.args[0]["thread_name"] == "spaces/AAAA/threads/CCCC"
def test_message_reply_option_appended_to_url(self):
def test_create_new_thread_option(self):
"""message_reply_option must be added as a query parameter with & (webhook already has ?)"""
with set_module_args(
{
@ -134,7 +128,7 @@ class TestGoogleChatModule(ModuleTestCase):
self.module.main()
url = fetch_url_mock.call_args[1]["url"]
assert url == WEBHOOK + "&messageReplyOption=REPLY_MESSAGE_OR_FAIL"
assert "messageReplyOption=REPLY_MESSAGE_OR_FAIL" in url
def test_check_mode(self):
"""check mode reports changed and never calls the API"""
@ -158,22 +152,17 @@ def test_build_payload_with_thread():
assert payload == {"text": "hello", "thread": {"threadKey": "deploy-1"}}
build_url_cases = [
# (webhook_url, message_reply_option, expected_url)
(WEBHOOK, None, WEBHOOK),
(
WEBHOOK,
"REPLY_MESSAGE_OR_FAIL",
WEBHOOK + "&messageReplyOption=REPLY_MESSAGE_OR_FAIL",
),
(
"https://chat.googleapis.com/v1/spaces/X/messages",
"REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD",
"https://chat.googleapis.com/v1/spaces/X/messages?messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD",
),
]
def test_build_url_without_thread():
url = google_chat.build_url(WEBHOOK, None, True)
assert url.startswith(WEBHOOK + "?")
assert "messageReplyOption" not in url
@pytest.mark.parametrize("webhook_url, option, expected", build_url_cases)
def test_build_url(webhook_url, option, expected):
assert google_chat.build_url(webhook_url, option) == expected
def test_build_url_create_new_thread_true():
url = google_chat.build_url(WEBHOOK, "deploy-1", True)
assert "messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD" in url
def test_build_url_create_new_thread_false():
url = google_chat.build_url(WEBHOOK, "deploy-1", False)
assert "messageReplyOption=REPLY_MESSAGE_OR_FAIL" in url