Skip to content

Commit 10cdebe

Browse files
authored
Let create-uber-principal command run on collection of workspaces (#2640)
## Changes Let `create-uber-principal` command run on a collection of workspaces. ### Linked issues Resolves #2605 ### Functionality - [x] modified existing command: `databricks labs ucx create-uber-principal` ### Tests - [x] manually tested - [x] added unit tests - [ ] ~added integration tests~ : Covering after #2507
1 parent 3b54920 commit 10cdebe

File tree

6 files changed

+102
-42
lines changed

6 files changed

+102
-42
lines changed

labs.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,17 @@ commands:
167167
description: AWS Profile to use for authentication
168168

169169
- name: create-uber-principal
170-
description: For azure cloud, creates a service principal and gives STORAGE BLOB READER access on all the storage account
171-
used by tables in the workspace and stores the spn info in the UCX cluster policy. For aws,
172-
it identifies all s3 buckets used by the Instance Profiles configured in the workspace.
170+
description: |
171+
For azure cloud, creates a service principal and gives `STORAGE_BLOB_READER` access on all the storage account
172+
used by tables in the workspace and stores the service principal information in the UCX cluster policy.
173+
For aws, indentify all s3 buckets used by the Instance Profiles configured in the workspace.
173174
flags:
174175
- name: subscription-id
175176
description: Subscription to scan storage account in
176177
- name: aws-profile
177178
description: AWS Profile to use for authentication
179+
- name: run-as-collection
180+
description: Run the command for the collection of workspaces with ucx installed. Default is False.
178181

179182
- name: validate-groups-membership
180183
description: Validate groups to check if the groups at account level and workspace level have different memberships

src/databricks/labs/ucx/azure/access.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def _remove_service_principal_configuration_from_workspace_warehouse_config(
373373
)
374374
raise error
375375

376-
def create_uber_principal(self, prompts: Prompts):
376+
def create_uber_principal(self, prompts: Prompts) -> None:
377377
config = self._installation.load(WorkspaceConfig)
378378
inventory_database = config.inventory_database
379379
display_name = f"unity-catalog-migration-{inventory_database}-{self._ws.get_workspace_id()}"

src/databricks/labs/ucx/cli.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,19 +313,25 @@ def create_uber_principal(
313313
w: WorkspaceClient,
314314
prompts: Prompts,
315315
ctx: WorkspaceContext | None = None,
316+
run_as_collection: bool = False,
317+
a: AccountClient | None = None,
316318
**named_parameters,
317319
):
318320
"""For azure cloud, creates a service principal and gives STORAGE BLOB READER access on all the storage account
319321
used by tables in the workspace and stores the spn info in the UCX cluster policy. For aws,
320322
it identifies all s3 buckets used by the Instance Profiles configured in the workspace.
321323
Pass subscription_id for azure and aws_profile for aws."""
322-
if not ctx:
323-
ctx = WorkspaceContext(w, named_parameters)
324-
if ctx.is_azure:
325-
return ctx.azure_resource_permissions.create_uber_principal(prompts)
326-
if ctx.is_aws:
327-
return ctx.aws_resource_permissions.create_uber_principal(prompts)
328-
raise ValueError("Unsupported cloud provider")
324+
if ctx:
325+
workspace_contexts = [ctx]
326+
else:
327+
workspace_contexts = _get_workspace_contexts(w, a, run_as_collection, **named_parameters)
328+
for workspace_context in workspace_contexts:
329+
if workspace_context.is_azure:
330+
workspace_context.azure_resource_permissions.create_uber_principal(prompts)
331+
elif workspace_context.is_aws:
332+
workspace_context.aws_resource_permissions.create_uber_principal(prompts)
333+
else:
334+
raise ValueError("Unsupported cloud provider")
329335

330336

331337
@ucx.command

src/databricks/labs/ucx/contexts/application.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ def connect_config(self) -> core.Config:
119119

120120
@cached_property
121121
def is_azure(self) -> bool:
122-
if self.is_aws:
123-
return False
124122
return self.connect_config.is_azure
125123

126124
@cached_property

src/databricks/labs/ucx/contexts/workspace_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def aws_cli_run_command(self):
132132
return run_command
133133

134134
@cached_property
135-
def aws_profile(self):
135+
def aws_profile(self) -> str:
136136
aws_profile = self.named_parameters.get("aws_profile")
137137
if not aws_profile:
138138
aws_profile = os.getenv("AWS_DEFAULT_PROFILE")

tests/unit/test_cli.py

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ def create_workspace_client_mock(workspace_id: int) -> WorkspaceClient:
8787
'token': 'bar',
8888
},
8989
'installed_workspace_ids': installed_workspace_ids,
90+
'policy_id': '01234567A8BCDEF9',
91+
# Exit Azure's `create_uber_principal` early by setting the uber service principal id
92+
# to isolate cli testing as much as possible to the cli commands and not the invoked ucx functionality.
93+
'uber_spn_id': '0123456789',
9094
}
9195
),
9296
'/Users/foo/.ucx/state.json': json.dumps(
@@ -593,30 +597,90 @@ def test_migrate_credentials_limit_aws(ws, acc_client):
593597
migrate_credentials(ws, prompts, ctx=ctx, a=acc_client)
594598

595599

596-
def test_create_master_principal_not_azure(ws):
597-
ws.config.is_azure = False
598-
ws.config.is_aws = False
600+
def test_create_uber_principal_raises_value_error_for_unsupported_cloud(ws) -> None:
601+
ctx = WorkspaceContext(ws).replace(
602+
is_azure=False,
603+
is_aws=False,
604+
)
599605
prompts = MockPrompts({})
600-
ctx = WorkspaceContext(ws)
601-
with pytest.raises(ValueError):
606+
with pytest.raises(ValueError, match="Unsupported cloud provider"):
602607
create_uber_principal(ws, prompts, ctx=ctx)
603608

604609

605-
def test_create_master_principal_no_subscription(ws):
606-
ws.config.auth_type = "azure-cli"
607-
ws.config.is_azure = True
610+
def test_create_azure_uber_principal_raises_value_error_if_subscription_id_is_missing(ws) -> None:
611+
ctx = WorkspaceContext(ws).replace(
612+
is_azure=True,
613+
is_aws=False,
614+
azure_cli_authenticated=True,
615+
)
616+
prompts = MockPrompts({"Enter a name for the uber service principal to be created": "test"})
617+
with pytest.raises(ValueError, match="Please enter subscription id to scan storage accounts in."):
618+
create_uber_principal(ws, prompts, ctx=ctx)
619+
620+
621+
def test_create_azure_uber_principal_calls_workspace_id(ws) -> None:
622+
ctx = WorkspaceContext(ws).replace(
623+
is_azure=True,
624+
is_aws=False,
625+
azure_cli_authenticated=True,
626+
azure_subscription_id="id",
627+
)
628+
prompts = MockPrompts({"Enter a name for the uber service principal to be created": "test"})
629+
630+
create_uber_principal(ws, prompts, ctx=ctx)
631+
632+
ws.get_workspace_id.assert_called_once()
633+
634+
635+
def test_create_azure_uber_principal_runs_as_collection_requests_workspace_ids(workspace_clients, acc_client) -> None:
636+
for workspace_client in workspace_clients:
637+
# Setting the auth as follows as we (currently) do not support injecting multiple workspace contexts
638+
workspace_client.config.auth_type = "azure-cli"
639+
prompts = MockPrompts({"Enter a name for the uber service principal to be created": "test"})
640+
641+
create_uber_principal(
642+
workspace_clients[0],
643+
prompts,
644+
run_as_collection=True,
645+
a=acc_client,
646+
subscription_id="test",
647+
)
648+
649+
for workspace_client in workspace_clients:
650+
workspace_client.get_workspace_id.assert_called()
651+
652+
653+
def test_create_aws_uber_principal_raises_value_error_if_aws_profile_is_missing(ws) -> None:
654+
ctx = WorkspaceContext(ws).replace(
655+
is_azure=False,
656+
is_aws=True,
657+
)
608658
prompts = MockPrompts({})
609-
ctx = WorkspaceContext(ws)
610-
with pytest.raises(ValueError):
611-
create_uber_principal(ws, prompts, ctx=ctx, subscription_id="")
659+
with pytest.raises(ValueError, match="AWS Profile is not specified. .*"):
660+
create_uber_principal(ws, prompts, ctx=ctx)
612661

613662

614-
def test_create_uber_principal(ws):
615-
ws.config.auth_type = "azure-cli"
616-
ws.config.is_azure = True
663+
def successful_aws_cli_call(_):
664+
successful_return = """
665+
{
666+
"UserId": "[email protected]",
667+
"Account": "1234",
668+
"Arn": "arn:aws:sts::1234:assumed-role/AWSVIEW/[email protected]"
669+
}
670+
"""
671+
return 0, successful_return, ""
672+
673+
674+
def test_create_aws_uber_principal_calls_dbutils_fs_mounts(ws) -> None:
675+
ctx = WorkspaceContext(ws).replace(
676+
is_azure=False,
677+
is_aws=True,
678+
aws_profile="test",
679+
aws_cli_run_command=successful_aws_cli_call,
680+
)
617681
prompts = MockPrompts({})
618-
with pytest.raises(ValueError):
619-
create_uber_principal(ws, prompts, subscription_id="12")
682+
create_uber_principal(ws, prompts, ctx=ctx)
683+
ws.dbutils.fs.mounts.assert_called_once()
620684

621685

622686
def test_migrate_locations_raises_value_error_for_unsupported_cloud_provider(ws) -> None:
@@ -669,22 +733,11 @@ def test_migrate_locations_azure_run_as_collection(workspace_clients, acc_client
669733

670734

671735
def test_migrate_locations_aws(ws, caplog) -> None:
672-
successful_return = """
673-
{
674-
"UserId": "[email protected]",
675-
"Account": "1234",
676-
"Arn": "arn:aws:sts::1234:assumed-role/AWSVIEW/[email protected]"
677-
}
678-
"""
679-
680-
def successful_call(_):
681-
return 0, successful_return, ""
682-
683736
ctx = WorkspaceContext(ws).replace(
684737
is_aws=True,
685738
is_azure=False,
686739
aws_profile="profile",
687-
aws_cli_run_command=successful_call,
740+
aws_cli_run_command=successful_aws_cli_call,
688741
)
689742

690743
migrate_locations(ws, ctx=ctx)

0 commit comments

Comments
 (0)