Skip to content

Commit 1dcc847

Browse files
committed
Add Docs and Service account project ID
1 parent e469339 commit 1dcc847

File tree

4 files changed

+139
-22
lines changed

4 files changed

+139
-22
lines changed

docs/gcloud-config.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
Configuration
2+
*************
3+
4+
Overview
5+
========
6+
7+
- Use service :class:`Client <gcloud.client.Client>` objects to configure
8+
your applications.
9+
10+
For example:
11+
12+
.. code-block:: python
13+
14+
>>> from gcloud import bigquery
15+
>>> client = bigquery.Client()
16+
17+
- :class:`Client <gcloud.client.Client>` objects hold both a ``project``
18+
and an authenticated connection to a service.
19+
20+
- The authentication credentials can be implicitly determined from the
21+
environment or directly via
22+
:meth:`from_service_account_json <gcloud.client.Client.from_service_account_json>`
23+
and
24+
:meth:`from_service_account_p12 <gcloud.client.Client.from_service_account_p12>`.
25+
26+
- Logging in with the `Google Cloud SDK`_ will automatically configure a JSON
27+
key file with your default project ID and credentials.
28+
Setting the ``GOOGLE_APPLICATION_CREDENTIALS`` and ``GCLOUD_PROJECT``
29+
environment variables will override the automatically configured credentials.
30+
31+
- You can change your default project ID to ``my-new-default-project`` with
32+
``gcloud`` command line tool.
33+
34+
.. code-block:: bash
35+
36+
$ gcloud config set project my-new-default-project
37+
38+
.. _Google Cloud SDK: http://cloud.google.com/sdk
39+
40+
- You can override the credentials inferred from the environment by passing
41+
explicit ``credentials`` to one of the alternative ``classmethod`` factories,
42+
:meth:`gcloud.client.Client.from_service_account_json`:
43+
44+
.. code-block:: python
45+
46+
>>> from gcloud import bigquery
47+
>>> client = bigquery.Client.from_service_account_json('/path/to/creds.json')
48+
49+
or :meth:`gcloud.client.Client.from_service_account_p12`:
50+
51+
.. code-block:: python
52+
53+
>>> from gcloud import bigquery
54+
>>> client = bigquery.Client.from_service_account_p12(
55+
... '/path/to/creds.p12', '[email protected]')

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
:caption: gcloud
55

66
gcloud-api
7+
gcloud-config
78
gcloud-auth
89

910
.. toctree::

gcloud/_helpers.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import os
2323
import re
2424
import socket
25+
import subprocess
2526
import sys
2627
from threading import local as Local
2728

@@ -130,13 +131,13 @@ def _ensure_tuple_or_list(arg_name, tuple_or_list):
130131
This effectively reduces the iterable types allowed to a very short
131132
whitelist: list and tuple.
132133
133-
:type arg_name: string
134+
:type arg_name: str
134135
:param arg_name: Name of argument to use in error message.
135136
136-
:type tuple_or_list: sequence of string
137+
:type tuple_or_list: sequence of str
137138
:param tuple_or_list: Sequence to be verified.
138139
139-
:rtype: list of string
140+
:rtype: list of str
140141
:returns: The ``tuple_or_list`` passed in cast to a ``list``.
141142
:raises: class:`TypeError` if the ``tuple_or_list`` is not a tuple or
142143
list.
@@ -150,7 +151,7 @@ def _ensure_tuple_or_list(arg_name, tuple_or_list):
150151
def _app_engine_id():
151152
"""Gets the App Engine application ID if it can be inferred.
152153
153-
:rtype: string or ``NoneType``
154+
:rtype: str or ``NoneType``
154155
:returns: App Engine application ID if running in App Engine,
155156
else ``None``.
156157
"""
@@ -163,7 +164,7 @@ def _app_engine_id():
163164
def _file_project_id():
164165
"""Gets the project id from the credentials file if one is available.
165166
166-
:rtype: string or ``NoneType``
167+
:rtype: str or ``NoneType``
167168
:returns: Project-ID from JSON credentials file if value exists,
168169
else ``None``.
169170
"""
@@ -177,6 +178,22 @@ def _file_project_id():
177178
return None
178179

