Unclogging the tubes

Everything is broken and nobody's upset. And yet we are here.

A case of a (not actually) missing browser


It's one of those where you think it's either malice or lack of any practical testing by the development team.

Let's assume you have WSL/WSL2 installed and it's an Ubuntu (I'm saying this only because I didn't test with other ones but the behavior theoretically should be the same) distro. You've recently installed gcloud SDK and are ready to go. So you start with some init:

$ gcloud init Welcome! This command will take you through the configuration of gcloud. Your current configuration has been set to: [default] You can skip diagnostics next time by using the following flag: gcloud init --skip-diagnostics Network diagnostic detects and fixes local network connection issues. Checking network connection...done. Reachability Check passed. Network diagnostic passed (1/1 checks passed). You must log in to continue. Would you like to log in (Y/n)? y

But then you quickly end up with the following error message:

You are authorizing gcloud CLI without access to a web browser. Please run the following command on a machine with a web browser and copy its output back here. Make sure the installed gcloud version is 372.0.0 or newer. gcloud auth login --remote-bootstrap="https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=REDACTED.apps.googleusercontent.com&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fsqlservice.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=REDACTED&access_type=offline&code_challenge=REDACTED&code_challenge_method=S256&token_usage=remote"

A quick Google search shows that a similar issue was happening with GitHub CLI utility. The solution is the following:

export BROWSER=wslview sudo ln -s $(which wslview) /usr/local/bin/xdg-open

Without going into too much details, it relies on setting a special BROWSER shell variable and symlinking the path to wslview (a utility which opens the default Windows browser where its argument is treated as the URL to open) to the default path of xdg-open utility which is usually used by X11 applications to open a default browser in the system.

And it doesn't work.

It looks like gcloud does some other magic to find out if the browser is present. We'll have to dig a little bit deeper to know what exactly. By inspecting dpkg-query -L google-cloud-cli, we can see that most of the Python-based gcloud code is located in /usr/lib/google-cloud-sdk/. Let's see if we can find the actual error message quickly there.

$ grep -R "without access to a web browser" . grep: ./lib/googlecloudsdk/core/credentials/__pycache__/flow.cpython-39.pyc: binary file matches

Hmm, a binary file match. Most likely the original code doesn't have this exact string on the same line, however, the precompiled Python version does since the string is present there already concatenated. Anyway, we know that we need to look for a file called flow.py in directory ./lib/googlecloudsdk/core/credentials/

In that file we quickly find a class called NoBrowserFlow:

... class NoBrowserFlow(InstalledAppFlow): """Flow to authorize gcloud on a machine without access to web browsers. Out-of-band flow (OobFlow) is deprecated. This flow together with the helper flow NoBrowserHelperFlow is the replacement. gcloud in environments without access to browsers (i.e. access via ssh) can use this flow to authorize gcloud. This flow will print authorization parameters which will be taken by the helper flow to build the final authorization request. The helper flow (run by a gcloud instance with access to browsers) will launch the browser and ask for user's authorization. After the authorization, the helper flow will print the authorization response to pass back to this flow to continue the process (exchanging for the refresh/access tokens). """ ...

Let's see where it's used:

$ grep -R NoBrowserFlow . grep: ./lib/googlecloudsdk/api_lib/auth/__pycache__/util.cpython-39.pyc: binary file matches ./lib/googlecloudsdk/api_lib/auth/util.py:class NoBrowserFlowRunner(FlowRunner): ./lib/googlecloudsdk/api_lib/auth/util.py: """A flow runner to run NoBrowserFlow.""" ./lib/googlecloudsdk/api_lib/auth/util.py: return c_flow.NoBrowserFlow.from_client_config( ./lib/googlecloudsdk/api_lib/auth/util.py: return c_flow.NoBrowserFlow.from_client_config( ./lib/googlecloudsdk/api_lib/auth/util.py: user_creds = NoBrowserFlowRunner(scopes, client_config).Run() ./lib/googlecloudsdk/api_lib/auth/util.py: user_creds = NoBrowserFlowRunner(scopes, client_config).Run()

OK, we have a util.py which most likely contains the logic to detect whether the browser is present or not. But it actually contains a try/catch block to another function. Saving your time, I can tell you that what they're actually doing under the hood is that they're calling Python's webbrowser module which actually works on its own in WSL, you can check that by executing

python -m webbrowser -t "https://www.python.org"

However, before that they're also checking if the currently running platform is Linux-based and if at least one of these environment variables are set:

_DISPLAY_VARIABLES = ['DISPLAY', 'WAYLAND_DISPLAY', 'MIR_SOCKET']

As you probably have already guessed, none of them are set in WSL by default because there's no window server running by default either. There is a thing called WSLg with support for this scenario but it's not installed by default. This results in a silly case where you have all the bits ready and waiting to be executed but the logic of the code itself isn't sophisticated enough to detect environments such as WSL.

The actual workaround is very simple. You need to short-circuit the environment variable check with:

export DISPLAY=foo

After this gcloud will happily report that the OAuth link has been opened in my browser:

$ gcloud init Welcome! This command will take you through the configuration of gcloud. Your current configuration has been set to: [default] You can skip diagnostics next time by using the following flag: gcloud init --skip-diagnostics Network diagnostic detects and fixes local network connection issues. Checking network connection...done. Reachability Check passed. Network diagnostic passed (1/1 checks passed). You must log in to continue. Would you like to log in (Y/n)? y Your browser has been opened to visit: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=REDACTED.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2F&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fsqlservice.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=REDACTED&access_type=offline&code_challenge=REDACTED&code_challenge_method=S256

You should be good to go after that. I probably need to file a bug report about this...

Update (2022-08-10): The bug report already exists and even contains the same workaround as described in this post :-)

Posted on