From 669d2a985877971fb6c1eb0ad97806fbcfcc7399 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Wed, 27 May 2020 22:55:53 -0700 Subject: [PATCH] feat: add mtls support (#7) --- docs/conf.py | 5 +- .../services/cloud_memcache/client.py | 92 +++++++- .../cloud_memcache/transports/grpc.py | 55 ++++- mypy.ini | 2 +- setup.py | 4 +- synth.metadata | 11 +- .../memcache_v1beta2/test_cloud_memcache.py | 205 +++++++++++++++++- 7 files changed, 338 insertions(+), 36 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cf879bf..54a5d17 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,6 +38,7 @@ "sphinx.ext.napoleon", "sphinx.ext.todo", "sphinx.ext.viewcode", + "recommonmark", ] # autodoc/autosummary flags @@ -49,10 +50,6 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -# Allow markdown includes (so releases.md can include CHANGLEOG.md) -# http://www.sphinx-doc.org/en/master/markdown.html -source_parsers = {".md": "recommonmark.parser.CommonMarkParser"} - # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] diff --git a/google/cloud/memcache_v1beta2/services/cloud_memcache/client.py b/google/cloud/memcache_v1beta2/services/cloud_memcache/client.py index 74c3cc2..19c6bde 100644 --- a/google/cloud/memcache_v1beta2/services/cloud_memcache/client.py +++ b/google/cloud/memcache_v1beta2/services/cloud_memcache/client.py @@ -16,7 +16,8 @@ # from collections import OrderedDict -from typing import Dict, Sequence, Tuple, Type, Union +import re +from typing import Callable, Dict, Sequence, Tuple, Type, Union import pkg_resources import google.api_core.client_options as ClientOptions # type: ignore @@ -90,8 +91,38 @@ class CloudMemcacheClient(metaclass=CloudMemcacheClientMeta): - ``projects/my-memcached-project/locations/us-central1/instances/my-memcached`` """ - DEFAULT_OPTIONS = ClientOptions.ClientOptions( - api_endpoint="memcache.googleapis.com" + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "memcache.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT ) @classmethod @@ -121,12 +152,21 @@ def instance_path(project: str, location: str, instance: str) -> str: project=project, location=location, instance=instance ) + @staticmethod + def parse_instance_path(path: str) -> Dict[str, str]: + """Parse a instance path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/locations/(?P.+?)/instances/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + def __init__( self, *, credentials: credentials.Credentials = None, transport: Union[str, CloudMemcacheTransport] = None, - client_options: ClientOptions = DEFAULT_OPTIONS, + client_options: ClientOptions = None, ) -> None: """Instantiate the cloud memcache client. @@ -140,6 +180,17 @@ def __init__( transport to use. If set to None, a transport is chosen automatically. client_options (ClientOptions): Custom options for the client. + (1) The ``api_endpoint`` property can be used to override the + default endpoint provided by the client. + (2) If ``transport`` argument is None, ``client_options`` can be + used to create a mutual TLS transport. If ``client_cert_source`` + is provided, mutual TLS transport will be created with the given + ``api_endpoint`` or the default mTLS endpoint, and the client + SSL credentials obtained from ``client_cert_source``. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. """ if isinstance(client_options, dict): client_options = ClientOptions.from_dict(client_options) @@ -148,17 +199,46 @@ def __init__( # Ordinarily, we provide the transport, but allowing a custom transport # instance provides an extensibility point for unusual situations. if isinstance(transport, CloudMemcacheTransport): + # transport is a CloudMemcacheTransport instance. if credentials: raise ValueError( "When providing a transport instance, " "provide its credentials directly." ) self._transport = transport - else: + elif client_options is None or ( + client_options.api_endpoint is None + and client_options.client_cert_source is None + ): + # Don't trigger mTLS if we get an empty ClientOptions. Transport = type(self).get_transport_class(transport) self._transport = Transport( + credentials=credentials, host=self.DEFAULT_ENDPOINT + ) + else: + # We have a non-empty ClientOptions. If client_cert_source is + # provided, trigger mTLS with user provided endpoint or the default + # mTLS endpoint. + if client_options.client_cert_source: + api_mtls_endpoint = ( + client_options.api_endpoint + if client_options.api_endpoint + else self.DEFAULT_MTLS_ENDPOINT + ) + else: + api_mtls_endpoint = None + + api_endpoint = ( + client_options.api_endpoint + if client_options.api_endpoint + else self.DEFAULT_ENDPOINT + ) + + self._transport = CloudMemcacheGrpcTransport( credentials=credentials, - host=client_options.api_endpoint or "memcache.googleapis.com", + host=api_endpoint, + api_mtls_endpoint=api_mtls_endpoint, + client_cert_source=client_options.client_cert_source, ) def list_instances( diff --git a/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/grpc.py b/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/grpc.py index 8c7ffad..0d09f79 100644 --- a/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/grpc.py +++ b/google/cloud/memcache_v1beta2/services/cloud_memcache/transports/grpc.py @@ -15,11 +15,13 @@ # limitations under the License. # -from typing import Callable, Dict +from typing import Callable, Dict, Tuple from google.api_core import grpc_helpers # type: ignore from google.api_core import operations_v1 # type: ignore from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + import grpc # type: ignore @@ -66,7 +68,9 @@ def __init__( *, host: str = "memcache.googleapis.com", credentials: credentials.Credentials = None, - channel: grpc.Channel = None + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None ) -> None: """Instantiate the transport. @@ -80,20 +84,55 @@ def __init__( This argument is ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. + api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If + provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A + callback to provide client SSL certificate bytes and private key + bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` + is None. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. """ - # Sanity check: Ensure that channel and credentials are not both - # provided. if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. credentials = False + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = grpc_helpers.create_channel( + host, + credentials=credentials, + ssl_credentials=ssl_credentials, + scopes=self.AUTH_SCOPES, + ) + # Run the base constructor. super().__init__(host=host, credentials=credentials) self._stubs = {} # type: Dict[str, Callable] - # If a channel was explicitly provided, set it. - if channel: - self._grpc_channel = channel - @classmethod def create_channel( cls, diff --git a/mypy.ini b/mypy.ini index f23e6b5..4505b48 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,3 +1,3 @@ [mypy] -python_version = 3.5 +python_version = 3.6 namespace_packages = True diff --git a/setup.py b/setup.py index ce5edb1..21fc0e3 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,7 @@ platforms="Posix; MacOS X; Windows", include_package_data=True, install_requires=( - "google-api-core >= 1.8.0, < 2.0.0dev", - "googleapis-common-protos >= 1.5.8", - "grpcio >= 1.10.0", + "google-api-core[grpc] >= 1.17.0, < 2.0.0dev", "proto-plus >= 0.4.0", ), python_requires=">=3.6", diff --git a/synth.metadata b/synth.metadata index fe8a790..515dba6 100644 --- a/synth.metadata +++ b/synth.metadata @@ -1,13 +1,18 @@ { - "updateTime": "2020-03-25T12:13:46.329877Z", "sources": [ + { + "git": { + "name": ".", + "remote": "https://github.com/googleapis/python-memcache.git", + "sha": "247ad6661e64d32fc4ba83b65b8f1562748dabe0" + } + }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", "sha": "551cf1e6e3addcc63740427c4f9b40dedd3dac27", - "internalRef": "302792195", - "log": "551cf1e6e3addcc63740427c4f9b40dedd3dac27\nfeat: Add OS Config AgentEndpointService v1 PatchJobs and Tasks APIs.\n\nPiperOrigin-RevId: 302792195\n\n1df117114c73299b614dfd3ba3632bf246669336\nSynchronize new proto/yaml changes.\n\nPiperOrigin-RevId: 302753982\n\n71d6c56a14bb433beb1237dccb48dabcd9597924\nRefresh monitoring client libraries.\nRename to Cloud Monitoring API.\nAdded support for TimeSeriesQueryLanguageCondition condition type in alert policies.\n\nPiperOrigin-RevId: 302735422\n\n25a1781c096974df99d556cc5888fefa82bc6425\nbazel: migrate all go_gapic_library targets to microgenerator implementation\n\n* update rules_go and gazelle bazel dependencies\n* update gapic-generator bazel dependency (with build file generator changes)\n\nPiperOrigin-RevId: 302730217\n\n36c0febd0fa7267ab66d14408eec2afd1b6bec4e\nUpdate GAPIC configurations to v2 .yaml.\n\nPiperOrigin-RevId: 302639621\n\n078f222366ed344509a48f2f084944ef61476613\nFix containeranalysis v1beta1 assembly target name\n\nPiperOrigin-RevId: 302529186\n\n0be7105dc52590fa9a24e784052298ae37ce53aa\nAdd BUILD.bazel file to asset/v1p1beta1\n\nPiperOrigin-RevId: 302154871\n\n6c248fd13e8543f8d22cbf118d978301a9fbe2a8\nAdd missing resource annotations and additional_bindings to dialogflow v2 API.\n\nPiperOrigin-RevId: 302063117\n\n9a3a7f33be9eeacf7b3e98435816b7022d206bd7\nChange the service name from \"chromeos-moblab.googleapis.com\" to \"chromeosmoblab.googleapis.com\"\n\nPiperOrigin-RevId: 302060989\n\n98a339237577e3de26cb4921f75fb5c57cc7a19f\nfeat: devtools/build/v1 publish client library config annotations\n\n* add details field to some of the BuildEvents\n* add final_invocation_id and build_tool_exit_code fields to BuildStatus\n\nPiperOrigin-RevId: 302044087\n\ncfabc98c6bbbb22d1aeaf7612179c0be193b3a13\nfeat: home/graph/v1 publish client library config annotations & comment updates\n\nThis change includes adding the client library configuration annotations, updated proto comments, and some client library configuration files.\n\nPiperOrigin-RevId: 302042647\n\nc8c8c0bd15d082db9546253dbaad1087c7a9782c\nchore: use latest gapic-generator in bazel WORKSPACE.\nincluding the following commits from gapic-generator:\n- feat: take source protos in all sub-packages (#3144)\n\nPiperOrigin-RevId: 301843591\n\ne4daf5202ea31cb2cb6916fdbfa9d6bd771aeb4c\nAdd bazel file for v1 client lib generation\n\nPiperOrigin-RevId: 301802926\n\n275fbcce2c900278d487c33293a3c7e1fbcd3a34\nfeat: pubsub/v1 add an experimental filter field to Subscription\n\nPiperOrigin-RevId: 301661567\n\nf2b18cec51d27c999ad30011dba17f3965677e9c\nFix: UpdateBackupRequest.backup is a resource, not a resource reference - remove annotation.\n\nPiperOrigin-RevId: 301636171\n\n800384063ac93a0cac3a510d41726fa4b2cd4a83\nCloud Billing Budget API v1beta1\nModified api documentation to include warnings about the new filter field.\n\nPiperOrigin-RevId: 301634389\n\n0cc6c146b660db21f04056c3d58a4b752ee445e3\nCloud Billing Budget API v1alpha1\nModified api documentation to include warnings about the new filter field.\n\nPiperOrigin-RevId: 301630018\n\nff2ea00f69065585c3ac0993c8b582af3b6fc215\nFix: Add resource definition for a parent of InspectTemplate which was otherwise missing.\n\nPiperOrigin-RevId: 301623052\n\n55fa441c9daf03173910760191646399338f2b7c\nAdd proto definition for AccessLevel, AccessPolicy, and ServicePerimeter.\n\nPiperOrigin-RevId: 301620844\n\ne7b10591c5408a67cf14ffafa267556f3290e262\nCloud Bigtable Managed Backup service and message proto files.\n\nPiperOrigin-RevId: 301585144\n\nd8e226f702f8ddf92915128c9f4693b63fb8685d\nfeat: Add time-to-live in a queue for builds\n\nPiperOrigin-RevId: 301579876\n\n430375af011f8c7a5174884f0d0e539c6ffa7675\ndocs: add missing closing backtick\n\nPiperOrigin-RevId: 301538851\n\n0e9f1f60ded9ad1c2e725e37719112f5b487ab65\nbazel: Use latest release of gax_java\n\nPiperOrigin-RevId: 301480457\n\n5058c1c96d0ece7f5301a154cf5a07b2ad03a571\nUpdate GAPIC v2 with batching parameters for Logging API\n\nPiperOrigin-RevId: 301443847\n\n64ab9744073de81fec1b3a6a931befc8a90edf90\nFix: Introduce location-based organization/folder/billing-account resources\nChore: Update copyright years\n\nPiperOrigin-RevId: 301373760\n\n23d5f09e670ebb0c1b36214acf78704e2ecfc2ac\nUpdate field_behavior annotations in V1 and V2.\n\nPiperOrigin-RevId: 301337970\n\nb2cf37e7fd62383a811aa4d54d013ecae638851d\nData Catalog V1 API\n\nPiperOrigin-RevId: 301282503\n\n1976b9981e2900c8172b7d34b4220bdb18c5db42\nCloud DLP api update. Adds missing fields to Finding and adds support for hybrid jobs.\n\nPiperOrigin-RevId: 301205325\n\nae78682c05e864d71223ce22532219813b0245ac\nfix: several sample code blocks in comments are now properly indented for markdown\n\nPiperOrigin-RevId: 301185150\n\ndcd171d04bda5b67db13049320f97eca3ace3731\nPublish Media Translation API V1Beta1\n\nPiperOrigin-RevId: 301180096\n\nff1713453b0fbc5a7544a1ef6828c26ad21a370e\nAdd protos and BUILD rules for v1 API.\n\nPiperOrigin-RevId: 301179394\n\n8386761d09819b665b6a6e1e6d6ff884bc8ff781\nfeat: chromeos/modlab publish protos and config for Chrome OS Moblab API.\n\nPiperOrigin-RevId: 300843960\n\nb2e2bc62fab90e6829e62d3d189906d9b79899e4\nUpdates to GCS gRPC API spec:\n\n1. Changed GetIamPolicy and TestBucketIamPermissions to use wrapper messages around google.iam.v1 IAM requests messages, and added CommonRequestParams. This lets us support RequesterPays buckets.\n2. Added a metadata field to GetObjectMediaResponse, to support resuming an object media read safely (by extracting the generation of the object being read, and using it in the resumed read request).\n\nPiperOrigin-RevId: 300817706\n\n7fd916ce12335cc9e784bb9452a8602d00b2516c\nAdd deprecated_collections field for backward-compatiblity in PHP and monolith-generated Python and Ruby clients.\n\nGenerate TopicName class in Java which covers the functionality of both ProjectTopicName and DeletedTopicName. Introduce breaking changes to be fixed by synth.py.\n\nDelete default retry parameters.\n\nRetry codes defs can be deleted once # https://github.com/googleapis/gapic-generator/issues/3137 is fixed.\n\nPiperOrigin-RevId: 300813135\n\n047d3a8ac7f75383855df0166144f891d7af08d9\nfix!: google/rpc refactor ErrorInfo.type to ErrorInfo.reason and comment updates.\n\nPiperOrigin-RevId: 300773211\n\nfae4bb6d5aac52aabe5f0bb4396466c2304ea6f6\nAdding RetryPolicy to pubsub.proto\n\nPiperOrigin-RevId: 300769420\n\n7d569be2928dbd72b4e261bf9e468f23afd2b950\nAdding additional protocol buffer annotations to v3.\n\nPiperOrigin-RevId: 300718800\n\n13942d1a85a337515040a03c5108993087dc0e4f\nAdd logging protos for Recommender v1.\n\nPiperOrigin-RevId: 300689896\n\na1a573c3eecfe2c404892bfa61a32dd0c9fb22b6\nfix: change go package to use cloud.google.com/go/maps\n\nPiperOrigin-RevId: 300661825\n\nc6fbac11afa0c7ab2972d9df181493875c566f77\nfeat: publish documentai/v1beta2 protos\n\nPiperOrigin-RevId: 300656808\n\n5202a9e0d9903f49e900f20fe5c7f4e42dd6588f\nProtos for v1beta1 release of Cloud Security Center Settings API\n\nPiperOrigin-RevId: 300580858\n\n83518e18655d9d4ac044acbda063cc6ecdb63ef8\nAdds gapic.yaml file and BUILD.bazel file.\n\nPiperOrigin-RevId: 300554200\n\n836c196dc8ef8354bbfb5f30696bd3477e8db5e2\nRegenerate recommender v1beta1 gRPC ServiceConfig file for Insights methods.\n\nPiperOrigin-RevId: 300549302\n\n34a5450c591b6be3d6566f25ac31caa5211b2f3f\nIncreases the default timeout from 20s to 30s for MetricService\n\nPiperOrigin-RevId: 300474272\n\n5d8bffe87cd01ba390c32f1714230e5a95d5991d\nfeat: use the latest gapic-generator in WORKSPACE for bazel build.\n\nPiperOrigin-RevId: 300461878\n\nd631c651e3bcfac5d371e8560c27648f7b3e2364\nUpdated the GAPIC configs to include parameters for Backups APIs.\n\nPiperOrigin-RevId: 300443402\n\n678afc7055c1adea9b7b54519f3bdb228013f918\nAdding Game Servers v1beta API.\n\nPiperOrigin-RevId: 300433218\n\n80d2bd2c652a5e213302041b0620aff423132589\nEnable proto annotation and gapic v2 for talent API.\n\nPiperOrigin-RevId: 300393997\n\n85e454be7a353f7fe1bf2b0affb753305785b872\ndocs(google/maps/roads): remove mention of nonexported api\n\nPiperOrigin-RevId: 300367734\n\nbf839ae632e0f263a729569e44be4b38b1c85f9c\nAdding protocol buffer annotations and updated config info for v1 and v2.\n\nPiperOrigin-RevId: 300276913\n\n309b899ca18a4c604bce63882a161d44854da549\nPublish `Backup` APIs and protos.\n\nPiperOrigin-RevId: 300246038\n\neced64c3f122421350b4aca68a28e89121d20db8\nadd PHP client libraries\n\nPiperOrigin-RevId: 300193634\n\n7727af0e39df1ae9ad715895c8576d7b65cf6c6d\nfeat: use the latest gapic-generator and protoc-java-resource-name-plugin in googleapis/WORKSPACE.\n\nPiperOrigin-RevId: 300188410\n\n2a25aa351dd5b5fe14895266aff5824d90ce757b\nBreaking change: remove the ProjectOrTenant resource and its references.\n\nPiperOrigin-RevId: 300182152\n\na499dbb28546379415f51803505cfb6123477e71\nUpdate web risk v1 gapic config and BUILD file.\n\nPiperOrigin-RevId: 300152177\n\n52701da10fec2a5f9796e8d12518c0fe574488fe\nFix: apply appropriate namespace/package options for C#, PHP and Ruby.\n\nPiperOrigin-RevId: 300123508\n\n365c029b8cdb63f7751b92ab490f1976e616105c\nAdd CC targets to the kms protos.\n\nThese are needed by go/tink.\n\nPiperOrigin-RevId: 300038469\n\n4ba9aa8a4a1413b88dca5a8fa931824ee9c284e6\nExpose logo recognition API proto for GA.\n\nPiperOrigin-RevId: 299971671\n\n1c9fc2c9e03dadf15f16b1c4f570955bdcebe00e\nAdding ruby_package option to accessapproval.proto for the Ruby client libraries generation.\n\nPiperOrigin-RevId: 299955924\n\n1cc6f0a7bfb147e6f2ede911d9b01e7a9923b719\nbuild(google/maps/routes): generate api clients\n\nPiperOrigin-RevId: 299955905\n\n29a47c965aac79e3fe8e3314482ca0b5967680f0\nIncrease timeout to 1hr for method `dropRange` in bigtable/admin/v2, which is\nsynced with the timeout setting in gapic_yaml.\n\nPiperOrigin-RevId: 299917154\n\n8f631c4c70a60a9c7da3749511ee4ad432b62898\nbuild(google/maps/roads/v1op): move go to monorepo pattern\n\nPiperOrigin-RevId: 299885195\n\nd66816518844ebbf63504c9e8dfc7133921dd2cd\nbuild(google/maps/roads/v1op): Add bazel build files to generate clients.\n\nPiperOrigin-RevId: 299851148\n\naf7dff701fabe029672168649c62356cf1bb43d0\nAdd LogPlayerReports and LogImpressions to Playable Locations service\n\nPiperOrigin-RevId: 299724050\n\nb6927fca808f38df32a642c560082f5bf6538ced\nUpdate BigQuery Connection API v1beta1 proto: added credential to CloudSqlProperties.\n\nPiperOrigin-RevId: 299503150\n\n91e1fb5ef9829c0c7a64bfa5bde330e6ed594378\nchore: update protobuf (protoc) version to 3.11.2\n\nPiperOrigin-RevId: 299404145\n\n30e36b4bee6749c4799f4fc1a51cc8f058ba167d\nUpdate cloud asset api v1p4beta1.\n\nPiperOrigin-RevId: 299399890\n\nffbb493674099f265693872ae250711b2238090c\nfeat: cloudbuild/v1 add new fields and annotate OUTPUT_OUT fields.\n\nPiperOrigin-RevId: 299397780\n\nbc973a15818e00c19e121959832676e9b7607456\nbazel: Fix broken common dependency\n\nPiperOrigin-RevId: 299397431\n\n71094a343e3b962e744aa49eb9338219537474e4\nchore: bigtable/admin/v2 publish retry config\n\nPiperOrigin-RevId: 299391875\n\n8f488efd7bda33885cb674ddd023b3678c40bd82\nfeat: Migrate logging to GAPIC v2; release new features.\n\nIMPORTANT: This is a breaking change for client libraries\nin all languages.\n\nCommitter: @lukesneeringer, @jskeet\nPiperOrigin-RevId: 299370279\n\n007605bf9ad3a1fd775014ebefbf7f1e6b31ee71\nUpdate API for bigqueryreservation v1beta1.\n- Adds flex capacity commitment plan to CapacityCommitment.\n- Adds methods for getting and updating BiReservations.\n- Adds methods for updating/splitting/merging CapacityCommitments.\n\nPiperOrigin-RevId: 299368059\n\nf0b581b5bdf803e45201ecdb3688b60e381628a8\nfix: recommendationengine/v1beta1 update some comments\n\nPiperOrigin-RevId: 299181282\n\n10e9a0a833dc85ff8f05b2c67ebe5ac785fe04ff\nbuild: add generated BUILD file for Routes Preferred API\n\nPiperOrigin-RevId: 299164808\n\n86738c956a8238d7c77f729be78b0ed887a6c913\npublish v1p1beta1: update with absolute address in comments\n\nPiperOrigin-RevId: 299152383\n\n73d9f2ad4591de45c2e1f352bc99d70cbd2a6d95\npublish v1: update with absolute address in comments\n\nPiperOrigin-RevId: 299147194\n\nd2158f24cb77b0b0ccfe68af784c6a628705e3c6\npublish v1beta2: update with absolute address in comments\n\nPiperOrigin-RevId: 299147086\n\n7fca61292c11b4cd5b352cee1a50bf88819dd63b\npublish v1p2beta1: update with absolute address in comments\n\nPiperOrigin-RevId: 299146903\n\n583b7321624736e2c490e328f4b1957335779295\npublish v1p3beta1: update with absolute address in comments\n\nPiperOrigin-RevId: 299146674\n\n638253bf86d1ce1c314108a089b7351440c2f0bf\nfix: add java_multiple_files option for automl text_sentiment.proto\n\nPiperOrigin-RevId: 298971070\n\n373d655703bf914fb8b0b1cc4071d772bac0e0d1\nUpdate Recs AI Beta public bazel file\n\nPiperOrigin-RevId: 298961623\n\ndcc5d00fc8a8d8b56f16194d7c682027b2c66a3b\nfix: add java_multiple_files option for automl classification.proto\n\nPiperOrigin-RevId: 298953301\n\na3f791827266f3496a6a5201d58adc4bb265c2a3\nchore: automl/v1 publish annotations and retry config\n\nPiperOrigin-RevId: 298942178\n\n01c681586d8d6dbd60155289b587aee678530bd9\nMark return_immediately in PullRequest deprecated.\n\nPiperOrigin-RevId: 298893281\n\nc9f5e9c4bfed54bbd09227e990e7bded5f90f31c\nRemove out of date documentation for predicate support on the Storage API\n\nPiperOrigin-RevId: 298883309\n\nfd5b3b8238d783b04692a113ffe07c0363f5de0f\ngenerate webrisk v1 proto\n\nPiperOrigin-RevId: 298847934\n\n" + "internalRef": "302792195" } }, { diff --git a/tests/unit/memcache_v1beta2/test_cloud_memcache.py b/tests/unit/memcache_v1beta2/test_cloud_memcache.py index a93cd40..b7e87f4 100644 --- a/tests/unit/memcache_v1beta2/test_cloud_memcache.py +++ b/tests/unit/memcache_v1beta2/test_cloud_memcache.py @@ -24,6 +24,7 @@ from google import auth from google.api_core import client_options from google.api_core import future +from google.api_core import grpc_helpers from google.api_core import operations_v1 from google.auth import credentials from google.cloud.memcache_v1beta2.services.cloud_memcache import CloudMemcacheClient @@ -36,6 +37,39 @@ from google.protobuf import timestamp_pb2 as timestamp # type: ignore +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert CloudMemcacheClient._get_default_mtls_endpoint(None) is None + assert ( + CloudMemcacheClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + CloudMemcacheClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + CloudMemcacheClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + CloudMemcacheClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + CloudMemcacheClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + ) + + def test_cloud_memcache_client_from_service_account_file(): creds = credentials.AnonymousCredentials() with mock.patch.object( @@ -52,28 +86,89 @@ def test_cloud_memcache_client_from_service_account_file(): def test_cloud_memcache_client_client_options(): - # Check the default options have their expected values. - assert CloudMemcacheClient.DEFAULT_OPTIONS.api_endpoint == "memcache.googleapis.com" + # Check that if channel is provided we won't create a new one. + with mock.patch( + "google.cloud.memcache_v1beta2.services.cloud_memcache.CloudMemcacheClient.get_transport_class" + ) as gtc: + transport = transports.CloudMemcacheGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + client = CloudMemcacheClient(transport=transport) + gtc.assert_not_called() - # Check that options can be customized. - options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + # Check mTLS is not triggered with empty client options. + options = client_options.ClientOptions() with mock.patch( "google.cloud.memcache_v1beta2.services.cloud_memcache.CloudMemcacheClient.get_transport_class" ) as gtc: transport = gtc.return_value = mock.MagicMock() client = CloudMemcacheClient(client_options=options) - transport.assert_called_once_with(credentials=None, host="squid.clam.whelk") + transport.assert_called_once_with( + credentials=None, host=client.DEFAULT_ENDPOINT + ) + + # Check mTLS is not triggered if api_endpoint is provided but + # client_cert_source is None. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch( + "google.cloud.memcache_v1beta2.services.cloud_memcache.transports.CloudMemcacheGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = CloudMemcacheClient(client_options=options) + grpc_transport.assert_called_once_with( + api_mtls_endpoint=None, + client_cert_source=None, + credentials=None, + host="squid.clam.whelk", + ) + + # Check mTLS is triggered if client_cert_source is provided. + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch( + "google.cloud.memcache_v1beta2.services.cloud_memcache.transports.CloudMemcacheGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = CloudMemcacheClient(client_options=options) + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=client_cert_source_callback, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check mTLS is triggered if api_endpoint and client_cert_source are provided. + options = client_options.ClientOptions( + api_endpoint="squid.clam.whelk", client_cert_source=client_cert_source_callback + ) + with mock.patch( + "google.cloud.memcache_v1beta2.services.cloud_memcache.transports.CloudMemcacheGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = CloudMemcacheClient(client_options=options) + grpc_transport.assert_called_once_with( + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=client_cert_source_callback, + credentials=None, + host="squid.clam.whelk", + ) def test_cloud_memcache_client_client_options_from_dict(): with mock.patch( - "google.cloud.memcache_v1beta2.services.cloud_memcache.CloudMemcacheClient.get_transport_class" - ) as gtc: - transport = gtc.return_value = mock.MagicMock() + "google.cloud.memcache_v1beta2.services.cloud_memcache.transports.CloudMemcacheGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None client = CloudMemcacheClient( client_options={"api_endpoint": "squid.clam.whelk"} ) - transport.assert_called_once_with(credentials=None, host="squid.clam.whelk") + grpc_transport.assert_called_once_with( + api_mtls_endpoint=None, + client_cert_source=None, + credentials=None, + host="squid.clam.whelk", + ) def test_list_instances(transport: str = "grpc"): @@ -717,8 +812,87 @@ def test_cloud_memcache_host_with_port(): def test_cloud_memcache_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - transport = transports.CloudMemcacheGrpcTransport(channel=channel) - assert transport.grpc_channel is channel + + # Check that if channel is provided, mtls endpoint and client_cert_source + # won't be used. + callback = mock.MagicMock() + transport = transports.CloudMemcacheGrpcTransport( + host="squid.clam.whelk", + channel=channel, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=callback, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert not callback.called + + +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) +def test_cloud_memcache_grpc_transport_channel_mtls_with_client_cert_source( + grpc_create_channel, grpc_ssl_channel_cred +): + # Check that if channel is None, but api_mtls_endpoint and client_cert_source + # are provided, then a mTLS channel will be created. + mock_cred = mock.Mock() + + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + transport = transports.CloudMemcacheGrpcTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] +) +@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) +def test_cloud_memcache_grpc_transport_channel_mtls_with_adc( + grpc_create_channel, api_mtls_endpoint +): + # Check that if channel and client_cert_source are None, but api_mtls_endpoint + # is provided, then a mTLS channel will be created with SSL ADC. + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + # Mock google.auth.transport.grpc.SslCredentials class. + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + mock_cred = mock.Mock() + transport = transports.CloudMemcacheGrpcTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint=api_mtls_endpoint, + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel def test_cloud_memcache_grpc_lro_client(): @@ -744,3 +918,12 @@ def test_instance_path(): ) actual = CloudMemcacheClient.instance_path(project, location, instance) assert expected == actual + + +def test_parse_instance_path(): + expected = {"project": "octopus", "location": "oyster", "instance": "nudibranch"} + path = CloudMemcacheClient.instance_path(**expected) + + # Check that the path construction is reversible. + actual = CloudMemcacheClient.parse_instance_path(path) + assert expected == actual