179180

181+
def _default_service_project_id():
182+
"""Retrieves the project ID from the gcloud command line tool.
183+
184+
:rtype: str or ``NoneType``
185+
:returns: Project-ID from ``gcloud info`` else ``None``
186+
"""
187+
gcloud_project_conf = subprocess.check_output(['gcloud', 'info'])
188+
gcloud_project_conf = gcloud_project_conf.split('\n')
189+
190+
for key in gcloud_project_conf:
191+
if key.startswith('Project:'):
192+
return key[10:-1]
193+
194+
return None
195+
196+
180197
def _compute_engine_id():
181198
"""Gets the Compute Engine project ID if it can be inferred.
182199
@@ -190,7 +207,7 @@ def _compute_engine_id():
190207
See https://github.com/google/oauth2client/issues/93 for context about
191208
DNS latency.
192209
193-
:rtype: string or ``NoneType``
210+
:rtype: str or ``NoneType``
194211
:returns: Compute Engine project ID if the metadata service is available,
195212
else ``None``.
196213
"""
@@ -223,14 +240,14 @@ def _determine_default_project(project=None):
223240
224241
* GCLOUD_PROJECT environment variable
225242
* GOOGLE_APPLICATION_CREDENTIALS JSON file
226-
* Get from oauth defaults
243+
* Get from `gcloud auth login` defaults
227244
* Google App Engine application ID
228245
* Google Compute Engine project ID (from metadata server)
229246
230-
:type project: string
247+
:type project: str
231248
:param project: Optional. The project name to use as default.
232249
233-
:rtype: string or ``NoneType``
250+
:rtype: str or ``NoneType``
234251
:returns: Default project if it can be determined.
235252
"""
236253
if project is None:
@@ -239,8 +256,8 @@ def _determine_default_project(project=None):
239256
if project is None:
240257
project = _file_project_id()
241258

242-
# if project is None:
243-
# print oauth2client.get_application_default()
259+
if project is None:
260+
project = _default_service_project_id()
244261

245262
if project is None:
246263
project = _app_engine_id()
@@ -257,7 +274,7 @@ def _millis(when):
257274
:type when: :class:`datetime.datetime`
258275
:param when: the datetime to convert
259276
260-
:rtype: integer
277+
:rtype: int
261278
:returns: milliseconds since epoch for ``when``
262279
"""
263280
micros = _microseconds_from_datetime(when)
@@ -282,7 +299,7 @@ def _microseconds_from_datetime(value):
282299
:type value: :class:`datetime.datetime`
283300
:param value: The timestamp to convert.
284301
285-
:rtype: integer
302+
:rtype: int
286303
:returns: The timestamp, in microseconds.
287304
"""
288305
if not value.tzinfo:
@@ -299,7 +316,7 @@ def _millis_from_datetime(value):
299316
:type value: :class:`datetime.datetime`, or None
300317
:param value: the timestamp
301318
302-
:rtype: integer, or ``NoneType``
319+
:rtype: int, or ``NoneType``
303320
:returns: the timestamp, in milliseconds, or None
304321
"""
305322
if value is not None:
@@ -456,20 +473,20 @@ def _datetime_to_pb_timestamp(when):
456473
def _name_from_project_path(path, project, template):
457474
"""Validate a URI path and get the leaf object's name.
458475
459-
:type path: string
476+
:type path: str
460477
:param path: URI path containing the name.
461478
462-
:type project: string or NoneType
479+
:type project: str or NoneType
463480
:param project: The project associated with the request. It is
464481
included for validation purposes. If passed as None,
465482
disables validation.
466483
467-
:type template: string
484+
:type template: str
468485
:param template: Template regex describing the expected form of the path.
469486
The regex must have two named groups, 'project' and
470487
'name'.
471488
472-
:rtype: string
489+
:rtype: str
473490
:returns: Name parsed from ``path``.
474491
:raises: :class:`ValueError` if the ``path`` is ill-formed or if
475492
the project from the ``path`` does not agree with the

gcloud/test__helpers.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,37 @@ def test_no_environment(self):
183183
self.assertEqual(None, self._callFUT())
184184

185185

186+
class Test__get_default_service_project_id(unittest2.TestCase):
187+
188+
def callFUT(self, project_result=''):
189+
from gcloud._helpers import _default_service_project_id
190+
import subprocess
191+
self.check_output_called_with = []
192+
193+
def check_output_mock(called_with=None):
194+
self.check_output_called_with = called_with
195+
return project_result
196+
197+
from gcloud._testing import _Monkey
198+
with _Monkey(subprocess, check_output=check_output_mock):
199+
return _default_service_project_id(), self.check_output_called_with
200+
201+
def test_read_from_cli_info(self):
202+
project_id, called_with = self.callFUT('Project: [test-project-id]')
203+
self.assertEqual('test-project-id', project_id)
204+
self.assertEqual(['gcloud', 'info'], called_with)
205+
206+
def test_cli_info_not_set(self):
207+
project_id, called_with = self.callFUT()
208+
self.assertEqual(None, project_id)
209+
self.assertEqual(['gcloud', 'info'], called_with)
210+
211+
def test_info_value_not_present(self):
212+
project_id, called_with = self.callFUT('Active Configuration')
213+
self.assertEqual(None, project_id)
214+
self.assertEqual(['gcloud', 'info'], called_with)
215+
216+
186217
class Test__compute_engine_id(unittest2.TestCase):
187218

188219
def _callFUT(self):
@@ -254,7 +285,7 @@ def _callFUT(self, project=None):
254285
return _determine_default_project(project=project)
255286

256287
def _determine_default_helper(self, prod=None, gae=None, gce=None,
257-
project=None):
288+
file_id=None, srv_id=None, project=None):
258289
from gcloud._testing import _Monkey
259290
from gcloud import _helpers
260291

@@ -264,6 +295,14 @@ def prod_mock():
264295
_callers.append('prod_mock')
265296
return prod
266297

298+
def file_id_mock():
299+
_callers.append('file_id_mock')
300+
return file_id
301+
302+
def srv_id_mock():
303+
_callers.append('srv_id_mock')
304+
return srv_id
305+
267306
def gae_mock():
268307
_callers.append('gae_mock')
269308
return gae
@@ -274,6 +313,8 @@ def gce_mock():
274313

275314
patched_methods = {
276315
'_get_production_project': prod_mock,
316+
'_file_project_id': file_id_mock,
317+
'_default_service_project_id': srv_id_mock,
277318
'_app_engine_id': gae_mock,
278319
'_compute_engine_id': gce_mock,
279320
}
@@ -286,7 +327,8 @@ def gce_mock():
286327
def test_no_value(self):
287328
project, callers = self._determine_default_helper()
288329
self.assertEqual(project, None)
289-
self.assertEqual(callers, ['prod_mock', 'gae_mock', 'gce_mock'])
330+
self.assertEqual(callers, ['prod_mock', 'file_id_mock', 'srv_id_mock',
331+
'gae_mock', 'gce_mock'])
290332

291333
def test_explicit(self):
292334
PROJECT = object()
@@ -304,13 +346,15 @@ def test_gae(self):
304346
PROJECT = object()
305347
project, callers = self._determine_default_helper(gae=PROJECT)
306348
self.assertEqual(project, PROJECT)
307-
self.assertEqual(callers, ['prod_mock', 'gae_mock'])
349+
self.assertEqual(callers, ['prod_mock', 'file_id_mock',
350+
'srv_id_mock', 'gae_mock'])
308351

309352
def test_gce(self):
310353
PROJECT = object()
311354
project, callers = self._determine_default_helper(gce=PROJECT)
312355
self.assertEqual(project, PROJECT)
313-
self.assertEqual(callers, ['prod_mock', 'gae_mock', 'gce_mock'])
356+
self.assertEqual(callers, ['prod_mock', 'file_id_mock', 'srv_id_mock',
357+
'gae_mock', 'gce_mock'])
314358

315359

316360
class Test__millis(unittest2.TestCase):

0 commit comments

Comments
 (0)