Skip to content

Commit 89ebb60

Browse files
authored
[Profile] az login: Fall back to device code flow in GitHub Codespaces (#27443)
1 parent f5e9599 commit 89ebb60

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-1
lines changed

src/azure-cli-core/azure/cli/core/_profile.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from azure.cli.core._session import ACCOUNT
1212
from azure.cli.core.azclierror import AuthenticationError
1313
from azure.cli.core.cloud import get_active_cloud, set_cloud_subscription
14-
from azure.cli.core.util import in_cloud_console, can_launch_browser
14+
from azure.cli.core.util import in_cloud_console, can_launch_browser, is_github_codespaces
1515
from knack.log import get_logger
1616
from knack.util import CLIError
1717

@@ -166,6 +166,10 @@ def login(self,
166166
logger.info('No web browser is available. Fall back to device code.')
167167
use_device_code = True
168168

169+
if not use_device_code and is_github_codespaces():
170+
logger.info('GitHub Codespaces is detected. Fall back to device code.')
171+
use_device_code = True
172+
169173
if use_device_code:
170174
user_identity = identity.login_with_device_code(scopes=scopes, **kwargs)
171175
else:

src/azure-cli-core/azure/cli/core/tests/test_profile.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,55 @@ def test_login_with_device_code(self, login_with_device_code_mock, get_user_cred
347347
subs = profile.login(True, None, None, False, None, use_device_code=True, allow_no_subscriptions=False)
348348

349349
# assert
350+
login_with_device_code_mock.assert_called_once()
351+
self.assertEqual(self.subscription1_with_tenant_info_output, subs)
352+
353+
@mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True)
354+
@mock.patch('azure.cli.core.auth.identity.Identity.get_user_credential', autospec=True)
355+
@mock.patch('azure.cli.core.auth.identity.Identity.login_with_device_code', autospec=True)
356+
@mock.patch('azure.cli.core._profile.can_launch_browser', autospec=True, return_value=False)
357+
def test_login_fallback_to_device_code_no_browser(self, can_launch_browser_mock, login_with_device_code_mock,
358+
get_user_credential_mock, create_subscription_client_mock):
359+
login_with_device_code_mock.return_value = self.user_identity_mock
360+
361+
cli = DummyCli()
362+
mock_subscription_client = mock.MagicMock()
363+
mock_subscription_client.tenants.list.return_value = [TenantStub(self.tenant_id)]
364+
mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)]
365+
create_subscription_client_mock.return_value = mock_subscription_client
366+
367+
storage_mock = {'subscriptions': None}
368+
profile = Profile(cli_ctx=cli, storage=storage_mock)
369+
subs = profile.login(True, None, None, False, None, use_device_code=True, allow_no_subscriptions=False)
370+
371+
# assert
372+
login_with_device_code_mock.assert_called_once()
373+
self.assertEqual(self.subscription1_with_tenant_info_output, subs)
374+
375+
@mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True)
376+
@mock.patch('azure.cli.core.auth.identity.Identity.get_user_credential', autospec=True)
377+
@mock.patch('azure.cli.core.auth.identity.Identity.login_with_device_code', autospec=True)
378+
@mock.patch('azure.cli.core._profile.is_github_codespaces', autospec=True, return_value=True)
379+
@mock.patch('azure.cli.core._profile.can_launch_browser', autospec=True, return_value=True)
380+
def test_login_fallback_to_device_code_github_codespaces(self, can_launch_browser_mock, is_github_codespaces_mock,
381+
login_with_device_code_mock, get_user_credential_mock,
382+
create_subscription_client_mock):
383+
# GitHub Codespaces does support launching a browser (actually a new tab),
384+
# so we mock can_launch_browser to True.
385+
login_with_device_code_mock.return_value = self.user_identity_mock
386+
387+
cli = DummyCli()
388+
mock_subscription_client = mock.MagicMock()
389+
mock_subscription_client.tenants.list.return_value = [TenantStub(self.tenant_id)]
390+
mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)]
391+
create_subscription_client_mock.return_value = mock_subscription_client
392+
393+
storage_mock = {'subscriptions': None}
394+
profile = Profile(cli_ctx=cli, storage=storage_mock)
395+
subs = profile.login(True, None, None, False, None, use_device_code=True, allow_no_subscriptions=False)
396+
397+
# assert
398+
login_with_device_code_mock.assert_called_once()
350399
self.assertEqual(self.subscription1_with_tenant_info_output, subs)
351400

352401
@mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True)

src/azure-cli-core/azure/cli/core/util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,11 @@ def is_windows():
754754
return platform_name == 'windows'
755755

756756

757+
def is_github_codespaces():
758+
# https://docs.github.com/en/codespaces/developing-in-a-codespace/default-environment-variables-for-your-codespace
759+
return os.environ.get('CODESPACES') == 'true'
760+
761+
757762
def can_launch_browser():
758763
import webbrowser
759764
platform_name, _ = _get_platform_info()

0 commit comments

Comments
 (0)