-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
GbqConnector should be able to fetch default credentials on Google Compute Engine #13608
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
9ec2a68
17f4740
9740938
a02b620
fca8003
7f1fd26
d4c7e3d
3479ed8
17dd814
5a2dd15
64134ac
5fd1775
62815e1
cf41e76
ac48104
4cf26aa
6fe392a
6bbdcdc
a65b3f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4475,6 +4475,15 @@ Additional information on service accounts can be found | |
|
||
You will need to install an additional dependency: `oauth2client <https://github.com/google/oauth2client>`__. | ||
|
||
.. versionadded:: 0.19.0 | ||
|
||
Authentication via ``application default credentials`` is also possible. This is only valid | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reverse this logic. If private_key......, otherwise authentication via .... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jreback I don't completely understand this. Other authentication methods are already mentioned above this. I've just added the information for the new method. Could you please share the text that you want me to put here - if you have time. Thanks! |
||
if the parameter ``private_key`` is not provided. This method also requires that | ||
the credentials can be fetched from the environment the code is running in. | ||
Otherwise, the OAuth2 client-side authentication is used. | ||
Additional information on ``application default credentials`` can be found | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Additional information on |
||
`here <https://developers.google.com/identity/protocols/application-default-credentials>`__. | ||
|
||
.. note:: | ||
|
||
The `'private_key'` parameter can be set to either the file path of the service account key | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ def _test_google_api_imports(): | |
except: | ||
from apiclient.discovery import build # noqa | ||
from apiclient.errors import HttpError # noqa | ||
from oauth2client.client import GoogleCredentials # noqa | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this line be moved inside of Using oauth2client version 1.2, I receive the following exception:
|
||
from oauth2client.client import AccessTokenRefreshError # noqa | ||
from oauth2client.client import OAuth2WebServerFlow # noqa | ||
from oauth2client.file import Storage # noqa | ||
|
@@ -160,7 +161,54 @@ def get_credentials(self): | |
if self.private_key: | ||
return self.get_service_account_credentials() | ||
else: | ||
return self.get_user_account_credentials() | ||
# Try to retrieve Application Default Credentials | ||
credentials = self.get_application_default_credentials() | ||
if not credentials: | ||
credentials = self.get_user_account_credentials() | ||
return credentials | ||
|
||
def get_application_default_credentials(self): | ||
""" | ||
This method tries to retrieve the "default application credentials". | ||
This could be useful for running code on Google Cloud Platform. | ||
|
||
.. versionadded:: 0.19.0 | ||
|
||
Parameters | ||
---------- | ||
None | ||
|
||
Returns | ||
------- | ||
- GoogleCredentials, | ||
If the default application credentials can be retrieved | ||
from the environment. The retrieved credentials should also | ||
have access to the project (self.project_id) on BigQuery. | ||
- OR None, | ||
If default application credentials can not be retrieved | ||
from the environment. Or, the retrieved credentials do not | ||
have access to the project (self.project_id) on BigQuery. | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a Returns section. Explaining that it will return the credentials or None if not found / error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explain the conditions under which this will get the correct credentials |
||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are these libararies a newer / different version that we are currently importing? |
||
from googleapiclient.discovery import build | ||
except ImportError: | ||
from apiclient.discovery import build | ||
from oauth2client.client import GoogleCredentials | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move inside of |
||
|
||
try: | ||
credentials = GoogleCredentials.get_application_default() | ||
except: | ||
return None | ||
|
||
# Check if the application has rights to the BigQuery project | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank line |
||
bigquery_service = build('bigquery', 'v2', credentials=credentials) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs to be in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @parthea I've changed the call to the "build" method - should work for both api versions now. |
||
jobs = bigquery_service.jobs() | ||
job_data = {'configuration': {'query': {'query': 'SELECT 1'}}} | ||
try: | ||
jobs.insert(projectId=self.project_id, body=job_data).execute() | ||
return credentials | ||
except: | ||
return None | ||
|
||
def get_user_account_credentials(self): | ||
from oauth2client.client import OAuth2WebServerFlow | ||
|
@@ -578,10 +626,16 @@ def read_gbq(query, project_id=None, index_col=None, col_order=None, | |
https://developers.google.com/api-client-library/python/apis/bigquery/v2 | ||
|
||
Authentication to the Google BigQuery service is via OAuth 2.0. | ||
By default user account credentials are used. You will be asked to | ||
grant permissions for product name 'pandas GBQ'. It is also posible | ||
to authenticate via service account credentials by using | ||
private_key parameter. | ||
- If "private_key" is not provided: | ||
By default "application default credentials" are used. | ||
|
||
.. versionadded:: 0.19.0 | ||
|
||
If default application credentials are not found or are restrictive, | ||
user account credentials are used. In this case, you will be asked to | ||
grant permissions for product name 'pandas GBQ'. | ||
- If "private_key" is provided: | ||
Service account credentials will be used to authenticate. | ||
|
||
Parameters | ||
---------- | ||
|
@@ -689,10 +743,16 @@ def to_gbq(dataframe, destination_table, project_id, chunksize=10000, | |
https://developers.google.com/api-client-library/python/apis/bigquery/v2 | ||
|
||
Authentication to the Google BigQuery service is via OAuth 2.0. | ||
By default user account credentials are used. You will be asked to | ||
grant permissions for product name 'pandas GBQ'. It is also posible | ||
to authenticate via service account credentials by using | ||
private_key parameter. | ||
- If "private_key" is not provided: | ||
By default "application default credentials" are used. | ||
|
||
.. versionadded:: 0.19.0 | ||
|
||
If default application credentials are not found or are restrictive, | ||
user account credentials are used. In this case, you will be asked to | ||
grant permissions for product name 'pandas GBQ'. | ||
- If "private_key" is provided: | ||
Service account credentials will be used to authenticate. | ||
|
||
Parameters | ||
---------- | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,6 +80,7 @@ def _test_imports(): | |
from apiclient.discovery import build # noqa | ||
from apiclient.errors import HttpError # noqa | ||
|
||
from oauth2client.client import GoogleCredentials # noqa | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this inside of |
||
from oauth2client.client import OAuth2WebServerFlow # noqa | ||
from oauth2client.client import AccessTokenRefreshError # noqa | ||
|
||
|
@@ -151,6 +152,27 @@ def test_requirements(): | |
raise nose.SkipTest(import_exception) | ||
|
||
|
||
def _check_if_can_get_correct_default_credentials(): | ||
# Checks if "Application Default Credentials" can be fetched | ||
# from the environment the tests are running in. | ||
# See Issue #13577 | ||
test_requirements() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a 1-liner what this does as a comment (and the issue number) |
||
from oauth2client.client import GoogleCredentials | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
try: | ||
from googleapiclient.discovery import build | ||
except ImportError: | ||
from apiclient.discovery import build | ||
try: | ||
credentials = GoogleCredentials.get_application_default() | ||
bigquery_service = build('bigquery', 'v2', credentials=credentials) | ||
jobs = bigquery_service.jobs() | ||
job_data = {'configuration': {'query': {'query': 'SELECT 1'}}} | ||
jobs.insert(projectId=PROJECT_ID, body=job_data).execute() | ||
return True | ||
except: | ||
return False | ||
|
||
|
||
def clean_gbq_environment(private_key=None): | ||
dataset = gbq._Dataset(PROJECT_ID, private_key=private_key) | ||
|
||
|
@@ -217,6 +239,21 @@ def test_should_be_able_to_get_results_from_query(self): | |
schema, pages = self.sut.run_query('SELECT 1') | ||
self.assertTrue(pages is not None) | ||
|
||
def test_get_application_default_credentials_does_not_throw_error(self): | ||
if _check_if_can_get_correct_default_credentials(): | ||
raise nose.SkipTest("Can get default_credentials " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok lgtm. |
||
"from the environment!") | ||
credentials = self.sut.get_application_default_credentials() | ||
self.assertIsNone(credentials) | ||
|
||
def test_get_application_default_credentials_returns_credentials(self): | ||
if not _check_if_can_get_correct_default_credentials(): | ||
raise nose.SkipTest("Cannot get default_credentials " | ||
"from the environment!") | ||
from oauth2client.client import GoogleCredentials | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so you need to do skipping based on resource availablity, see the other tests. |
||
credentials = self.sut.get_application_default_credentials() | ||
self.assertTrue(isinstance(credentials, GoogleCredentials)) | ||
|
||
|
||
class TestGBQConnectorServiceAccountKeyPathIntegration(tm.TestCase): | ||
def setUp(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to be below the change