Skip to content
This repository has been archived by the owner on Nov 29, 2023. It is now read-only.

feat: add client_cert_source_for_mtls parameter to grpc transport #114

Merged
merged 9 commits into from Mar 17, 2021
59 changes: 10 additions & 49 deletions README.rst
@@ -1,35 +1,18 @@
Python Client for BigQuery Data Transfer API
============================================

|GA| |pypi| |versions|

The `BigQuery Data Transfer API`_ allows users to transfer data from partner
SaaS applications to Google BigQuery on a scheduled, managed basis.

- `Client Library Documentation`_
- `Product Documentation`_

.. |GA| image:: https://img.shields.io/badge/support-GA-gold.svg
:target: https://github.com/googleapis/google-cloud-python/blob/master/README.rst#general-availability
.. |pypi| image:: https://img.shields.io/pypi/v/google-cloud-bigquery-datatransfer.svg
:target: https://pypi.org/project/google-cloud-bigquery-datatransfer/
.. |versions| image:: https://img.shields.io/pypi/pyversions/google-cloud-bigquery-datatransfer.svg
:target: https://pypi.org/project/google-cloud-bigquery-datatransfer/
.. _BigQuery Data Transfer API: https://cloud.google.com/bigquery/transfer
.. _Client Library Documentation: https://googleapis.dev/python/bigquerydatatransfer/latest
.. _Product Documentation: https://cloud.google.com/bigquery/docs/transfer-service-overview
Python Client for Google Cloud Bigquery Datatransfer API
=================================================

Quick Start
-----------

In order to use this library, you first need to go through the following steps:

1. `Select or create a Cloud Platform project.`_
2. `Enable the BigQuery Data Transfer API.`_
3. `Setup Authentication.`_
2. `Enable billing for your project.`_
3. Enable the Google Cloud Bigquery Datatransfer API.
4. `Setup Authentication.`_

.. _Select or create a Cloud Platform project.: https://console.cloud.google.com/project
.. _Enable the BigQuery Data Transfer API.: https://cloud.google.com/bigquery/docs/transfer-service-overview
.. _Enable billing for your project.: https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_a_project
.. _Setup Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html

Installation
Expand All @@ -46,43 +29,21 @@ dependencies.
.. _`virtualenv`: https://virtualenv.pypa.io/en/latest/


Supported Python Versions
^^^^^^^^^^^^^^^^^^^^^^^^^
Python >= 3.6

Deprecated Python Versions
^^^^^^^^^^^^^^^^^^^^^^^^^^
Python == 2.7.

The last version of this library compatible with Python 2.7 is
``google-cloud-bigquery-datatransfer==1.1.1``.


Mac/Linux
^^^^^^^^^

.. code-block:: console

pip install virtualenv
virtualenv <your-env>
python3 -m venv <your-env>
source <your-env>/bin/activate
<your-env>/bin/pip install google-cloud-bigquery-datatransfer
<your-env>/bin/pip install /path/to/library


Windows
^^^^^^^

.. code-block:: console

pip install virtualenv
virtualenv <your-env>
python3 -m venv <your-env>
<your-env>\Scripts\activate
<your-env>\Scripts\pip.exe install google-cloud-bigquery-datatransfer

Next Steps
~~~~~~~~~~

- Read the `Client Library Documentation`_ for BigQuery Data Transfer API
API to see other available methods on the client.
- Read the `Product documentation`_ to learn
more about the product and see How-to Guides.
<your-env>\Scripts\pip.exe install \path\to\library
Expand Up @@ -94,8 +94,36 @@ class DataTransferServiceAsyncClient:
DataTransferServiceClient.parse_common_location_path
)

from_service_account_info = DataTransferServiceClient.from_service_account_info
from_service_account_file = DataTransferServiceClient.from_service_account_file
@classmethod
def from_service_account_info(cls, info: dict, *args, **kwargs):
"""Creates an instance of this client using the provided credentials info.

Args:
info (dict): The service account private key info.
args: Additional arguments to pass to the constructor.
kwargs: Additional arguments to pass to the constructor.

Returns:
DataTransferServiceAsyncClient: The constructed client.
"""
return DataTransferServiceClient.from_service_account_info.__func__(DataTransferServiceAsyncClient, info, *args, **kwargs) # type: ignore

@classmethod
def from_service_account_file(cls, filename: str, *args, **kwargs):
"""Creates an instance of this client using the provided credentials
file.

Args:
filename (str): The path to the service account private key json
file.
args: Additional arguments to pass to the constructor.
kwargs: Additional arguments to pass to the constructor.

Returns:
DataTransferServiceAsyncClient: The constructed client.
"""
return DataTransferServiceClient.from_service_account_file.__func__(DataTransferServiceAsyncClient, filename, *args, **kwargs) # type: ignore

from_service_account_json = from_service_account_file

@property
Expand Down
Expand Up @@ -328,21 +328,17 @@ def __init__(
util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"))
)

ssl_credentials = None
client_cert_source_func = None
is_mtls = False
if use_client_cert:
if client_options.client_cert_source:
import grpc # type: ignore

cert, key = client_options.client_cert_source()
ssl_credentials = grpc.ssl_channel_credentials(
certificate_chain=cert, private_key=key
)
is_mtls = True
client_cert_source_func = client_options.client_cert_source
else:
creds = SslCredentials()
is_mtls = creds.is_mtls
ssl_credentials = creds.ssl_credentials if is_mtls else None
is_mtls = mtls.has_default_client_cert_source()
client_cert_source_func = (
mtls.default_client_cert_source() if is_mtls else None
)

