Skip to content

Commit b87744e

Browse files
dcfocuscaithagoras0
authored andcommitted
Add support for Oauth to enable GCS access
1 parent 54e7c03 commit b87744e

File tree

6 files changed

+73
-5
lines changed

6 files changed

+73
-5
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ python:
33
- "2.7"
44
- "3.5"
55
- "3.6"
6-
- "pypy-5.4"
6+
- "3.7"
7+
- "pypy3.6-7.0.0"
78
env:
89
- PRESTO_VERSION=0.202
910
services:

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Introduction
44

55
This package provides a client interface to query [Presto](https://prestodb.io/)
6-
a distributed SQL engine. It supports Python 2.7, 3.5, 3.6, and pypy.
6+
a distributed SQL engine. It supports Python 2.7, 3.5, 3.6, 3.7, and pypy.
77

88
# Installation
99

@@ -56,6 +56,36 @@ cur.execute('SELECT * FROM system.runtime.nodes')
5656
rows = cur.fetchall()
5757
```
5858

59+
# Oauth Authentication
60+
To enable GCS access, Oauth authentication support is added by passing in a `shadow.json` file of a service account.
61+
Following example shows a use case where both Kerberos and Oauth authentication are enabled.
62+
63+
```python
64+
import getpass
65+
import prestodb
66+
from prestodb.client import PrestoRequest, PrestoQuery
67+
from requests_kerberos import DISABLED
68+
69+
kerberos_auth = prestodb.auth.KerberosAuthentication(
70+
mutual_authentication=DISABLED,
71+
service_name='kerberos service name',
72+
force_preemptive=True,
73+
hostname_override='example.com'
74+
)
75+
76+
req = PrestoRequest(
77+
host='GCP coordinator url',
78+
port=443,
79+
user=getpass.getuser(),
80+
service_account_file='Service account json file path',
81+
http_scheme='https',
82+
auth=kerberos_auth
83+
)
84+
85+
query = PrestoQuery(req, "SELECT * FROM system.runtime.nodes")
86+
rows = list(query.execute())
87+
```
88+
5989
# Transactions
6090
The client runs by default in *autocommit* mode. To enable transactions, set
6191
*isolation_level* to a value different than `IsolationLevel.AUTOCOMMIT`:

prestodb/client.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def __init__(
209209
max_attempts=MAX_ATTEMPTS, # type: int
210210
request_timeout=constants.DEFAULT_REQUEST_TIMEOUT, # type: Union[float, Tuple[float, float]]
211211
handle_retry=exceptions.RetryWithExponentialBackoff(),
212+
service_account_file=None,
212213
):
213214
# type: (...) -> None
214215
self._client_session = ClientSession(
@@ -230,6 +231,19 @@ def __init__(
230231
else:
231232
# mypy cannot follow module import
232233
self._http_session = self.http.Session() # type: ignore
234+
235+
self.credentials = None
236+
self.auth_req = None
237+
if service_account_file is not None:
238+
import google.auth.transport.requests
239+
from google.oauth2 import service_account
240+
241+
self.auth_req = google.auth.transport.requests.Request()
242+
self.credentials = service_account.Credentials.from_service_account_file(
243+
service_account_file, scopes=[constants.GCS_READ_ONLY]
244+
)
245+
self._http_session.headers.update(self.get_oauth_token())
246+
233247
self._http_session.headers.update(self.http_headers)
234248
self._exceptions = self.HTTP_EXCEPTIONS
235249
self._auth = auth
@@ -422,6 +436,17 @@ def process(self, http_response):
422436
columns=response.get("columns"),
423437
)
424438

439+
@property
440+
def http_session(self):
441+
return self._http_session
442+
443+
def get_oauth_token(self):
444+
self.credentials.refresh(self.auth_req)
445+
return {
446+
constants.PRESTO_EXTRA_CREDENTIAL: "%s = %s"
447+
% (constants.GCS_CREDENTIALS_OAUTH_TOKEN_KEY, self.credentials.token)
448+
}
449+
425450

426451
class PrestoResult(object):
427452
"""
@@ -466,12 +491,13 @@ def __init__(
466491
sql, # type: Text
467492
):
468493
# type: (...) -> None
494+
self.auth_req = request.auth_req # type: Optional[Request]
495+
self.credentials = request.credentials # type: Optional[Credentials]
469496
self.query_id = None # type: Optional[Text]
470497

471498
self._stats = {} # type: Dict[Any, Any]
472499
self._warnings = [] # type: List[Dict[Any, Any]]
473500
self._columns = None # type: Optional[List[Text]]
474-
475501
self._finished = False
476502
self._cancelled = False
477503
self._request = request
@@ -506,6 +532,9 @@ def execute(self):
506532
if self._cancelled:
507533
raise exceptions.PrestoUserError("Query has been cancelled", self.query_id)
508534

535+
if self.credentials is not None and not self.credentials.valid:
536+
self._request.http_session.headers.update(self._request.get_oauth_token())
537+
509538
response = self._request.post(self._sql)
510539
status = self._request.process(response)
511540
self.query_id = status.id

prestodb/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,8 @@
4141

4242
HEADER_STARTED_TRANSACTION = HEADER_PREFIX + "Started-Transaction-Id"
4343
HEADER_TRANSACTION = HEADER_PREFIX + "Transaction-Id"
44+
45+
PRESTO_EXTRA_CREDENTIAL = "X-Presto-Extra-Credential"
46+
GCS_CREDENTIALS_OAUTH_TOKEN_KEY = "hive.gcs.oauth"
47+
48+
GCS_READ_ONLY = "https://www.googleapis.com/auth/devstorage.read_only"

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727

2828
kerberos_require = ["requests_kerberos"]
2929

30-
all_require = [kerberos_require]
30+
google_auth_require = ["google_auth"]
31+
32+
all_require = [kerberos_require, google_auth_require]
3133

3234
tests_require = all_require + ["httpretty", "pytest", "pytest-runner"]
3335

@@ -70,6 +72,7 @@
7072
extras_require={
7173
"all": all_require,
7274
"kerberos": kerberos_require,
75+
"google_auth": google_auth_require,
7376
"tests": tests_require,
7477
':python_version=="2.7"': py27_require,
7578
},

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27,py35,py36,pypy2
2+
envlist = py27,py35,py36,py37,pypy2
33

44
[testenv]
55
deps = pytest

0 commit comments

Comments
 (0)