diff --git a/docs/04_automation_and_utility.md b/docs/04_automation_and_utility.md index 636eb5f..05eaad4 100644 --- a/docs/04_automation_and_utility.md +++ b/docs/04_automation_and_utility.md @@ -1,5 +1,6 @@ --- log: +2026-11-07: Optimized `_get_adc_credentials` in `auth.py` to stub `google.auth._cloud_sdk.get_project_id` around `google.auth.default()`. When ADC user credentials lack a `quota_project_id`, `google-auth` otherwise spawns `gcloud config get-value project` on every CLI invocation to resolve a `project_id` that `_get_adc_credentials` immediately discards (`_`). 2026-11-04: `colab drivemount` now opens the Drive consent URL in the system browser (`utils.open_url_in_browser`, also used by `colab pay`/`colab url --open`) instead of only printing it. The URL is still printed first, and a missing browser opener (SSH, containers, CI) is treated as normal: the flow keeps waiting for the user exactly as before. The prompt moved into `commands/automation.py::show_drive_auth_url` so it can be unit-tested. 2026-11-02: Added `_check_linux_keyring_locked` in `auth.py` to inspect the active Linux `SecretService` collection via `secretstorage` before `get_password`/`set_password`, warning immediately when the keyring is locked so users know why the CLI is waiting on an unlock prompt (and suggesting `--auth=adc`). 2026-10-29: Session history files are created readable only by the owner (0600), and existing ones are tightened to 0600 whenever the history logger starts. diff --git a/src/colab_cli/auth.py b/src/colab_cli/auth.py index 581aaf6..c67cd8f 100644 --- a/src/colab_cli/auth.py +++ b/src/colab_cli/auth.py @@ -299,7 +299,22 @@ def _get_adc_credentials() -> Credentials: # That warning shows up on every single `colab` invocation under ADC, # which is pure noise. Filter it, but keep the scope as tight as possible: # only this exact message, only during this one call. - with warnings.catch_warnings(): + # + # Additionally, when ADC user credentials lack a `quota_project_id`, + # `google.auth._default._get_gcloud_sdk_credentials` calls + # `google.auth._cloud_sdk.get_project_id()`, which spawns a + # `gcloud config get-value project` subprocess (adding 2-13s of latency on + # every CLI invocation) solely to compute the second tuple element + # (`project_id`) that we immediately discard (`_`). Stubbing + # `get_project_id` during `google.auth.default()` avoids spawning `gcloud` + # while preserving the full ADC discovery chain. + from unittest.mock import patch as _patch + from google.auth import _cloud_sdk + + with ( + warnings.catch_warnings(), + _patch.object(_cloud_sdk, "get_project_id", return_value="colab-cli"), + ): warnings.filterwarnings( "ignore", message=r"Your application has authenticated using end user credentials.*", diff --git a/tests/test_auth_adc.py b/tests/test_auth_adc.py index 4e7ec5c..7f0cff7 100644 --- a/tests/test_auth_adc.py +++ b/tests/test_auth_adc.py @@ -200,3 +200,30 @@ def fake_default(*args, **kwargs): assert len(matching) == 1, ( "Unrelated UserWarnings from google.auth must NOT be suppressed." ) + + +def test_get_credentials_adc_skips_gcloud_project_subprocess(mocker): + """When ADC user credentials have no quota_project_id, google.auth.default() + normally calls google.auth._cloud_sdk.get_project_id(), spawning a slow + `gcloud config get-value project` subprocess on every CLI invocation for a + project_id that _get_adc_credentials discards. Verify that subprocess path + is bypassed during google.auth.default().""" + from google.auth import _cloud_sdk + + mock_creds = MagicMock() + mock_creds.requires_scopes = False + + observed_project_id = [] + + def fake_default(*args, **kwargs): + observed_project_id.append(_cloud_sdk.get_project_id()) + return mock_creds, observed_project_id[-1] + + mocker.patch("google.auth.default", side_effect=fake_default) + m_subproc = mocker.patch("subprocess.check_output") + mocker.patch("colab_cli.auth.requests.AuthorizedSession") + + get_credentials(provider=AuthProvider.ADC) + + assert observed_project_id == ["colab-cli"] + m_subproc.assert_not_called()