Skip to content

Commit b56097c

Browse files
[patch] allow sending message to multiple channels
1 parent d071f0b commit b56097c

File tree

1 file changed

+152
-76
lines changed

1 file changed

+152
-76
lines changed

bin/mas-devops-notify-slack

Lines changed: 152 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def notifyProvisionRoks(channels: list[str], rc: int, additionalMsg: str | None
9898

9999

100100
def notifyPipelineStart(channels: list[str], instanceId: str | None = None, pipelineName: str | None = None) -> dict | None:
101-
"""Send Slack notification about pipeline start and create thread."""
101+
"""Send Slack notification about pipeline start and create thread for all channels."""
102102
namespace = f"mas-{instanceId}-pipelines"
103103
if instanceId is None or instanceId == "":
104104
print("instanceId must be set")
@@ -110,27 +110,48 @@ def notifyPipelineStart(channels: list[str], instanceId: str | None = None, pipe
110110
print("Pipeline start notification already sent")
111111
return threadInfo
112112

113-
# Send pipeline started message
113+
# Send pipeline started message to all channels
114114
toolchainLink = _getToolchainLink()
115115
instanceInfo = f"\nInstance ID: `{instanceId}`" if instanceId else ""
116116
message = [
117117
SlackUtil.buildHeader(f"🚀 MAS {pipelineName} Pipeline Started"),
118118
SlackUtil.buildSection(f"Pipeline Run: {instanceInfo}\n{toolchainLink}")
119119
]
120-
response = SlackUtil.postMessageBlocks(channels[0], message)
121-
if response.data.get("ok", False):
122-
threadId = response["ts"]
123-
channelId = response["channel"]
124-
# Store thread information in ConfigMap
125-
SlackUtil.createThreadConfigMap(namespace, channelId, threadId, instanceId)
126-
return SlackUtil.getThreadConfigMap(namespace, instanceId)
120+
response = SlackUtil.postMessageBlocks(channels, message)
121+
122+
# Store thread information for all channels in ConfigMap
123+
configMapData = {"instanceId": instanceId}
124+
125+
if isinstance(response, list):
126+
# Multiple channels - store each channel's thread info
127+
for idx, res in enumerate(response):
128+
if res.data.get("ok", False):
129+
threadId = res["ts"]
130+
channelId = res["channel"]
131+
# Store with channel-specific keys
132+
configMapData[f"channel_{idx}"] = channelId
133+
configMapData[f"threadId_{idx}"] = threadId
134+
configMapData["channel_count"] = str(len(response))
127135
else:
128-
print("Failed to send pipeline start Slack message")
129-
return False
136+
# Single channel
137+
if response.data.get("ok", False):
138+
threadId = response["ts"]
139+
channelId = response["channel"]
140+
configMapData["channel_0"] = channelId
141+
configMapData["threadId_0"] = threadId
142+
configMapData["channel_count"] = "1"
143+
else:
144+
print("Failed to send pipeline start Slack message")
145+
return False
146+
147+
# Create ConfigMap with all channel/thread info
148+
SlackUtil.createThreadConfigMap(namespace, "", "", instanceId)
149+
SlackUtil.updateThreadConfigMap(namespace, instanceId, configMapData)
150+
return SlackUtil.getThreadConfigMap(namespace, instanceId)
130151

131152

132153
def notifyAnsibleStart(channels: list[str], taskName: str, instanceId: str | None = None, pipelineName: str | None = None) -> bool:
133-
"""Send Slack notification about Ansible task start."""
154+
"""Send Slack notification about Ansible task start to all channels."""
134155
namespace = f"mas-{instanceId}-pipelines"
135156
if instanceId is None or instanceId == "":
136157
print("instanceId must be set")
@@ -141,29 +162,48 @@ def notifyAnsibleStart(channels: list[str], taskName: str, instanceId: str | Non
141162
if threadInfo is None:
142163
print("No thread found - creating pipeline start notification")
143164
threadInfo = notifyPipelineStart(channels, instanceId, pipelineName)
144-
threadId = threadInfo.get("threadId")
145-
channelId = threadInfo.get("channelId")
146165

147-
# Send task start message as thread reply
166+
# Get channel count
167+
channelCount = int(threadInfo.get("channel_count", "0"))
168+
if channelCount == 0:
169+
print("No channels found in thread info")
170+
return False
171+
172+
# Send task start message as thread reply to all channels
148173
taskMessage = [
149174
SlackUtil.buildSection(f"⏳ **{taskName}** - Started")
150175
]
151-
response = SlackUtil.postMessageBlocks(channelId, taskMessage, threadId)
152176

153-
# Save message timestamp in ConfigMap for later editing
154-
if response.data.get("ok", False):
155-
messageTs = response.data.get("ts")
156-
if messageTs:
157-
# Store with task name as key
158-
SlackUtil.updateThreadConfigMap(namespace, instanceId, {f"task_{taskName}": messageTs})
177+
allSuccess = True
178+
taskMessageData = {}
159179

160-
if isinstance(response, list):
161-
return all([res.data.get("ok", False) for res in response])
162-
return response.data.get("ok", False)
180+
for idx in range(channelCount):
181+
channelId = threadInfo.get(f"channel_{idx}")
182+
threadId = threadInfo.get(f"threadId_{idx}")
183+
184+
if channelId and threadId:
185+
response = SlackUtil.postMessageBlocks(channelId, taskMessage, threadId)
186+
187+
# Save message timestamp for this channel
188+
if response.data.get("ok", False):
189+
messageTs = response.data.get("ts")
190+
if messageTs:
191+
# Store with task name and channel index as key
192+
taskMessageData[f"task_{taskName}_{idx}"] = messageTs
193+
else:
194+
allSuccess = False
195+
else:
196+
allSuccess = False
197+
198+
# Update ConfigMap with all task message timestamps
199+
if taskMessageData:
200+
SlackUtil.updateThreadConfigMap(namespace, instanceId, taskMessageData)
201+
202+
return allSuccess
163203

164204

165205
def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceId: str | None = None, pipelineName: str | None = None) -> bool:
166-
"""Send Slack notification about Ansible task completion status."""
206+
"""Send Slack notification about Ansible task completion status to all channels."""
167207
namespace = f"mas-{instanceId}-pipelines"
168208
if instanceId is None or instanceId == "":
169209
print("instanceId must be set")
@@ -175,11 +215,11 @@ def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceI
175215
print("No thread found - creating pipeline start notification")
176216
threadInfo = notifyPipelineStart(channels, instanceId, pipelineName)
177217

178-
threadId = threadInfo.get("threadId")
179-
channelId = threadInfo.get("channelId")
180-
181-
# Get the message timestamp for this task
182-
taskMessageTs = threadInfo.get(f"task_{taskName}")
218+
# Get channel count
219+
channelCount = int(threadInfo.get("channel_count", "0"))
220+
if channelCount == 0:
221+
print("No channels found in thread info")
222+
return False
183223

184224
# Determine status
185225
if rc == 0:
@@ -189,62 +229,83 @@ def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceI
189229
emoji = "❌"
190230
status = "Failed"
191231

192-
# Calculate task duration if we have the message timestamp
193-
durationText = ""
194-
if taskMessageTs:
195-
from datetime import datetime
196-
try:
197-
# Message timestamp is in format "1234567890.123456"
198-
startTime = float(taskMessageTs)
199-
endTime = datetime.utcnow().timestamp()
200-
duration = int(endTime - startTime)
201-
202-
hours, remainder = divmod(duration, 3600)
203-
minutes, seconds = divmod(remainder, 60)
204-
205-
if hours > 0:
206-
durationText = f" ({hours}h {minutes}m {seconds}s)"
207-
elif minutes > 0:
208-
durationText = f" ({minutes}m {seconds}s)"
209-
else:
210-
durationText = f" ({seconds}s)"
211-
except Exception as e:
212-
print(f"Failed to calculate duration: {e}")
232+
allSuccess = True
233+
234+
# Update message in each channel
235+
for idx in range(channelCount):
236+
channelId = threadInfo.get(f"channel_{idx}")
237+
threadId = threadInfo.get(f"threadId_{idx}")
238+
taskMessageTs = threadInfo.get(f"task_{taskName}_{idx}")
239+
240+
if not channelId or not threadId:
241+
allSuccess = False
242+
continue
243+
244+
# Calculate task duration if we have the message timestamp
245+
durationText = ""
246+
if taskMessageTs:
247+
from datetime import datetime
248+
try:
249+
# Message timestamp is in format "1234567890.123456"
250+
startTime = float(taskMessageTs)
251+
endTime = datetime.utcnow().timestamp()
252+
duration = int(endTime - startTime)
253+
254+
hours, remainder = divmod(duration, 3600)
255+
minutes, seconds = divmod(remainder, 60)
256+
257+
if hours > 0:
258+
durationText = f" ({hours}h {minutes}m {seconds}s)"
259+
elif minutes > 0:
260+
durationText = f" ({minutes}m {seconds}s)"
261+
else:
262+
durationText = f" ({seconds}s)"
263+
except Exception as e:
264+
print(f"Failed to calculate duration for channel {idx}: {e}")
265+
266+
# Build the completion message
267+
taskMessage = [
268+
SlackUtil.buildSection(f"{emoji} **{taskName}** - {status}{durationText}")
269+
]
270+
if rc != 0:
271+
taskMessage.append(SlackUtil.buildSection(f"Return Code: `{rc}`\nCheck logs for details"))
213272

214-
# Build the completion message
215-
taskMessage = [
216-
SlackUtil.buildSection(f"{emoji} **{taskName}** - {status}{durationText}")
217-
]
218-
if rc != 0:
219-
taskMessage.append(SlackUtil.buildSection(f"Return Code: `{rc}`\nCheck logs for details"))
273+
# If we have the original message timestamp, update it; otherwise post new message
274+
if taskMessageTs:
275+
response = SlackUtil.updateMessageBlocks(channelId, taskMessageTs, taskMessage)
276+
if not response.data.get("ok", False):
277+
allSuccess = False
278+
else:
279+
# Fallback: post new message if task start message wasn't tracked
280+
print(f"No start message found for task {taskName} in channel {idx}, posting new completion message")
281+
response = SlackUtil.postMessageBlocks(channelId, taskMessage, threadId)
282+
if not response.data.get("ok", False):
283+
allSuccess = False
220284

221-
# If we have the original message timestamp, update it; otherwise post new message
222-
if taskMessageTs:
223-
response = SlackUtil.updateMessageBlocks(channelId, taskMessageTs, taskMessage)
224-
return response.data.get("ok", False)
225-
else:
226-
# Fallback: post new message if task start message wasn't tracked
227-
print(f"No start message found for task {taskName}, posting new completion message")
228-
response = SlackUtil.postMessageBlocks(channelId, taskMessage, threadId)
229-
if isinstance(response, list):
230-
return all([res.data.get("ok", False) for res in response])
231-
return response.data.get("ok", False)
285+
return allSuccess
232286

233287

234288
def notifyPipelineComplete(channels: list[str], rc: int, instanceId: str | None = None, pipelineName: str | None = None) -> bool:
235-
"""Send Slack notification about pipeline completion and cleanup ConfigMap."""
289+
"""Send Slack notification about pipeline completion to all channels and cleanup ConfigMap."""
236290
namespace = f"mas-{instanceId}-pipelines"
237291
if instanceId is None or instanceId == "":
238292
print("instanceId must be set")
239293
sys.exit(1)
294+
240295
# Get thread information
241296
threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId)
242297
if threadInfo is None:
243298
print("No thread information found - pipeline may not have started properly")
244299
return False
245-
threadId = threadInfo.get("threadId")
246-
channelId = threadInfo.get("channelId")
300+
301+
# Get channel count
302+
channelCount = int(threadInfo.get("channel_count", "0"))
303+
if channelCount == 0:
304+
print("No channels found in thread info")
305+
return False
306+
247307
startTime = threadInfo.get("startTime")
308+
248309
# Calculate duration if start time is available
249310
durationText = ""
250311
if startTime:
@@ -261,6 +322,7 @@ def notifyPipelineComplete(channels: list[str], rc: int, instanceId: str | None
261322
durationText = f"\nTotal Duration: {minutes}m {seconds}s"
262323
except Exception:
263324
pass
325+
264326
instanceInfo = f"\nInstance ID: `{instanceId}`" if instanceId else ""
265327
if rc == 0:
266328
emoji = "🎉"
@@ -270,16 +332,30 @@ def notifyPipelineComplete(channels: list[str], rc: int, instanceId: str | None
270332
emoji = "💥"
271333
status = "Failed"
272334
additionalInfo = f"\nPipeline failed with return code: `{rc}`"
335+
273336
message = [
274337
SlackUtil.buildHeader(f"{emoji} MAS {pipelineName} Pipeline {status}"),
275338
SlackUtil.buildSection(f"Pipeline Run: {instanceInfo}{durationText}{additionalInfo}")
276339
]
277-
response = SlackUtil.postMessageBlocks(channelId, message, threadId)
340+
341+
allSuccess = True
342+
343+
# Send completion message to all channels
344+
for idx in range(channelCount):
345+
channelId = threadInfo.get(f"channel_{idx}")
346+
threadId = threadInfo.get(f"threadId_{idx}")
347+
348+
if channelId and threadId:
349+
response = SlackUtil.postMessageBlocks(channelId, message, threadId)
350+
if not response.data.get("ok", False):
351+
allSuccess = False
352+
else:
353+
allSuccess = False
354+
278355
# Clean up ConfigMap
279356
SlackUtil.deleteThreadConfigMap(namespace, instanceId)
280-
if isinstance(response, list):
281-
return all([res.data.get("ok", False) for res in response])
282-
return response.data.get("ok", False)
357+
358+
return allSuccess
283359

284360

285361
if __name__ == "__main__":

0 commit comments

Comments
 (0)