Skip to content

Commit abc77f0

Browse files
author
Sanjay Prabhakar
committed
Merge branch 'stable' into backuprestore-dev
2 parents 97a1d8e + e93c898 commit abc77f0

12 files changed

Lines changed: 432 additions & 456 deletions

.secrets.baseline

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2025-10-27T10:20:07Z",
6+
"generated_at": "2025-12-15T15:57:18Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -82,15 +82,15 @@
8282
"hashed_secret": "053f5ed451647be0bbb6f67b80d6726808cad97e",
8383
"is_secret": false,
8484
"is_verified": false,
85-
"line_number": 35,
85+
"line_number": 44,
8686
"type": "Secret Keyword",
8787
"verified_result": null
8888
},
8989
{
9090
"hashed_secret": "4f75456d6c1887d41ed176f7ad3e2cfff3fdfd91",
9191
"is_secret": false,
9292
"is_verified": false,
93-
"line_number": 44,
93+
"line_number": 53,
9494
"type": "Secret Keyword",
9595
"verified_result": null
9696
}

bin/mas-devops-create-initial-users-for-saas

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,18 @@
1010
#
1111
# *****************************************************************************
1212

13+
from mas.devops.users import MASUserUtils
14+
from botocore.exceptions import ClientError
15+
import boto3
16+
import sys
17+
import json
18+
import yaml
1319
from kubernetes import client, config
1420
from kubernetes.config.config_exception import ConfigException
1521
import argparse
1622
import logging
1723
import urllib3
1824
urllib3.disable_warnings()
19-
import yaml
20-
import json
21-
import sys
22-
23-
import boto3
24-
from botocore.exceptions import ClientError
25-
26-
from mas.devops.users import MASUserUtils
27-
2825

2926

3027
if __name__ == "__main__":
@@ -38,7 +35,6 @@ if __name__ == "__main__":
3835
parser.add_argument("--admin-dashboard-port", required=False, default=443)
3936
parser.add_argument("--manage-api-port", required=False, default=443)
4037

41-
4238
group = parser.add_mutually_exclusive_group(required=True)
4339
group.add_argument("--initial-users-yaml-file")
4440
group.add_argument("--initial-users-secret-name")
@@ -66,7 +62,6 @@ if __name__ == "__main__":
6662
admin_dashboard_port = args.admin_dashboard_port
6763
manage_api_port = args.manage_api_port
6864

69-
7065
logger.info("Configuration:")
7166
logger.info("--------------")
7267
logger.info(f"mas_instance_id: {mas_instance_id}")
@@ -88,7 +83,6 @@ if __name__ == "__main__":
8883
config.load_kube_config()
8984
logger.debug("Loaded kubeconfig file")
9085

91-
9286
user_utils = MASUserUtils(mas_instance_id, mas_workspace_id, client.api_client.ApiClient(), coreapi_port=coreapi_port, admin_dashboard_port=admin_dashboard_port, manage_api_port=manage_api_port)
9387

