Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
60b4c77
[minor] Support January Catalog Update
prveenkumr Jan 5, 2026
fba4bfd
Merge branch 'stable' into update-260129
prveenkumr Jan 6, 2026
d0445dd
Merge branch 'stable' into update-260129
prveenkumr Jan 6, 2026
9def75d
Merge branch 'stable' into update-260129
prveenkumr Jan 8, 2026
a5816f7
Merge branch 'stable' into update-260129
prveenkumr Jan 12, 2026
e924ed2
[minor] Add db2_channel_default to control Db2 operator version via c…
jainyjoseph Jan 19, 2026
11976cc
Merge branch 'stable' into update-260129
prveenkumr Jan 19, 2026
e10498d
[patch] jan release prep
prveenkumr Jan 19, 2026
3afcf87
[patch] update db2u version
anilprajapatiibm Jan 20, 2026
c1facb3
[patch] add elasticsearch_version
anilprajapatiibm Jan 21, 2026
c499634
[patch] updated latest app version of jan release
prveenkumr Jan 21, 2026
d039af4
[patch] update latest catalog digest
prveenkumr Jan 21, 2026
06f6007
[patch] add catalog digest
anilprajapatiibm Jan 22, 2026
b15faa0
Merge branch 'stable' into update-260129
prveenkumr Jan 22, 2026
8c4e44e
[patch] update mas-core, iot and manage apps version
prveenkumr Jan 22, 2026
744a18d
[patch] update apps version
prveenkumr Jan 22, 2026
66441bc
Merge branch 'stable' into update-260129
prveenkumr Jan 22, 2026
49ba805
Merge branch 'stable' into update-260129
prveenkumr Jan 26, 2026
5c47c3d
[patch] mas and manage fc, mvi and digest update
prveenkumr Jan 27, 2026
66cd091
[patch] update latest catalog digest
prveenkumr Jan 27, 2026
33f3d82
[patch] remove -amd64 form the optimizer 92-fc
anilprajapatiibm Jan 27, 2026
a1ce79e
[patch] cli notification
anilprajapatiibm Jan 27, 2026
917a9c4
[patch] remove extra new lines
anilprajapatiibm Jan 28, 2026
84328e3
[patch] remove extra newlines
anilprajapatiibm Jan 28, 2026
70ab05d
[patch] remove extra newlines
anilprajapatiibm Jan 28, 2026
2537f04
[patch] add Exception
anilprajapatiibm Jan 28, 2026
58c7af8
[patch] remove extra blank spaces
anilprajapatiibm Jan 28, 2026
7b559fe
[patch] add pipeline start end ansible start end slack notification m…
anilprajapatiibm Feb 3, 2026
c88cf12
[minor] Support February Catalog Update
prveenkumr Feb 9, 2026
68e3d16
[patch] remove pipeline name from the messages
anilprajapatiibm Feb 10, 2026
ba24702
[patch] Adding OCP 4.20 (#187)
shimto-jacob-siby Feb 10, 2026
a951579
Merge branch 'stable' into update-260226
prveenkumr Feb 10, 2026
a956b93
[patch] update db2u, catalog digest
prveenkumr Feb 12, 2026
6fdd2ce
Merge branch 'stable' into notification-ap
anilprajapatiibm Feb 13, 2026
f3f2851
create mas-devops secret
anilprajapatiibm Feb 13, 2026
8704ea1
[patch] create mas-devops-slack secret with slack vars
anilprajapatiibm Feb 13, 2026
9527f56
[patch] add slack secrets vars into secrets
anilprajapatiibm Feb 13, 2026
b7c3a3b
[patch] add pipeline name in message
anilprajapatiibm Feb 13, 2026
653fc25
Merge branch 'stable' into update-260226
prveenkumr Feb 17, 2026
1eccd65
[patch] assist, mvi, iot, monitor, aiservice, dd app version updated
prveenkumr Feb 17, 2026
8548d21
[patch] Use editable install in CI workflows
prveenkumr Feb 17, 2026
aa5d8b5
Revert "[patch] Use editable install in CI workflows"
prveenkumr Feb 17, 2026
6e061c8
[patch] Update MAS catalogs: digests and component versions
prveenkumr Feb 19, 2026
1c8b7db
[patch] Use editable install in CI workflows
prveenkumr Feb 19, 2026
169910e
Revert "[patch] Use editable install in CI workflows"
prveenkumr Feb 19, 2026
e2a877c
[patch] add tsm_version to 1.7.3 in catalogs
prveenkumr Feb 19, 2026
d4a2777
Merge branch 'stable' into update-260226
prveenkumr Feb 19, 2026
8817df4
[patch] update catalog digest
prveenkumr Feb 19, 2026
034eac6
[patch] Bump catalog digests and MAS manage versions
prveenkumr Feb 21, 2026
3f5b48b
[patch] Update catalog digests and MAS component versions
prveenkumr Feb 22, 2026
edcdb13
[patch] rebuild py-devops
prveenkumr Feb 23, 2026
e14bf55
Merge branch 'stable' into notification-ap
anilprajapatiibm Feb 24, 2026
9065502
Merge branch 'update-260226' into notification-ap
anilprajapatiibm Feb 24, 2026
e74a5a5
[patch] return getThreadConfigMap in 1st time message creation
anilprajapatiibm Feb 24, 2026
d071f0b
[patch] send start message and update the same message on completion
anilprajapatiibm Feb 24, 2026
b56097c
[patch] allow sending message to multiple channels
anilprajapatiibm Feb 25, 2026
eceff31
Merge branch 'stable' into notification-ap
anilprajapatiibm Mar 5, 2026
4663649
[patch] add pipeline name ags
anilprajapatiibm Mar 6, 2026
70ebe4c
Merge branch 'stable' into notification-ap
anilprajapatiibm Mar 9, 2026
2a8ee9f
[patch] add slack tests
anilprajapatiibm Mar 10, 2026
8a29c75
[patch] fix the pytest
anilprajapatiibm Mar 10, 2026
b4bfad5
Merge branch 'stable' into notification-ap
anilprajapatiibm Apr 8, 2026
c0a31e2
Merge branch 'stable' into notification-ap
anilprajapatiibm Apr 8, 2026
0f35b92
[patch] add prepareRestoreSecrets
anilprajapatiibm Apr 8, 2026
b2d7e9e
[patch]fix message
anilprajapatiibm Apr 10, 2026
d94bc03
[patch] add pipeline name in config name
anilprajapatiibm Apr 10, 2026
93397fe
Revert "[patch] add pipeline name in config name"
anilprajapatiibm Apr 10, 2026
eb6b98a
Reapply "[patch] add pipeline name in config name"
anilprajapatiibm Apr 15, 2026
8a54b66
[patch] use new comfigmap name in debug message
anilprajapatiibm Apr 15, 2026
a0724aa
[patch] add prepareUpdateSlackSecrets
anilprajapatiibm Apr 16, 2026
f16e1de
[patch] work with mas update pipeline
anilprajapatiibm Apr 16, 2026
e18b767
patch] add mas update pipeline testing
anilprajapatiibm Apr 16, 2026
7f4f20b
[patch] fail if channel is empty
anilprajapatiibm Apr 16, 2026
4fd09a2
[patch] add special case mas-update post-deps-update-verify-ingress
anilprajapatiibm Apr 16, 2026
c62503a
[patch] add message
anilprajapatiibm Apr 16, 2026
1fd8cd4
[patch] image_pull_policy added
anilprajapatiibm Apr 16, 2026
618408e
Merge branch 'stable' into notification-ap
anilprajapatiibm Apr 16, 2026
819621d
Delete src/mas/devops/data/catalogs/v9-290129-ppc64le.yaml
durera Apr 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 306 additions & 1 deletion bin/mas-devops-notify-slack
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,300 @@ def notifyProvisionRoks(channels: list[str], rc: int, additionalMsg: str | None
return response.data.get("ok", False)


def notifyPipelineStart(channels: list[str], instanceId: str | None = None, pipelineName: str | None = None) -> dict | None:
"""Send Slack notification about pipeline start and create thread for all channels."""
# Exit early if no channels provided
if not channels or len(channels) == 0:
print("No Slack channels provided - skipping pipeline start notification")
return None

# For update pipeline, use mas-pipelines namespace (no instance ID)
# For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace
if instanceId is None or instanceId == "":
namespace = "mas-pipelines"
else:
namespace = f"mas-{instanceId}-pipelines"

# Check if thread already exists
threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName)
if threadInfo is not None:
print("Pipeline start notification already sent")
return threadInfo

