Skip to content

Commit 8789afa

Browse files
authored
feat: avoid authentication with storage emulator (#679)
* feat: avoid authentication with storage emulator * add unit tests * add more unit tests * add support for corner case project set in envvar * add _helpers unit test * allow flexibility for locational endpoints
1 parent 71a4535 commit 8789afa

File tree

4 files changed

+116
-7
lines changed

4 files changed

+116
-7
lines changed

google/cloud/storage/_helpers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from urllib.parse import urlsplit
2424

2525
from google import resumable_media
26+
from google.auth import environment_vars
2627
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
2728
from google.cloud.storage.retry import DEFAULT_RETRY
2829
from google.cloud.storage.retry import DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED
@@ -62,6 +63,12 @@ def _get_storage_host():
6263
return os.environ.get(STORAGE_EMULATOR_ENV_VAR, _DEFAULT_STORAGE_HOST)
6364

6465

66+
def _get_environ_project():
67+
return os.getenv(
68+
environment_vars.PROJECT, os.getenv(environment_vars.LEGACY_PROJECT),
69+
)
70+
71+
6572
def _validate_name(name):
6673
"""Pre-flight ``Bucket`` name validation.
6774

google/cloud/storage/client.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from google.cloud._helpers import _LocalStack, _NOW
3232
from google.cloud.client import ClientWithProject
3333
from google.cloud.exceptions import NotFound
34+
from google.cloud.storage._helpers import _get_environ_project
3435
from google.cloud.storage._helpers import _get_storage_host
3536
from google.cloud.storage._helpers import _DEFAULT_STORAGE_HOST
3637
from google.cloud.storage._helpers import _bucket_bound_hostname_url
@@ -121,13 +122,6 @@ def __init__(
121122
if project is _marker:
122123
project = None
123124

124-
super(Client, self).__init__(
125-
project=project,
126-
credentials=credentials,
127-
client_options=client_options,
128-
_http=_http,
129-
)
130-
131125
kw_args = {"client_info": client_info}
132126

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

145+
# Use anonymous credentials and no project when
146+
# STORAGE_EMULATOR_HOST or a non-default api_endpoint is set.
147+
if (
148+
kw_args["api_endpoint"] is not None
149+
and kw_args["api_endpoint"].find("storage.googleapis.com") < 0
150+
):
151+
if credentials is None:
152+
credentials = AnonymousCredentials()
153+
if project is None:
154+
project = _get_environ_project()
155+
if project is None:
156+
no_project = True
157+
project = "<none>"
158+
159+
super(Client, self).__init__(
160+
project=project,
161+
credentials=credentials,
162+
client_options=client_options,
163+
_http=_http,
164+
)
165+
151166
if no_project:
152167
self.project = None
153168

tests/unit/test__helpers.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ def test_w_env_var(self):
4646
self.assertEqual(host, HOST)
4747

4848

49+
class Test__get_environ_project(unittest.TestCase):
50+
@staticmethod
51+
def _call_fut():
52+
from google.cloud.storage._helpers import _get_environ_project
53+
54+
return _get_environ_project()
55+
56+
def test_wo_env_var(self):
57+
with mock.patch("os.environ", {}):
58+
project = self._call_fut()
59+
60+
self.assertEqual(project, None)
61+
62+
def test_w_env_var(self):
63+
from google.auth import environment_vars
64+
65+
PROJECT = "environ-project"
66+
67+
with mock.patch("os.environ", {environment_vars.PROJECT: PROJECT}):
68+
project = self._call_fut()
69+
self.assertEqual(project, PROJECT)
70+
71+
with mock.patch("os.environ", {environment_vars.LEGACY_PROJECT: PROJECT}):
72+
project = self._call_fut()
73+
74+
self.assertEqual(project, PROJECT)
75+
76+
4977
class Test_PropertyMixin(unittest.TestCase):
5078
@staticmethod
5179
def _get_default_timeout():

tests/unit/test_client.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,65 @@ def test_ctor_mtls(self):
236236
self.assertEqual(client._connection.ALLOW_AUTO_SWITCH_TO_MTLS_URL, False)
237237
self.assertEqual(client._connection.API_BASE_URL, "http://foo")
238238

239+
def test_ctor_w_emulator_wo_project(self):
240+
from google.auth.credentials import AnonymousCredentials
241+
from google.cloud.storage._helpers import STORAGE_EMULATOR_ENV_VAR
242+
243+
# avoids authentication if STORAGE_EMULATOR_ENV_VAR is set
244+
host = "http://localhost:8080"
245+
environ = {STORAGE_EMULATOR_ENV_VAR: host}
246+
with mock.patch("os.environ", environ):
247+
client = self._make_one()
248+
249+
self.assertIsNone(client.project)
250+
self.assertEqual(client._connection.API_BASE_URL, host)
251+
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)
252+
253+
# avoids authentication if storage emulator is set through api_endpoint
254+
client = self._make_one(
255+
client_options={"api_endpoint": "http://localhost:8080"}
256+
)
257+
self.assertIsNone(client.project)
258+
self.assertEqual(client._connection.API_BASE_URL, host)
259+
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)
260+
261+
def test_ctor_w_emulator_w_environ_project(self):
262+
from google.auth.credentials import AnonymousCredentials
263+
from google.cloud.storage._helpers import STORAGE_EMULATOR_ENV_VAR
264+
265+
# avoids authentication and infers the project from the environment
266+
host = "http://localhost:8080"
267+
environ_project = "environ-project"
268+
environ = {
269+
STORAGE_EMULATOR_ENV_VAR: host,
270+
"GOOGLE_CLOUD_PROJECT": environ_project,
271+
}
272+
with mock.patch("os.environ", environ):
273+
client = self._make_one()
274+
275+
self.assertEqual(client.project, environ_project)
276+
self.assertEqual(client._connection.API_BASE_URL, host)
277+
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)
278+
279+
def test_ctor_w_emulator_w_project_arg(self):
280+
from google.auth.credentials import AnonymousCredentials
281+
from google.cloud.storage._helpers import STORAGE_EMULATOR_ENV_VAR
282+
283+
# project argument overrides project set in the enviroment
284+
host = "http://localhost:8080"
285+
environ_project = "environ-project"
286+
project = "my-test-project"
287+
environ = {
288+
STORAGE_EMULATOR_ENV_VAR: host,
289+
"GOOGLE_CLOUD_PROJECT": environ_project,
290+
}
291+
with mock.patch("os.environ", environ):
292+
client = self._make_one(project=project)
293+
294+
self.assertEqual(client.project, project)
295+
self.assertEqual(client._connection.API_BASE_URL, host)
296+
self.assertIsInstance(client._connection.credentials, AnonymousCredentials)
297+
239298
def test_create_anonymous_client(self):
240299
from google.auth.credentials import AnonymousCredentials
241300
from google.cloud.storage._http import Connection

0 commit comments

Comments
 (0)