From 64993c08a11bb257f12c7fa4f46249399f933767 Mon Sep 17 00:00:00 2001 From: Noel Gomez Date: Sun, 3 May 2026 10:16:45 -0700 Subject: [PATCH 1/3] improve error msgs during apply --- snowcap/blueprint.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/snowcap/blueprint.py b/snowcap/blueprint.py index a3ba8a8..34c14c0 100644 --- a/snowcap/blueprint.py +++ b/snowcap/blueprint.py @@ -1711,11 +1711,31 @@ def process_commands(commands, roles, available_roles): # Check for missing roles upfront (filter out empty/invalid roles) missing_roles = {r for r in roles if str(r)} - set(available_roles) if missing_roles: - missing_list = ", ".join(sorted(str(r) for r in missing_roles)) + # Build a mapping of missing role -> changes that require it + role_to_changes: dict[str, list[str]] = {} + for cmd in commands: + role = cmd["role"] + if role in missing_roles: + role_str = str(role) + if role_str not in role_to_changes: + role_to_changes[role_str] = [] + change = cmd["change"] + role_to_changes[role_str].append(f"{change.urn.fqn}") + + # Build detailed error message + details = [] + for role in sorted(role_to_changes.keys()): + changes = role_to_changes[role] + if len(changes) == 1: + details.append(f" - {role}: required for {changes[0]}") + else: + details.append(f" - {role}: required for {len(changes)} changes including {changes[0]}") + raise MissingPrivilegeException( - f"The following roles are required but not available to your user: {missing_list}\n" - f" Grant the missing roles to your user:\n" - + "\n".join(f" GRANT ROLE {role} TO USER your_user;" for role in sorted(missing_roles, key=str)) + f"The following roles are required but not available to your user:\n" + + "\n".join(details) + + "\n\n Grant the missing roles to your user:\n" + + "\n".join(f" GRANT ROLE {role} TO USER your_user;" for role in sorted(role_to_changes.keys())) ) # Map changes to their levels (default to 0 if not in self._levels) From b5e826171b192b648eb7d836daf115c01d36c584 Mon Sep 17 00:00:00 2001 From: Noel Gomez Date: Sun, 3 May 2026 10:29:25 -0700 Subject: [PATCH 2/3] grant roles before apply --- snowcap/blueprint.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/snowcap/blueprint.py b/snowcap/blueprint.py index 34c14c0..bd1f9a9 100644 --- a/snowcap/blueprint.py +++ b/snowcap/blueprint.py @@ -2030,12 +2030,17 @@ def compile_plan_to_sql( sql_commands_per_change = [] available_roles = session_ctx["available_roles"].copy() default_role = session_ctx["role"] + current_user = ResourceName(session_ctx["user"]) for change in plan: if isinstance(change, CreateResource): if change.urn.resource_type == ResourceType.ROLE: available_roles.append(ResourceName(change.after["name"])) elif change.urn.resource_type == ResourceType.ROLE_GRANT: - if change.after["to_role"] in available_roles: + # Handle role grants to another role that we already have + if change.after.get("to_role") and change.after["to_role"] in available_roles: + available_roles.append(ResourceName(change.after["role"])) + # Handle role grants to the current user + elif change.after.get("to_user") and ResourceName(change.after["to_user"]) == current_user: available_roles.append(ResourceName(change.after["role"])) for change in plan: role, commands = sql_commands_for_change(change, available_roles, default_role) From 23a7706e27bd1f2ec9dc6ffa56ce9353e7e5bb58 Mon Sep 17 00:00:00 2001 From: Noel Gomez Date: Sun, 3 May 2026 11:03:49 -0700 Subject: [PATCH 3/3] fix linting error and test issue --- snowcap/blueprint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/snowcap/blueprint.py b/snowcap/blueprint.py index bd1f9a9..e15ebde 100644 --- a/snowcap/blueprint.py +++ b/snowcap/blueprint.py @@ -1732,7 +1732,7 @@ def process_commands(commands, roles, available_roles): details.append(f" - {role}: required for {len(changes)} changes including {changes[0]}") raise MissingPrivilegeException( - f"The following roles are required but not available to your user:\n" + "The following roles are required but not available to your user:\n" + "\n".join(details) + "\n\n Grant the missing roles to your user:\n" + "\n".join(f" GRANT ROLE {role} TO USER your_user;" for role in sorted(role_to_changes.keys())) @@ -2030,7 +2030,7 @@ def compile_plan_to_sql( sql_commands_per_change = [] available_roles = session_ctx["available_roles"].copy() default_role = session_ctx["role"] - current_user = ResourceName(session_ctx["user"]) + current_user = ResourceName(session_ctx.get("user", "")) if session_ctx.get("user") else None for change in plan: if isinstance(change, CreateResource): if change.urn.resource_type == ResourceType.ROLE: @@ -2040,7 +2040,7 @@ def compile_plan_to_sql( if change.after.get("to_role") and change.after["to_role"] in available_roles: available_roles.append(ResourceName(change.after["role"])) # Handle role grants to the current user - elif change.after.get("to_user") and ResourceName(change.after["to_user"]) == current_user: + elif current_user and change.after.get("to_user") and ResourceName(change.after["to_user"]) == current_user: available_roles.append(ResourceName(change.after["role"])) for change in plan: role, commands = sql_commands_for_change(change, available_roles, default_role)