Skip to content

Commit d113e3e

Browse files
authored
Let migrate-locations command run as collection (#2652)
## Changes Let `validate-external-locations` command to run as collection ### Linked issues Resolves #2608 ### Functionality - [x] modified existing command: `databricks labs ucx migrate-locations` ### Tests - [x] manually tested - [x] added unit tests - [ ] ~added integration tests~ : Covering after #2507
1 parent bef3afd commit d113e3e

File tree

5 files changed

+89
-11
lines changed

5 files changed

+89
-11
lines changed

labs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ commands:
208208
description: Subscription to scan storage account in
209209
- name: aws-profile
210210
description: AWS Profile to use for authentication
211+
- name: run-as-collection
212+
description: Run the command for the collection of workspaces with ucx installed. Default is False.
211213

212214
- name: create-catalogs-schemas
213215
description: Create UC external catalogs and schemas based on the destinations created from create_table_mapping command.

src/databricks/labs/ucx/aws/locations.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(
2727
self._aws_resource_permissions = aws_resource_permissions
2828
self._principal_acl = principal_acl
2929

30-
def run(self):
30+
def run(self) -> None:
3131
"""
3232
For each path find out the role that has access to it
3333
Find out the credential that is pointing to this path
@@ -36,7 +36,10 @@ def run(self):
3636
credential_dict = self._get_existing_credentials_dict()
3737
external_locations = self._external_locations.snapshot()
3838
existing_external_locations = self._ws.external_locations.list()
39-
existing_paths = [external_location.url for external_location in existing_external_locations]
39+
existing_paths = []
40+
for external_location in existing_external_locations:
41+
if external_location.url is not None:
42+
existing_paths.append(external_location.url)
4043
compatible_roles = self._aws_resource_permissions.load_uc_compatible_roles()
4144
missing_paths = self._identify_missing_external_locations(external_locations, existing_paths, compatible_roles)
4245
for path, role_arn in missing_paths:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def _filter_unsupported_location(self, location_urls: list[str]) -> list[str]:
182182
logger.warning(f"Skip unsupported location: {url}")
183183
return supported_urls
184184

185-
def run(self):
185+
def run(self) -> list[str]:
186186
# list missing external locations in UC
187187
_, missing_locations = self._hms_locations.match_table_external_locations()
188188
# Extract the location URLs from the missing locations

src/databricks/labs/ucx/cli.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -428,17 +428,27 @@ def migrate_credentials(
428428

429429

430430
@ucx.command
431-
def migrate_locations(w: WorkspaceClient, ctx: WorkspaceContext | None = None, **named_parameters):
431+
def migrate_locations(
432+
w: WorkspaceClient,
433+
ctx: WorkspaceContext | None = None,
434+
run_as_collection: bool = False,
435+
a: AccountClient | None = None,
436+
**named_parameters,
437+
):
432438
"""This command creates UC external locations. The candidate locations to be created are extracted from
433439
guess_external_locations task in the assessment job. You can run validate_external_locations command to check
434440
the candidate locations. Please make sure the credentials haven migrated before running this command. The command
435441
will only create the locations that have corresponded UC Storage Credentials.
436442
"""
437-
if not ctx:
438-
ctx = WorkspaceContext(w, named_parameters)
439-
if ctx.is_azure or ctx.is_aws:
440-
return ctx.external_locations_migration.run()
441-
raise ValueError("Unsupported cloud provider")
443+
if ctx:
444+
workspace_contexts = [ctx]
445+
else:
446+
workspace_contexts = _get_workspace_contexts(w, a, run_as_collection, **named_parameters)
447+
for workspace_context in workspace_contexts:
448+
if workspace_context.is_azure or workspace_context.is_aws:
449+
workspace_context.external_locations_migration.run()
450+
else:
451+
raise ValueError("Unsupported cloud provider")
442452

443453

444454
@ucx.command

tests/unit/test_cli.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,13 @@ def test_create_uber_principal(ws):
584584
create_uber_principal(ws, prompts, subscription_id="12")
585585

586586

587-
def test_migrate_locations_azure(ws):
587+
def test_migrate_locations_raises_value_error_for_unsupported_cloud_provider(ws) -> None:
588+
ctx = WorkspaceContext(ws).replace(is_azure=False, is_aws=False)
589+
with pytest.raises(ValueError, match="Unsupported cloud provider"):
590+
migrate_locations(ws, ctx=ctx)
591+
592+
593+
def test_migrate_locations_azure(ws) -> None:
588594
azurerm = create_autospec(AzureResources)
589595
ctx = WorkspaceContext(ws).replace(
590596
is_azure=True,
@@ -593,12 +599,41 @@ def test_migrate_locations_azure(ws):
593599
azure_subscription_id='test',
594600
azure_resources=azurerm,
595601
)
602+
596603
migrate_locations(ws, ctx=ctx)
604+
597605
ws.external_locations.list.assert_called()
598606
azurerm.storage_accounts.assert_called()
599607

600608

601-
def test_migrate_locations_aws(ws, caplog):
609+
@pytest.mark.xfail(reason="Currently not supported in unit tests see TODO")
610+
def test_migrate_locations_azure_run_as_collection(workspace_clients, acc_client) -> None:
611+
"""Test migrate locations for a collection of workspaces
612+
613+
The "run as collection" test should run the same as the test `test_migrate_locations_azure`, but the assert should
614+
be called on **all** workspace clients.
615+
"""
616+
for workspace_client in workspace_clients:
617+
# Setting the auth as follows as we (currently) do not support injecting multiple workspace contexts
618+
workspace_client.config.is_aws = False
619+
workspace_client.config.auth_type = "azure-cli"
620+
621+
# TODO: Migrate locations fails in unit testing as we currently not support injecting multiple workspace contexts
622+
# thus we cannot mock azure-cli login for multiple workspaces.
623+
# If we support this in the future, the `pytest.raises` can be removed and the test should pass
624+
with pytest.raises(OSError, match=".*The provided request must include a 'scope' input parameter.*"):
625+
migrate_locations(
626+
workspace_clients[0],
627+
run_as_collection=True,
628+
a=acc_client,
629+
subscription_id="test",
630+
)
631+
632+
for workspace_client in workspace_clients:
633+
workspace_client.external_locations.list.assert_called()
634+
635+
636+
def test_migrate_locations_aws(ws, caplog) -> None:
602637
successful_return = """
603638
{
604639
"UserId": "[email protected]",
@@ -616,10 +651,38 @@ def successful_call(_):
616651
aws_profile="profile",
617652
aws_cli_run_command=successful_call,
618653
)
654+
619655
migrate_locations(ws, ctx=ctx)
656+
620657
ws.external_locations.list.assert_called()
621658

622659

660+
@pytest.mark.xfail(reason="Currently not supported in unit tests see TODO")
661+
def test_migrate_locations_run_as_collection(workspace_clients, acc_client) -> None:
662+
"""Test migrate locations for a collection of workspaces
663+
664+
The "run as collection" test should run the same as the test `test_migrate_locations_aws`, but the assert should
665+
be called on **all** workspace clients.
666+
"""
667+
for workspace_client in workspace_clients:
668+
# Setting the auth as follows as we (currently) do not support injecting multiple workspace contexts
669+
workspace_client.config.is_azure = False
670+
671+
# TODO: Migrate locations fails in unit testing as we currently not support injecting multiple workspace contexts
672+
# thus we cannot mock AWS login for multiple workspaces.
673+
# If we support this in the future, the `pytest.raises` can be removed and the test should pass
674+
with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'get'"):
675+
migrate_locations(
676+
workspace_clients[0],
677+
run_as_collection=True,
678+
a=acc_client,
679+
aws_profile="profile",
680+
)
681+
682+
for workspace_client in workspace_clients:
683+
workspace_client.external_locations.list.assert_called()
684+
685+
623686
def test_migrate_locations_gcp(ws):
624687
ctx = WorkspaceContext(ws).replace(is_aws=False, is_azure=False)
625688
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)