Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: avoid authentication with storage emulator #679

Merged
merged 7 commits into from Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions google/cloud/storage/_helpers.py
Expand Up @@ -25,6 +25,7 @@
from six import string_types
from six.moves.urllib.parse import urlsplit
from google import resumable_media
from google.auth import environment_vars
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
from google.cloud.storage.retry import DEFAULT_RETRY
from google.cloud.storage.retry import DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED
Expand Down Expand Up @@ -64,6 +65,12 @@ def _get_storage_host():
return os.environ.get(STORAGE_EMULATOR_ENV_VAR, _DEFAULT_STORAGE_HOST)


def _get_environ_project():
return os.getenv(
environment_vars.PROJECT, os.getenv(environment_vars.LEGACY_PROJECT),
)


def _validate_name(name):
"""Pre-flight ``Bucket`` name validation.

Expand Down
29 changes: 22 additions & 7 deletions google/cloud/storage/client.py
Expand Up @@ -31,6 +31,7 @@
from google.cloud._helpers import _LocalStack, _NOW
from google.cloud.client import ClientWithProject
from google.cloud.exceptions import NotFound
from google.cloud.storage._helpers import _get_environ_project
from google.cloud.storage._helpers import _get_storage_host
from google.cloud.storage._helpers import _DEFAULT_STORAGE_HOST
from google.cloud.storage._helpers import _bucket_bound_hostname_url
Expand Down Expand Up @@ -121,13 +122,6 @@ def __init__(
if project is _marker:
project = None

super(Client, self).__init__(
project=project,
credentials=credentials,
client_options=client_options,
_http=_http,
)

kw_args = {"client_info": client_info}

# `api_endpoint` should be only set by the user via `client_options`,
Expand All @@ -148,6 +142,27 @@ def __init__(
api_endpoint = client_options.api_endpoint
kw_args["api_endpoint"] = api_endpoint

# Use anonymous credentials and no project when
# STORAGE_EMULATOR_HOST or a non-default api_endpoint is set.
if (
kw_args["api_endpoint"] is not None
and kw_args["api_endpoint"].find("storage.googleapis.com") < 0
):
if credentials is None:
credentials = AnonymousCredentials()
if project is None:
project = _get_environ_project()
if project is None:
no_project = True
project = "<none>"

super(Client, self).__init__(
project=project,
credentials=credentials,
client_options=client_options,
_http=_http,
)

if no_project:
self.project = None

Expand Down
28 changes: 28 additions & 0 deletions tests/unit/test__helpers.py
Expand Up @@ -46,6 +46,34 @@ def test_w_env_var(self):
self.assertEqual(host, HOST)


class Test__get_environ_project(unittest.TestCase):
@staticmethod
def _call_fut():
from google.cloud.storage._helpers import _get_environ_project

return _get_environ_project()

def test_wo_env_var(self):
with mock.patch("os.environ", {}):
project = self._call_fut()

self.assertEqual(project, None)

def test_w_env_var(self):
from google.auth import environment_vars

PROJECT = "environ-project"

with mock.patch("os.environ", {environment_vars.PROJECT: PROJECT}):
project = self._call_fut()
self.assertEqual(project, PROJECT)

with mock.patch("os.environ", {environment_vars.LEGACY_PROJECT: PROJECT}):
project = self._call_fut()

self.assertEqual(project, PROJECT)


class Test_PropertyMixin(unittest.TestCase):
@staticmethod
def _get_default_timeout():
Expand Down
59 changes: 59 additions & 0 deletions tests/unit/test_client.py
Expand Up @@ -236,6 +236,65 @@ def test_ctor_mtls(self):
self.assertEqual(client._connection.ALLOW_AUTO_SWITCH_TO_MTLS_URL, False)
self.assertEqual(client._connection.API_BASE_URL, "http://foo")

def test_ctor_w_emulator_wo_project(self):
from google.auth.credentials import AnonymousCredentials
from google.cloud.storage._helpers import STORAGE_EMULATOR_ENV_VAR

# avoids authentication if STORAGE_EMULATOR_ENV_VAR is set
host = "http://localhost:8080"
environ = {STORAGE_EMULATOR_ENV_VAR: host}
with mock.patch("os.environ", environ):
client = self._make_one()

self.assertIsNone(client.project)
self.assertEqual(client._connection.API_BASE_URL, host)
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)

# avoids authentication if storage emulator is set through api_endpoint
client = self._make_one(
client_options={"api_endpoint": "http://localhost:8080"}
)
self.assertIsNone(client.project)
self.assertEqual(client._connection.API_BASE_URL, host)
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)

def test_ctor_w_emulator_w_environ_project(self):
from google.auth.credentials import AnonymousCredentials
from google.cloud.storage._helpers import STORAGE_EMULATOR_ENV_VAR

# avoids authentication and infers the project from the environment
host = "http://localhost:8080"
environ_project = "environ-project"
environ = {
STORAGE_EMULATOR_ENV_VAR: host,
"GOOGLE_CLOUD_PROJECT": environ_project,
}
with mock.patch("os.environ", environ):
client = self._make_one()

self.assertEqual(client.project, environ_project)
self.assertEqual(client._connection.API_BASE_URL, host)
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)

def test_ctor_w_emulator_w_project_arg(self):
from google.auth.credentials import AnonymousCredentials
from google.cloud.storage._helpers import STORAGE_EMULATOR_ENV_VAR

# project argument overrides project set in the enviroment
host = "http://localhost:8080"
environ_project = "environ-project"
project = "my-test-project"
environ = {
STORAGE_EMULATOR_ENV_VAR: host,
"GOOGLE_CLOUD_PROJECT": environ_project,
}
with mock.patch("os.environ", environ):
client = self._make_one(project=project)

self.assertEqual(client.project, project)
self.assertEqual(client._connection.API_BASE_URL, host)
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)

def test_create_anonymous_client(self):
from google.auth.credentials import AnonymousCredentials
from google.cloud.storage._http import Connection
Expand Down