9488
if initial_users_secret_name is not None:
@@ -100,7 +94,7 @@ if __name__ == "__main__":
10094
service_name='secretsmanager',
10195
)
10296
try:
103-
initial_users_secret = aws_sm_client.get_secret_value( # pragma: allowlist secret
97+
initial_users_secret = aws_sm_client.get_secret_value( # pragma: allowlist secret
10498
SecretId=initial_users_secret_name
10599
)
106100
except ClientError as e:
@@ -109,16 +103,15 @@ if __name__ == "__main__":
109103
sys.exit(0)
110104

111105
raise Exception(f"Failed to fetch secret {initial_users_secret_name}: {str(e)}")
112-
106+
113107
secret_json = json.loads(initial_users_secret['SecretString'])
114108
initial_users = user_utils.parse_initial_users_from_aws_secret_json(secret_json)
115109
elif initial_users_yaml_file is not None:
116110
with open(initial_users_yaml_file, 'r') as file:
117111
initial_users = yaml.safe_load(file)
118112
else:
119113
raise Exception("Something unexpected happened")
120-
121-
114+
122115
result = user_utils.create_initial_users_for_saas(initial_users)
123116

124117
# if user details were sourced from an AWS SM secret, remove the completed entries from the secret
@@ -133,14 +126,13 @@ if __name__ == "__main__":
133126
if has_updates:
134127
logger.info(f"Updating secret {initial_users_secret_name}")
135128
try:
136-
aws_sm_client.update_secret( # pragma: allowlist secret
129+
aws_sm_client.update_secret( # pragma: allowlist secret
137130
SecretId=initial_users_secret_name,
138131
SecretString=json.dumps(secret_json)
139132
)
140133
except ClientError as e:
141134
raise Exception(f"Failed to update secret {initial_users_secret_name}: {str(e)}")
142-
143135

144136
if len(result["failed"]) > 0:
145-
failed_user_ids = list(map(lambda u : u["email"], result["failed"]))
146-
raise Exception(f"Sync failed for the following user IDs {failed_user_ids}")
137+
failed_user_ids = list(map(lambda u: u["email"], result["failed"]))
138+
raise Exception(f"Sync failed for the following user IDs {failed_user_ids}")

bin/mas-devops-notify-slack

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,27 @@ import sys
1616
from mas.devops.slack import SlackUtil
1717

1818

19-
def notifyProvisionFyre(channel: str, rc: int) -> bool:
20-
name = os.getenv("CLUSTER_NAME", None)
21-
if name is None:
19+
def _getClusterName() -> str:
20+
name = os.getenv("CLUSTER_NAME", "")
21+
if name == "":
2222
print("CLUSTER_NAME env var must be set")
2323
sys.exit(1)
24+
return name
2425

25-
# Support optional metadata from standard IBM CD Toolchains environment variables
26-
toolchainLink = ""
26+
27+
def _getToolchainLink() -> str:
2728
toolchainUrl = os.getenv("TOOLCHAIN_PIPELINERUN_URL", None)
2829
toolchainTriggerName = os.getenv("TOOLCHAIN_TRIGGER_NAME", None)
2930
if toolchainUrl is not None and toolchainTriggerName is not None:
30-
toolchainLink = f" | <{toolchainUrl}|Pipeline Run>"
31+
toolchainLink = f"<{toolchainUrl}|{toolchainTriggerName}>"
32+
return toolchainLink
33+
return ""
34+
35+
36+
def notifyProvisionFyre(channels: list[str], rc: int, additionalMsg: str = None) -> bool:
37+
"""Send Slack notification about Fyre OCP cluster provisioning status."""
38+
name = _getClusterName()
39+
toolchainLink = _getToolchainLink()
3140

3241
if rc == 0:
3342
url = os.getenv("OCP_CONSOLE_URL", None)
@@ -44,13 +53,47 @@ def notifyProvisionFyre(channel: str, rc: int) -> bool:
4453
SlackUtil.buildSection(f"- Username: `{username}`\n- Password: `{password}`"),
4554
SlackUtil.buildSection(f"<https://beta.fyre.ibm.com/development/vms|Fyre Dashboard>{toolchainLink}")
4655
]
56+
if additionalMsg is not None:
57+
message.append(SlackUtil.buildSection(additionalMsg))
4758
else:
4859
message = [
4960
SlackUtil.buildHeader(f":glyph-fail: Your IBM DevIT Fyre OCP cluster ({name}) failed to deploy"),
5061
SlackUtil.buildSection(f"<https://beta.fyre.ibm.com/development/vms|Fyre Dashboard>{toolchainLink}")
5162
]
5263

53-
response = SlackUtil.postMessageBlocks(channel, message)
64+
response = SlackUtil.postMessageBlocks(channels, message)
65+
if isinstance(response, list):
66+
return all([res.data.get("ok", False) for res in response])
67+
return response.data.get("ok", False)
68+
69+
70+
def notifyProvisionRoks(channels: list[str], rc: int, additionalMsg: str = None) -> bool:
71+
"""Send Slack notification about ROKS cluster provisioning status."""
72+
name = _getClusterName()
73+
toolchainLink = _getToolchainLink()
74+
75+
if rc == 0:
76+
url = os.getenv("OCP_CONSOLE_URL", None)
77+
if url is None:
78+
print("OCP_CONSOLE_URL env var must be set")
79+
sys.exit(1)
80+
81+
message = [
82+
SlackUtil.buildHeader(f":glyph-ok: Your IBM Cloud ROKS cluster ({name}) is ready"),
83+
SlackUtil.buildSection(f"{url}"),
84+
SlackUtil.buildSection(f"<https://cloud.ibm.com/kubernetes/clusters|IBM Cloud Dashboard>{toolchainLink}")
85+
]
86+
if additionalMsg is not None:
87+
message.append(SlackUtil.buildSection(additionalMsg))
88+
else:
89+
message = [
90+
SlackUtil.buildHeader(f":glyph-fail: Your IBM Cloud ROKS cluster ({name}) failed to deploy"),
91+
SlackUtil.buildSection(f"<https://cloud.ibm.com/kubernetes/clusters|IBM Cloud Dashboard>{toolchainLink}")
92+
]
93+
94+
response = SlackUtil.postMessageBlocks(channels, message)
95+
if isinstance(response, list):
96+
return all([res.data.get("ok", False) for res in response])
5497
return response.data.get("ok", False)
5598

5699

@@ -61,13 +104,20 @@ if __name__ == "__main__":
61104
if SLACK_TOKEN == "" or SLACK_CHANNEL == "":
62105
sys.exit(0)
63106

107+
# Parse comma-separated channel list
108+
channelList = [ch.strip() for ch in SLACK_CHANNEL.split(",")]
109+
64110
# Initialize the properties we need
65111
parser = argparse.ArgumentParser()
66112

67113
# Primary Options
68114
parser.add_argument("--action", required=True)
69115
parser.add_argument("--rc", required=True, type=int)
116+
parser.add_argument("--msg", required=False, default=None)
117+
70118
args, unknown = parser.parse_known_args()
71119

72120
if args.action == "ocp-provision-fyre":
73-
notifyProvisionFyre(SLACK_CHANNEL, args.rc)
121+
notifyProvisionFyre(channelList, args.rc, args.msg)
122+
elif args.action == "ocp-provision-roks":
123+
notifyProvisionRoks(channelList, args.rc, args.msg)

bin/mas-devops-saas-job-cleaner

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ if __name__ == "__main__":
4646
ch.setFormatter(chFormatter)
4747
logger.addHandler(ch)
4848

49-
5049
limit = args.limit
5150
label = args.label
5251
dry_run = args.dry_run

src/mas/devops/ocp.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,68 @@ def execInPod(core_v1_api: client.CoreV1Api, pod_name: str, namespace, command:
390390
logger.debug(f"stdout: \n----------------------------------------------------------------\n{stdout}\n----------------------------------------------------------------\n")
391391

392392
return stdout
393+
394+
395+
def updateGlobalPullSecret(dynClient: DynamicClient, registryUrl: str, username: str, password: str) -> dict:
396+
"""
397+
Update the global pull secret in openshift-config namespace with new registry credentials.
398+
399+
Args:
400+
dynClient: OpenShift Dynamic Client
401+
registryUrl: Registry URL (e.g., "myregistry.com:5000")
402+
username: Registry username
403+
password: Registry password
404+
405+
Returns:
406+
dict: Updated secret information
407+
"""
408+
import json
409+
import base64
410+
411+
logger.info(f"Updating global pull secret with credentials for {registryUrl}")
412+
413+
# Get the existing pull secret
414+
secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret")
415+
try:
416+
pullSecret = secretsAPI.get(name="pull-secret", namespace="openshift-config")
417+
except NotFoundError:
418+
raise Exception("Global pull-secret not found in openshift-config namespace")
419+
420+
# Convert to dict to allow modifications
421+
secretDict = pullSecret.to_dict()
422+
423+
# Decode the existing dockerconfigjson
424+
dockerConfigJson = secretDict['data'].get(".dockerconfigjson", "")
425+
dockerConfig = json.loads(base64.b64decode(dockerConfigJson).decode('utf-8'))
426+
427+
# Create auth string (username:password base64 encoded)
428+
authString = base64.b64encode(f"{username}:{password}".encode('utf-8')).decode('utf-8')
429+
430+
# Add or update the registry credentials
431+
if "auths" not in dockerConfig:
432+
dockerConfig["auths"] = {}
433+
434+
dockerConfig["auths"][registryUrl] = {
435+
"username": username,
436+
"password": password,
437+
"email": username,
438+
"auth": authString
439+
}
440+
441+
# Encode back to base64
442+
updatedDockerConfig = base64.b64encode(json.dumps(dockerConfig).encode('utf-8')).decode('utf-8')
443+
444+
# Update the secret dict
445+
secretDict['data'][".dockerconfigjson"] = updatedDockerConfig
446+
447+
# Apply the updated secret
448+
updatedSecret = secretsAPI.apply(body=secretDict, namespace="openshift-config")
449+
450+
logger.info(f"Successfully updated global pull secret with credentials for {registryUrl}")
451+
452+
return {
453+
"name": updatedSecret.metadata.name,
454+
"namespace": updatedSecret.metadata.namespace,
455+
"registry": registryUrl,
456+
"changed": True
457+
}

0 commit comments

Comments
 (0)