# Send pipeline started message to all channels
toolchainLink = _getToolchainLink()
instanceInfo = f"Instance ID: `{instanceId}`" if instanceId else ""
message = [
SlackUtil.buildHeader(f"πŸš€ MAS {pipelineName} Pipeline Started"),
SlackUtil.buildSection(f"Pipeline Run: {pipelineName}\n{instanceInfo}\n{toolchainLink}")
]
response = SlackUtil.postMessageBlocks(channels, message)

# Store thread information for all channels in ConfigMap
configMapData = {"instanceId": instanceId, "pipelineName": pipelineName}

if isinstance(response, list):
# Multiple channels - store each channel's thread info
for idx, res in enumerate(response):
if res.data.get("ok", False):
threadId = res["ts"]
channelId = res["channel"]
# Store with channel-specific keys
configMapData[f"channel_{idx}"] = channelId
configMapData[f"threadId_{idx}"] = threadId
configMapData["channel_count"] = str(len(response))
else:
# Single channel
if response.data.get("ok", False):
threadId = response["ts"]
channelId = response["channel"]
configMapData["channel_0"] = channelId
configMapData["threadId_0"] = threadId
configMapData["channel_count"] = "1"
else:
print("Failed to send pipeline start Slack message")
return False

# Create ConfigMap with all channel/thread info
SlackUtil.createThreadConfigMap(namespace, instanceId, pipelineName)
SlackUtil.updateThreadConfigMap(namespace, instanceId, configMapData, pipelineName)
return SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName)


