Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[flake8]
# These rules are ignored
# - E501 line too long
ignore = E501
max-line-length = 120
# - E203 whitespace before ':' (conflicts with Black formatting)
# - D100-D400 docstring style warnings (legacy code, will be addressed separately)
extend-ignore = E501, E203, D
max-line-length = 160
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ kubectl.exe
/build
/.vscode
/site
/.bob/.bob-errors
/.bob/notes
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
default_language_version:
python: python
repos:
- repo: https://github.com/hhatto/autopep8
rev: v2.3.2
- repo: https://github.com/psf/black
rev: 26.5.1
hooks:
- id: autopep8
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 7.3.0
hooks:
Expand Down
46 changes: 41 additions & 5 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "^.secrets.baseline$",
"lines": null
},
"generated_at": "2026-05-21T00:57:00Z",
"generated_at": "2026-06-17T12:31:04Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -87,6 +87,16 @@
"verified_result": null
}
],
"bin/mas-devops-create-initial-users-for-saas": [
{
"hashed_secret": "28ed3a797da3c48c309a4ef792147f3c56cfec40",
"is_secret": false,
"is_verified": false,
"line_number": 116,
"type": "Secret Keyword",
"verified_result": null
}
],
"bin/mas-devops-notify-slack": [
{
"hashed_secret": "053f5ed451647be0bbb6f67b80d6726808cad97e",
Expand Down Expand Up @@ -158,7 +168,7 @@
"hashed_secret": "4dfd3a58b4820476afe7efa2e2c52b267eec876a",
"is_secret": false,
"is_verified": false,
"line_number": 753,
"line_number": 688,
"type": "Secret Keyword",
"verified_result": null
}
Expand All @@ -168,7 +178,7 @@
"hashed_secret": "a4b48a81cdab1e1a5dd37907d6c85ca1c61ddc7c",
"is_secret": false,
"is_verified": false,
"line_number": 290,
"line_number": 245,
"type": "Secret Keyword",
"verified_result": null
}
Expand All @@ -178,15 +188,41 @@
"hashed_secret": "94f5ed592906089c107208b29e178ddf1f9f5143",
"is_secret": false,
"is_verified": false,
"line_number": 42,
"line_number": 40,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "a9410d9785f49750b9f8672794fc288558c1611c",
"is_secret": false,
"is_verified": false,
"line_number": 55,
"line_number": 60,
"type": "Secret Keyword",
"verified_result": null
}
],
"test/src/test_users.py": [
{
"hashed_secret": "74ba31d41223751c75cc0a453dd7df04889bdc72",
"is_secret": false,
"is_verified": false,
"line_number": 143,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "2edced2e2f44a016a5b2e5ce25fe704e62cbb2e7",
"is_secret": false,
"is_verified": false,
"line_number": 150,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "f66f0353de50f6990a5d761b00268056fa80f95f",
"is_secret": false,
"is_verified": false,
"line_number": 1945,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mas.devops
===============================================================================
[![Code style: PEP8](https://img.shields.io/badge/code%20style-PEP--8-blue.svg)](https://peps.python.org/pep-0008/)
[![Code Style: Black](https://img.shields.io/badge/Code%20Style-Black-000000.svg)](https://github.com/psf/black)
[![Flake8: checked](https://img.shields.io/badge/flake8-checked-blueviolet)](https://flake8.pycqa.org/en/latest/)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ibm-mas/python-devops/python-release.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/mas.devops)](https://pypi.org/project/mas-devops)
Expand Down Expand Up @@ -31,7 +31,7 @@ installOpenShiftPipelines(dynamicClient)

# Create the pipelines namespace and install the MAS tekton definitions
createNamespace(dynamicClient, pipelinesNamespace)
updateTektonDefinitions(pipelinesNamespace, "/mascli/templates/ibm-mas-tekton.yaml")
updateTektonDefinitions(dynamicClient, pipelinesNamespace, "/mascli/templates/ibm-mas-tekton.yaml")

# Launch the upgrade pipeline and print the URL to view the pipeline run
pipelineURL = launchUpgradePipeline(self.dynamicClient, instanceId)
Expand Down Expand Up @@ -73,7 +73,7 @@ mas-devops-create-initial-users-for-saas \
--manage-api-port 8443 \
--coreapi-port 8444 \
--admin-dashboard-port 8445


mas-devops-create-initial-users-for-saas \
--mas-instance-id tgk01 \
Expand Down
65 changes: 42 additions & 23 deletions bin/mas-devops-apply-preinstall-rbac-for-saas
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,69 @@ import sys
import argparse
import logging
import urllib3

urllib3.disable_warnings()


if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Apply Pre-Install MAS RBAC')
parser = argparse.ArgumentParser(description="Apply Pre-Install MAS RBAC")

parser.add_argument("--mas-instance-id", required=True, help="MAS Instance ID")
parser.add_argument("--mas-version", required=True, help="MAS Version (e.g., 9.2)")
parser.add_argument("--admin-mode", required=False, default="namespaced",
choices=["cluster", "namespaced", "minimal"],
help="Admin mode: cluster, namespaced, or minimal")
parser.add_argument("--selected-apps", required=False, default="core",
help="Comma-separated list of apps (e.g., core,manage,iot)")
parser.add_argument("--rbac-root-dir", required=False, default="/opt/app-root/rbac",
help="Root directory containing RBAC manifests")
parser.add_argument("--log-level", required=False,
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="INFO")

parser.add_argument(
"--admin-mode",
required=False,
default="namespaced",
choices=["cluster", "namespaced", "minimal"],
help="Admin mode: cluster, namespaced, or minimal",
)
parser.add_argument(
"--selected-apps",
required=False,
default="core",
help="Comma-separated list of apps (e.g., core,manage,iot)",
)
parser.add_argument(
"--rbac-root-dir",
required=False,
default="/opt/app-root/rbac",
help="Root directory containing RBAC manifests",
)
parser.add_argument(
"--log-level",
required=False,
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="INFO",
)

args = parser.parse_args()

# Setup logging
log_level = getattr(logging, args.log_level)
logger = logging.getLogger()
logger.setLevel(log_level)

ch = logging.StreamHandler()
ch.setLevel(log_level)
chFormatter = logging.Formatter(
"%(asctime)-25s %(name)-50s %(levelname)-8s %(message)s"
)
ch.setFormatter(chFormatter)
logger.addHandler(ch)

mas_instance_id = args.mas_instance_id
mas_version = ".".join(args.mas_version.split(".")[:2])
admin_mode = args.admin_mode
selected_apps_str = args.selected_apps
rbac_root_dir = args.rbac_root_dir

# Parse selected apps
selected_apps = None
if selected_apps_str:
selected_apps = [app.strip() for app in selected_apps_str.split(',') if app.strip()]

selected_apps = [
app.strip() for app in selected_apps_str.split(",") if app.strip()
]

logger.info("Configuration:")
logger.info("--------------")
logger.info(f"mas_instance_id: {mas_instance_id}")
Expand All @@ -72,7 +90,7 @@ if __name__ == "__main__":
logger.info(f"rbac_root_dir: {rbac_root_dir}")
logger.info(f"log_level: {log_level}")
logger.info("")

try:
# Try to load in-cluster configuration
config.load_incluster_config()
Expand All @@ -81,7 +99,7 @@ if __name__ == "__main__":
# If that fails, fall back to kubeconfig file
config.load_kube_config()
logger.debug("Loaded kubeconfig file")

try:
dynClient = DynamicClient(client.api_client.ApiClient())
applyPreInstallMASRBAC(
Expand All @@ -90,12 +108,13 @@ if __name__ == "__main__":
masInstanceId=mas_instance_id,
adminMode=admin_mode,
selectedApps=selected_apps,
rbacRootDir=rbac_root_dir
rbacRootDir=rbac_root_dir,
)
logger.info("Pre-Install MAS RBAC applied successfully")
sys.exit(0)
except Exception as e:
logger.error(f"Error applying Pre-Install MAS RBAC: {e}")
import traceback

traceback.print_exc()
sys.exit(1)
sys.exit(1)
54 changes: 40 additions & 14 deletions bin/mas-devops-create-initial-users-for-saas
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ from kubernetes.config.config_exception import ConfigException
import argparse
import logging
import urllib3

urllib3.disable_warnings()


Expand All @@ -30,7 +31,12 @@ if __name__ == "__main__":
# Primary Options
parser.add_argument("--mas-instance-id", required=True)
parser.add_argument("--mas-workspace-id", required=True)
parser.add_argument("--log-level", required=False, choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO")
parser.add_argument(
"--log-level",
required=False,
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="INFO",
)
parser.add_argument("--coreapi-port", required=False, default=443)
parser.add_argument("--admin-dashboard-port", required=False, default=443)
parser.add_argument("--manage-api-port", required=False, default=443)
Expand Down Expand Up @@ -86,31 +92,47 @@ if __name__ == "__main__":
config.load_kube_config()
logger.debug("Loaded kubeconfig file")

user_utils = MASUserUtils(mas_instance_id, mas_workspace_id, client.api_client.ApiClient(), mas_version, coreapi_port=coreapi_port, admin_dashboard_port=admin_dashboard_port, manage_api_port=manage_api_port)
user_utils = MASUserUtils(
mas_instance_id,
mas_workspace_id,
client.api_client.ApiClient(),
mas_version,
coreapi_port=coreapi_port,
admin_dashboard_port=admin_dashboard_port,
manage_api_port=manage_api_port,
)

if initial_users_secret_name is not None:

logger.info(f"Loading initial_users configuration from secret {initial_users_secret_name}")
logger.info(
f"Loading initial_users configuration from secret {initial_users_secret_name}"
)

session = boto3.session.Session()
aws_sm_client = session.client(
service_name='secretsmanager',
service_name="secretsmanager",
)
try:
initial_users_secret = aws_sm_client.get_secret_value( # pragma: allowlist secret
SecretId=initial_users_secret_name
initial_users_secret = (
aws_sm_client.get_secret_value( # pragma: allowlist secret
SecretId=initial_users_secret_name
)
)
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
logger.info(f"Secret {initial_users_secret_name} was not found, nothing to do, exiting now.")
if e.response["Error"]["Code"] == "ResourceNotFoundException":
logger.info(
f"Secret {initial_users_secret_name} was not found, nothing to do, exiting now."
)
sys.exit(0)

raise Exception(f"Failed to fetch secret {initial_users_secret_name}: {str(e)}")
raise Exception(
f"Failed to fetch secret {initial_users_secret_name}: {str(e)}"
)

secret_json = json.loads(initial_users_secret['SecretString'])
secret_json = json.loads(initial_users_secret["SecretString"])
initial_users = user_utils.parse_initial_users_from_aws_secret_json(secret_json)
elif initial_users_yaml_file is not None:
with open(initial_users_yaml_file, 'r') as file:
with open(initial_users_yaml_file, "r") as file:
initial_users = yaml.safe_load(file)
else:
raise Exception("Something unexpected happened")
Expand All @@ -122,7 +144,9 @@ if __name__ == "__main__":
if initial_users_secret_name is not None:
has_updates = False
for completed_user in result["completed"]:
logger.info(f"Removing synced user {completed_user['email']} from {initial_users_secret_name} secret")
logger.info(
f"Removing synced user {completed_user['email']} from {initial_users_secret_name} secret"
)
secret_json.pop(completed_user["email"])
has_updates = True

Expand All @@ -131,10 +155,12 @@ if __name__ == "__main__":
try:
aws_sm_client.update_secret( # pragma: allowlist secret
SecretId=initial_users_secret_name,
SecretString=json.dumps(secret_json)
SecretString=json.dumps(secret_json),
)
except ClientError as e:
raise Exception(f"Failed to update secret {initial_users_secret_name}: {str(e)}")
raise Exception(
f"Failed to update secret {initial_users_secret_name}: {str(e)}"
)

if len(result["failed"]) > 0:
failed_user_ids = list(map(lambda u: u["email"], result["failed"]))
Expand Down
Loading
Loading