Skip to content

Commit 515b964

Browse files
committed
...
1 parent 17c2746 commit 515b964

File tree

2 files changed

+52
-32
lines changed

2 files changed

+52
-32
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,9 @@ def test_creating_notebook_on_behalf_of_ephemeral_principal(make_notebook):
338338
assert notebook.exists()
339339
```
340340

341-
See also [`acc`](#acc-fixture), [`ws`](#ws-fixture), [`make_random`](#make_random-fixture), [`env_or_skip`](#env_or_skip-fixture), [`log_account_link`](#log_account_link-fixture).
341+
This fixture currently doesn't work with Databricks Metadata Service authentication on Azure Databricks.
342+
343+
See also [`acc`](#acc-fixture), [`ws`](#ws-fixture), [`make_random`](#make_random-fixture), [`env_or_skip`](#env_or_skip-fixture), [`log_account_link`](#log_account_link-fixture), [`is_in_debug`](#is_in_debug-fixture).
342344

343345

344346
[[back to top](#python-testing-for-databricks)]
@@ -1259,7 +1261,7 @@ Returns true if the test is running from a debugger in IDE, otherwise false.
12591261
The following IDE are supported: IntelliJ IDEA (including Community Edition),
12601262
PyCharm (including Community Edition), and Visual Studio Code.
12611263

1262-
See also [`debug_env`](#debug_env-fixture), [`env_or_skip`](#env_or_skip-fixture).
1264+
See also [`debug_env`](#debug_env-fixture), [`env_or_skip`](#env_or_skip-fixture), [`make_run_as`](#make_run_as-fixture).
12631265

12641266

12651267
[[back to top](#python-testing-for-databricks)]

src/databricks/labs/pytester/fixtures/iam.py

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from collections.abc import Callable, Generator, Iterable
44
from datetime import timedelta
55

6+
import pytest
7+
from pytest import fixture
68
from databricks.sdk.credentials_provider import OAuthCredentialsProvider, OauthCredentialsStrategy
79
from databricks.sdk.oauth import ClientCredentials, Token
8-
from pytest import fixture
10+
from databricks.sdk.service.oauth2 import CreateServicePrincipalSecretResponse
911
from databricks.labs.lsql import Row
1012
from databricks.labs.lsql.backends import StatementExecutionBackend, SqlBackend
1113
from databricks.sdk import AccountGroupsAPI, GroupsAPI, WorkspaceClient, AccountClient
@@ -242,8 +244,41 @@ def __repr__(self):
242244
return f'RunAs({self.display_name})'
243245

244246

247+
def _make_workspace_client(
248+
ws: WorkspaceClient,
249+
created_secret: CreateServicePrincipalSecretResponse,
250+
service_principal: ServicePrincipal,
251+
) -> WorkspaceClient:
252+
oidc = ws.config.oidc_endpoints
253+
assert oidc is not None, 'OIDC is required'
254+
application_id = service_principal.application_id
255+
secret_value = created_secret.secret
256+
assert application_id is not None
257+
assert secret_value is not None
258+
259+
token_source = ClientCredentials(
260+
client_id=application_id,
261+
client_secret=secret_value,
262+
token_url=oidc.token_endpoint,
263+
scopes=["all-apis"],
264+
use_header=True,
265+
)
266+
267+
def inner() -> dict[str, str]:
268+
inner_token = token_source.token()
269+
return {'Authorization': f'{inner_token.token_type} {inner_token.access_token}'}
270+
271+
def token() -> Token:
272+
return token_source.token()
273+
274+
credentials_provider = OAuthCredentialsProvider(inner, token)
275+
credentials_strategy = OauthCredentialsStrategy('oauth-m2m', lambda _: credentials_provider)
276+
ws_as_spn = WorkspaceClient(host=ws.config.host, credentials_strategy=credentials_strategy)
277+
return ws_as_spn
278+
279+
245280
@fixture
246-
def make_run_as(acc: AccountClient, ws: WorkspaceClient, make_random, env_or_skip, log_account_link):
281+
def make_run_as(acc: AccountClient, ws: WorkspaceClient, make_random, env_or_skip, log_account_link, is_in_debug):
247282
"""
248283
This fixture provides a function to create an account service principal via [`acc` fixture](#acc-fixture) and
249284
assign it to a workspace. The service principal is removed after the test is complete. The service principal is
@@ -288,8 +323,18 @@ def test_creating_notebook_on_behalf_of_ephemeral_principal(make_notebook):
288323
notebook = make_notebook()
289324
assert notebook.exists()
290325
```
326+
327+
This fixture currently doesn't work with Databricks Metadata Service authentication on Azure Databricks.
291328
"""
292329

330+
if ws.config.auth_type == 'metadata-service' and ws.config.is_azure:
331+
# TODO: fix `invalid_scope: AADSTS1002012: The provided value for scope all-apis is not valid.` error
332+
#
333+
# We're having issues with the Azure Metadata Service and service principals. The error message is:
334+
# Client credential flows must have a scope value with /.default suffixed to the resource identifier
335+
# (application ID URI)
336+
pytest.skip('Azure Metadata Service does not support service principals')
337+
293338
def create(*, account_groups: list[str] | None = None):
294339
workspace_id = ws.get_workspace_id()
295340
service_principal = acc.service_principals.create(display_name=f'spn-{make_random()}')
@@ -315,39 +360,12 @@ def create(*, account_groups: list[str] | None = None):
315360
)
316361
permissions = [WorkspacePermission.USER]
317362
acc.workspace_assignment.update(workspace_id, service_principal_id, permissions=permissions)
318-
ws_as_spn = _make_workspace_client(created_secret, service_principal)
363+
ws_as_spn = _make_workspace_client(ws, created_secret, service_principal)
319364

320365
log_account_link('account service principal', f'users/serviceprincipals/{service_principal_id}')
321366

322367
return RunAs(service_principal, ws_as_spn, env_or_skip)
323368

324-
def _make_workspace_client(created_secret, service_principal):
325-
oidc = ws.config.oidc_endpoints
326-
assert oidc is not None, 'OIDC is required'
327-
application_id = service_principal.application_id
328-
secret_value = created_secret.secret
329-
assert application_id is not None
330-
assert secret_value is not None
331-
token_source = ClientCredentials(
332-
client_id=application_id,
333-
client_secret=secret_value,
334-
token_url=oidc.token_endpoint,
335-
scopes=["all-apis"],
336-
use_header=True,
337-
)
338-
339-
def inner() -> dict[str, str]:
340-
inner_token = token_source.token()
341-
return {'Authorization': f'{inner_token.token_type} {inner_token.access_token}'}
342-
343-
def token() -> Token:
344-
return token_source.token()
345-
346-
credentials_provider = OAuthCredentialsProvider(inner, token)
347-
credentials_strategy = OauthCredentialsStrategy('oauth-m2m', lambda _: credentials_provider)
348-
ws_as_spn = WorkspaceClient(host=ws.config.host, credentials_strategy=credentials_strategy)
349-
return ws_as_spn
350-
351369
def remove(run_as: RunAs):
352370
service_principal_id = run_as._service_principal.id # pylint: disable=protected-access
353371
assert service_principal_id is not None

0 commit comments

Comments
 (0)