def notifyAnsibleStart(channels: list[str], taskName: str, instanceId: str | None = None, pipelineName: str | None = None) -> bool:
"""Send Slack notification about Ansible task start to all channels."""
# Exit early if no channels provided
if not channels or len(channels) == 0:
print("No Slack channels provided - skipping Ansible task start notification")
return False

# For update pipeline, use mas-pipelines namespace (no instance ID)
# For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace
if instanceId is None or instanceId == "":
namespace = "mas-pipelines"
else:
namespace = f"mas-{instanceId}-pipelines"

# Get thread information, create if doesn't exist
threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName)
if threadInfo is None:
print("No thread found - creating pipeline start notification")
threadInfo = notifyPipelineStart(channels, instanceId, pipelineName)

# Get channel count
channelCount = int(threadInfo.get("channel_count", "0"))
if channelCount == 0:
print("No channels found in thread info")
return False

# Send task start message as thread reply to all channels
taskMessage = [
SlackUtil.buildSection(f"⏳ **{taskName}** - Started")
]

allSuccess = True
taskMessageData = {}

for idx in range(channelCount):
channelId = threadInfo.get(f"channel_{idx}")
threadId = threadInfo.get(f"threadId_{idx}")

if channelId and threadId:
response = SlackUtil.postMessageBlocks(channelId, taskMessage, threadId)

# Save message timestamp for this channel
if response.data.get("ok", False):
messageTs = response.data.get("ts")
if messageTs:
# Store with task name and channel index as key
taskMessageData[f"task_{taskName}_{idx}"] = messageTs
else:
allSuccess = False
else:
allSuccess = False

# Update ConfigMap with all task message timestamps
if taskMessageData:
SlackUtil.updateThreadConfigMap(namespace, instanceId, taskMessageData, pipelineName)

return allSuccess


def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceId: str | None = None, pipelineName: str | None = None) -> bool:
"""Send Slack notification about Ansible task completion status to all channels."""
# Exit early if no channels provided
if not channels or len(channels) == 0:
print("No Slack channels provided - skipping Ansible task completion notification")
return False

# For update pipeline, use mas-pipelines namespace (no instance ID)
# For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace
if instanceId is None or instanceId == "":
namespace = "mas-pipelines"
else:
namespace = f"mas-{instanceId}-pipelines"

# Get thread information, create if doesn't exist
threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName)
if threadInfo is None:
print("No thread found - creating pipeline start notification")
threadInfo = notifyPipelineStart(channels, instanceId, pipelineName)

# Get channel count
channelCount = int(threadInfo.get("channel_count", "0"))
if channelCount == 0:
print("No channels found in thread info")
return False

# Determine status
if rc == 0:
emoji = "βœ…"
status = "Success"
else:
emoji = "❌"
status = "Failed"

allSuccess = True

# Update message in each channel
for idx in range(channelCount):
channelId = threadInfo.get(f"channel_{idx}")
threadId = threadInfo.get(f"threadId_{idx}")
taskMessageTs = threadInfo.get(f"task_{taskName}_{idx}")

if not channelId or not threadId:
allSuccess = False
continue

# Calculate task duration if we have the message timestamp
durationText = ""
if taskMessageTs:
from datetime import datetime, timezone
try:
# Message timestamp is in format "1234567890.123456"
startTime = float(taskMessageTs)
endTime = datetime.now(timezone.utc).timestamp()
duration = int(endTime - startTime)

hours, remainder = divmod(duration, 3600)
minutes, seconds = divmod(remainder, 60)

if hours > 0:
durationText = f" ({hours}h {minutes}m {seconds}s)"
elif minutes > 0:
durationText = f" ({minutes}m {seconds}s)"
else:
durationText = f" ({seconds}s)"
except Exception as e:
print(f"Failed to calculate duration for channel {idx}: {e}")

# Build the completion message
taskMessage = [
SlackUtil.buildSection(f"{emoji} **{taskName}** - {status}{durationText}")
]
if rc != 0:
taskMessage.append(SlackUtil.buildSection(f"Return Code: `{rc}`\nCheck logs for details"))

