1+ #!/usr/bin/env python3
2+
3+ # *****************************************************************************
4+ # Copyright (c) 2025 IBM Corporation and other Contributors.
5+ #
6+ # All rights reserved. This program and the accompanying materials
7+ # are made available under the terms of the Eclipse Public License v1.0
8+ # which accompanies this distribution, and is available at
9+ # http://www.eclipse.org/legal/epl-v10.html
10+ #
11+ # *****************************************************************************
12+
13+ from kubernetes import client , config
14+ from kubernetes .config .config_exception import ConfigException
15+ import argparse
16+ import logging
17+ import urllib3
18+ 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+
28+
29+
30+ if __name__ == "__main__" :
31+ parser = argparse .ArgumentParser ()
32+
33+ # Primary Options
34+ parser .add_argument ("--mas-instance-id" , required = True )
35+ parser .add_argument ("--mas-workspace-id" , required = True )
36+ parser .add_argument ("--log-level" , required = False , choices = ["DEBUG" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" ], default = "INFO" )
37+ parser .add_argument ("--coreapi-port" , required = False , default = 443 )
38+ parser .add_argument ("--admin-dashboard-port" , required = False , default = 443 )
39+ parser .add_argument ("--manage-api-port" , required = False , default = 443 )
40+
41+
42+ group = parser .add_mutually_exclusive_group (required = True )
43+ group .add_argument ("--initial-users-yaml-file" )
44+ group .add_argument ("--initial-users-secret-name" )
45+
46+ args , unknown = parser .parse_known_args ()
47+
48+ log_level = getattr (logging , args .log_level )
49+
50+ logger = logging .getLogger ()
51+ logger .setLevel (log_level )
52+
53+ ch = logging .StreamHandler ()
54+ ch .setLevel (log_level )
55+ chFormatter = logging .Formatter (
56+ "%(asctime)-25s %(name)-50s [%(threadName)s] %(levelname)-8s %(message)s"
57+ )
58+ ch .setFormatter (chFormatter )
59+ logger .addHandler (ch )
60+
61+ mas_instance_id = args .mas_instance_id
62+ mas_workspace_id = args .mas_workspace_id
63+ initial_users_yaml_file = args .initial_users_yaml_file
64+ initial_users_secret_name = args .initial_users_secret_name
65+ coreapi_port = args .coreapi_port
66+ admin_dashboard_port = args .admin_dashboard_port
67+ manage_api_port = args .manage_api_port
68+
69+
70+ logger .info ("Configuration:" )
71+ logger .info ("--------------" )
72+ logger .info (f"mas_instance_id: { mas_instance_id } " )
73+ logger .info (f"mas_workspace_id: { mas_workspace_id } " )
74+ logger .info (f"initial_users_yaml_file: { initial_users_yaml_file } " )
75+ logger .info (f"initial_users_secret_name: { initial_users_secret_name } " )
76+ logger .info (f"log_level: { log_level } " )
77+ logger .info (f"coreapi_port: { coreapi_port } " )
78+ logger .info (f"admin_dashboard_port: { admin_dashboard_port } " )
79+ logger .info (f"manage_api_port: { manage_api_port } " )
80+ logger .info ("" )
81+
82+ try :
83+ # Try to load in-cluster configuration
84+ config .load_incluster_config ()
85+ logger .debug ("Loaded in-cluster configuration" )
86+ except ConfigException :
87+ # If that fails, fall back to kubeconfig file
88+ config .load_kube_config ()
89+ logger .debug ("Loaded kubeconfig file" )
90+
91+
92+ 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 )
93+
94+ if initial_users_secret_name is not None :
95+
96+ logger .info (f"Loading initial_users configuration from secret { initial_users_secret_name } " )
97+
98+ session = boto3 .session .Session ()
99+ aws_sm_client = session .client (
100+ service_name = 'secretsmanager' ,
101+ )
102+ try :
103+ initial_users_secret = aws_sm_client .get_secret_value ( # pragma: allowlist secret
104+ SecretId = initial_users_secret_name
105+ )
106+ except ClientError as e :
107+ if e .response ['Error' ]['Code' ] == 'ResourceNotFoundException' :
108+ logger .info (f"Secret { initial_users_secret_name } was not found, nothing to do, exiting now." )
109+ sys .exit (0 )
110+
111+ raise Exception (f"Failed to fetch secret { initial_users_secret_name } : { str (e )} " )
112+
113+ secret_json = json .loads (initial_users_secret ['SecretString' ])
114+ initial_users = user_utils .parse_initial_users_from_aws_secret_json (secret_json )
115+ elif initial_users_yaml_file is not None :
116+ with open (initial_users_yaml_file , 'r' ) as file :
117+ initial_users = yaml .safe_load (file )
118+ else :
119+ raise Exception ("Something unexpected happened" )
120+
121+
122+ result = user_utils .create_initial_users_for_saas (initial_users )
123+
124+ # if user details were sourced from an AWS SM secret, remove the completed entries from the secret
125+ # so we don't try and resync them the next time round (and potentially undo an update made by a customer)
126+ if initial_users_secret_name is not None :
127+ has_updates = False
128+ for completed_user in result ["completed" ]:
129+ logger .info (f"Removing synced user { completed_user ['email' ]} from { initial_users_secret_name } secret" )
130+ secret_json .pop (completed_user ["email" ])
131+ has_updates = True
132+
133+ if has_updates :
134+ logger .info (f"Updating secret { initial_users_secret_name } " )
135+ try :
136+ aws_sm_client .update_secret ( # pragma: allowlist secret
137+ SecretId = initial_users_secret_name ,
138+ SecretString = json .dumps (secret_json )
139+ )
140+ except ClientError as e :
141+ raise Exception (f"Failed to update secret { initial_users_secret_name } : { str (e )} " )
142+
143+
144+ 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 } " )
0 commit comments