# Figure out which api endpoint to use.
if client_options.api_endpoint is not None:
Expand Down Expand Up @@ -385,7 +381,7 @@ def __init__(
credentials_file=client_options.credentials_file,
host=api_endpoint,
scopes=client_options.scopes,
ssl_channel_credentials=ssl_credentials,
client_cert_source_for_mtls=client_cert_source_func,
quota_project_id=client_options.quota_project_id,
client_info=client_info,
)
Expand Down
Expand Up @@ -15,7 +15,16 @@
# limitations under the License.
#

from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple
from typing import (
Any,
AsyncIterable,
Awaitable,
Callable,
Iterable,
Sequence,
Tuple,
Optional,
)

from google.cloud.bigquery_datatransfer_v1.types import datatransfer
from google.cloud.bigquery_datatransfer_v1.types import transfer
Expand Down
Expand Up @@ -62,6 +62,7 @@ def __init__(
api_mtls_endpoint: str = None,
client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
ssl_channel_credentials: grpc.ChannelCredentials = None,
client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None,
quota_project_id: Optional[str] = None,
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
Expand Down Expand Up @@ -92,6 +93,10 @@ def __init__(
``api_mtls_endpoint`` is None.
ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
for grpc channel. It is ignored if ``channel`` is provided.
client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
A callback to provide client certificate bytes and private key bytes,
both in PEM format. It is used to configure mutual TLS channel. It is
ignored if ``channel`` or ``ssl_channel_credentials`` is provided.
quota_project_id (Optional[str]): An optional project to use for billing
and quota.
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
Expand All @@ -108,6 +113,11 @@ def __init__(
"""
self._ssl_channel_credentials = ssl_channel_credentials

if api_mtls_endpoint:
warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
if client_cert_source:
warnings.warn("client_cert_source is deprecated", DeprecationWarning)

if channel:
# Sanity check: Ensure that channel and credentials are not both
# provided.
Expand All @@ -117,11 +127,6 @@ def __init__(
self._grpc_channel = channel
self._ssl_channel_credentials = None
elif api_mtls_endpoint:
warnings.warn(
"api_mtls_endpoint and client_cert_source are deprecated",
DeprecationWarning,
)

host = (
api_mtls_endpoint
if ":" in api_mtls_endpoint
Expand Down Expand Up @@ -165,12 +170,18 @@ def __init__(
scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id
)

if client_cert_source_for_mtls and not ssl_channel_credentials:
cert, key = client_cert_source_for_mtls()
self._ssl_channel_credentials = grpc.ssl_channel_credentials(
certificate_chain=cert, private_key=key
)

# create a new channel. The provided one is ignored.
self._grpc_channel = type(self).create_channel(
host,
credentials=credentials,
credentials_file=credentials_file,
ssl_credentials=ssl_channel_credentials,
ssl_credentials=self._ssl_channel_credentials,
scopes=scopes or self.AUTH_SCOPES,
quota_project_id=quota_project_id,
options=[
Expand Down
Expand Up @@ -106,6 +106,7 @@ def __init__(
api_mtls_endpoint: str = None,
client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
ssl_channel_credentials: grpc.ChannelCredentials = None,
client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None,
quota_project_id=None,
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
Expand Down Expand Up @@ -137,6 +138,10 @@ def __init__(
``api_mtls_endpoint`` is None.
ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
for grpc channel. It is ignored if ``channel`` is provided.
client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]):
A callback to provide client certificate bytes and private key bytes,
both in PEM format. It is used to configure mutual TLS channel. It is
ignored if ``channel`` or ``ssl_channel_credentials`` is provided.
quota_project_id (Optional[str]): An optional project to use for billing
and quota.
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
Expand All @@ -153,6 +158,11 @@ def __init__(
"""
self._ssl_channel_credentials = ssl_channel_credentials

if api_mtls_endpoint:
warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning)
if client_cert_source:
warnings.warn("client_cert_source is deprecated", DeprecationWarning)

if channel:
# Sanity check: Ensure that channel and credentials are not both
# provided.
Expand All @@ -162,11 +172,6 @@ def __init__(
self._grpc_channel = channel
self._ssl_channel_credentials = None
elif api_mtls_endpoint:
warnings.warn(
"api_mtls_endpoint and client_cert_source are deprecated",
DeprecationWarning,
)

host = (
api_mtls_endpoint
if ":" in api_mtls_endpoint
Expand Down Expand Up @@ -210,12 +215,18 @@ def __init__(
scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id
)

if client_cert_source_for_mtls and not ssl_channel_credentials:
cert, key = client_cert_source_for_mtls()
self._ssl_channel_credentials = grpc.ssl_channel_credentials(
certificate_chain=cert, private_key=key
)

# create a new channel. The provided one is ignored.
self._grpc_channel = type(self).create_channel(
host,
credentials=credentials,
credentials_file=credentials_file,
ssl_credentials=ssl_channel_credentials,
ssl_credentials=self._ssl_channel_credentials,
scopes=scopes or self.AUTH_SCOPES,
quota_project_id=quota_project_id,
options=[
Expand Down