# If we have the original message timestamp, update it; otherwise post new message
if taskMessageTs:
response = SlackUtil.updateMessageBlocks(channelId, taskMessageTs, taskMessage)
if not response.data.get("ok", False):
allSuccess = False
else:
# Fallback: post new message if task start message wasn't tracked
print(f"No start message found for task {taskName} in channel {idx}, posting new completion message")
response = SlackUtil.postMessageBlocks(channelId, taskMessage, threadId)
if not response.data.get("ok", False):
allSuccess = False

# Special case, mas-update pipeline
if namespace == "mas-pipelines" and taskName == "post-deps-update-verify-ingress":
print(f"mas-update pipeline completed with status: {rc}, sending pipeline complete message")
allSuccess: bool = notifyPipelineComplete(channels, rc, instanceId, pipelineName)

return allSuccess


def notifyPipelineComplete(channels: list[str], rc: int, instanceId: str | None = None, pipelineName: str | None = None) -> bool:
"""Send Slack notification about pipeline completion to all channels and cleanup ConfigMap."""
# Exit early if no channels provided
if not channels or len(channels) == 0:
print("No Slack channels provided - skipping pipeline completion notification")
return False

# For update pipeline, use mas-pipelines namespace (no instance ID)
# For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace
if instanceId is None or instanceId == "":
namespace = "mas-pipelines"
else:
namespace = f"mas-{instanceId}-pipelines"

# Get thread information
threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName)
if threadInfo is None:
print("No thread information found - pipeline may not have started properly")
return False

# Get channel count
channelCount = int(threadInfo.get("channel_count", "0"))
if channelCount == 0:
print("No channels found in thread info")
return False

startTime = threadInfo.get("startTime")

# Calculate duration if start time is available
durationText = ""
if startTime:
from datetime import datetime, timezone
try:
start = datetime.fromisoformat(startTime.replace("Z", "+00:00"))
end = datetime.now(timezone.utc)
duration = end - start
hours, remainder = divmod(int(duration.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
if hours > 0:
durationText = f"\nTotal Duration: {hours}h {minutes}m {seconds}s"
else:
durationText = f"\nTotal Duration: {minutes}m {seconds}s"
except Exception:
pass

instanceInfo = f"Instance ID: `{instanceId}`" if instanceId else ""
if rc == 0:
emoji = "πŸŽ‰"
status = "Completed Successfully"
additionalInfo = "\nAll tasks completed successfully"
else:
emoji = "πŸ’₯"
status = "Failed"
additionalInfo = f"\nPipeline failed with return code: `{rc}`"

message = [
SlackUtil.buildHeader(f"{emoji} MAS {pipelineName} Pipeline {status}"),
SlackUtil.buildSection(f"Pipeline Run: {pipelineName}\n{instanceInfo}{durationText}{additionalInfo}")
]

allSuccess = True

# Send completion message to all channels
for idx in range(channelCount):
channelId = threadInfo.get(f"channel_{idx}")
threadId = threadInfo.get(f"threadId_{idx}")

if channelId and threadId:
response = SlackUtil.postMessageBlocks(channelId, message, threadId)
if not response.data.get("ok", False):
allSuccess = False
else:
allSuccess = False

# Clean up ConfigMap
SlackUtil.deleteThreadConfigMap(namespace, instanceId, pipelineName)

return allSuccess


if __name__ == "__main__":
# If SLACK_TOKEN or SLACK_CHANNEL env vars are not set then silently exit taking no action
SLACK_TOKEN = os.getenv("SLACK_TOKEN", "")
Expand All @@ -112,12 +406,23 @@ if __name__ == "__main__":

# Primary Options
parser.add_argument("--action", required=True)
parser.add_argument("--rc", required=True, type=int)
parser.add_argument("--rc", required=False, type=int)
parser.add_argument("--msg", required=False, default=None)
parser.add_argument("--task-name", required=False, default="")
parser.add_argument("--instance-id", required=False, default=None)
parser.add_argument("--pipeline-name", required=False, default=None)

args, unknown = parser.parse_known_args()

if args.action == "ocp-provision-fyre":
notifyProvisionFyre(channelList, args.rc, args.msg)
elif args.action == "ocp-provision-roks":
notifyProvisionRoks(channelList, args.rc, args.msg)
elif args.action == "pipeline-start":
notifyPipelineStart(channelList, args.instance_id, args.pipeline_name)
elif args.action == "ansible-start":
notifyAnsibleStart(channelList, args.task_name, args.instance_id, args.pipeline_name)
elif args.action == "ansible-complete":
notifyAnsibleComplete(channelList, args.rc, args.task_name, args.instance_id, args.pipeline_name)
elif args.action == "pipeline-complete":
notifyPipelineComplete(channelList, args.rc, args.instance_id, args.pipeline_name)
Loading
Loading