1# -*- coding: utf-8 -*-
2# Copyright 2020 Google LLC
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16from collections import OrderedDict
17from distutils import util
18import os
19import re
20from typing import Dict, Optional, Sequence, Tuple, Type, Union
21
22from google.api_core import client_options as client_options_lib  # type: ignore
23from google.api_core import gapic_v1  # type: ignore
24from google.api_core import retry as retries  # type: ignore
25from google.api_core.operations_v1 import pagers
26from google.api_core.operations_v1.transports.base import (
27    DEFAULT_CLIENT_INFO,
28    OperationsTransport,
29)
30from google.api_core.operations_v1.transports.rest import OperationsRestTransport
31from google.auth import credentials as ga_credentials  # type: ignore
32from google.auth.exceptions import MutualTLSChannelError  # type: ignore
33from google.auth.transport import mtls  # type: ignore
34from google.longrunning import operations_pb2
35from google.oauth2 import service_account  # type: ignore
36
37OptionalRetry = Union[retries.Retry, object]
38
39
40class AbstractOperationsClientMeta(type):
41    """Metaclass for the Operations client.
42
43    This provides class-level methods for building and retrieving
44    support objects (e.g. transport) without polluting the client instance
45    objects.
46    """
47
48    _transport_registry = OrderedDict()  # type: Dict[str, Type[OperationsTransport]]
49    _transport_registry["rest"] = OperationsRestTransport
50
51    def get_transport_class(
52        cls, label: Optional[str] = None,
53    ) -> Type[OperationsTransport]:
54        """Returns an appropriate transport class.
55
56        Args:
57            label: The name of the desired transport. If none is
58                provided, then the first transport in the registry is used.
59
60        Returns:
61            The transport class to use.
62        """
63        # If a specific transport is requested, return that one.
64        if label:
65            return cls._transport_registry[label]
66
67        # No transport is requested; return the default (that is, the first one
68        # in the dictionary).
69        return next(iter(cls._transport_registry.values()))
70
71
72class AbstractOperationsClient(metaclass=AbstractOperationsClientMeta):
73    """Manages long-running operations with an API service.
74
75    When an API method normally takes long time to complete, it can be
76    designed to return [Operation][google.api_core.operations_v1.Operation] to the
77    client, and the client can use this interface to receive the real
78    response asynchronously by polling the operation resource, or pass
79    the operation resource to another API (such as Google Cloud Pub/Sub
80    API) to receive the response. Any API service that returns
81    long-running operations should implement the ``Operations``
82    interface so developers can have a consistent client experience.
83    """
84
85    @staticmethod
86    def _get_default_mtls_endpoint(api_endpoint):
87        """Converts api endpoint to mTLS endpoint.
88
89        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
90        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
91        Args:
92            api_endpoint (Optional[str]): the api endpoint to convert.
93        Returns:
94            str: converted mTLS api endpoint.
95        """
96        if not api_endpoint:
97            return api_endpoint
98
99        mtls_endpoint_re = re.compile(
100            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
101        )
102
103        m = mtls_endpoint_re.match(api_endpoint)
104        name, mtls, sandbox, googledomain = m.groups()
105        if mtls or not googledomain:
106            return api_endpoint
107
108        if sandbox:
109            return api_endpoint.replace(
110                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
111            )
112
113        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
114
115    DEFAULT_ENDPOINT = "longrunning.googleapis.com"
116    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
117        DEFAULT_ENDPOINT
118    )
119
120    @classmethod
121    def from_service_account_info(cls, info: dict, *args, **kwargs):
122        """Creates an instance of this client using the provided credentials
123            info.
124
125        Args:
126            info (dict): The service account private key info.
127            args: Additional arguments to pass to the constructor.
128            kwargs: Additional arguments to pass to the constructor.
129
130        Returns:
131            AbstractOperationsClient: The constructed client.
132        """
133        credentials = service_account.Credentials.from_service_account_info(info)
134        kwargs["credentials"] = credentials
135        return cls(*args, **kwargs)
136
137    @classmethod
138    def from_service_account_file(cls, filename: str, *args, **kwargs):
139        """Creates an instance of this client using the provided credentials
140            file.
141
142        Args:
143            filename (str): The path to the service account private key json
144                file.
145            args: Additional arguments to pass to the constructor.
146            kwargs: Additional arguments to pass to the constructor.
147
148        Returns:
149            AbstractOperationsClient: The constructed client.
150        """
151        credentials = service_account.Credentials.from_service_account_file(filename)
152        kwargs["credentials"] = credentials
153        return cls(*args, **kwargs)
154
155    from_service_account_json = from_service_account_file
156
157    @property
158    def transport(self) -> OperationsTransport:
159        """Returns the transport used by the client instance.
160
161        Returns:
162            OperationsTransport: The transport used by the client
163                instance.
164        """
165        return self._transport
166
167    @staticmethod
168    def common_billing_account_path(billing_account: str,) -> str:
169        """Returns a fully-qualified billing_account string."""
170        return "billingAccounts/{billing_account}".format(
171            billing_account=billing_account,
172        )
173
174    @staticmethod
175    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
176        """Parse a billing_account path into its component segments."""
177        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
178        return m.groupdict() if m else {}
179
180    @staticmethod
181    def common_folder_path(folder: str,) -> str:
182        """Returns a fully-qualified folder string."""
183        return "folders/{folder}".format(folder=folder,)
184
185    @staticmethod
186    def parse_common_folder_path(path: str) -> Dict[str, str]:
187        """Parse a folder path into its component segments."""
188        m = re.match(r"^folders/(?P<folder>.+?)$", path)
189        return m.groupdict() if m else {}
190
191    @staticmethod
192    def common_organization_path(organization: str,) -> str:
193        """Returns a fully-qualified organization string."""
194        return "organizations/{organization}".format(organization=organization,)
195
196    @staticmethod
197    def parse_common_organization_path(path: str) -> Dict[str, str]:
198        """Parse a organization path into its component segments."""
199        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
200        return m.groupdict() if m else {}
201
202    @staticmethod
203    def common_project_path(project: str,) -> str:
204        """Returns a fully-qualified project string."""
205        return "projects/{project}".format(project=project,)
206
207    @staticmethod
208    def parse_common_project_path(path: str) -> Dict[str, str]:
209        """Parse a project path into its component segments."""
210        m = re.match(r"^projects/(?P<project>.+?)$", path)
211        return m.groupdict() if m else {}
212
213    @staticmethod
214    def common_location_path(project: str, location: str,) -> str:
215        """Returns a fully-qualified location string."""
216        return "projects/{project}/locations/{location}".format(
217            project=project, location=location,
218        )
219
220    @staticmethod
221    def parse_common_location_path(path: str) -> Dict[str, str]:
222        """Parse a location path into its component segments."""
223        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
224        return m.groupdict() if m else {}
225
226    def __init__(
227        self,
228        *,
229        credentials: Optional[ga_credentials.Credentials] = None,
230        transport: Union[str, OperationsTransport, None] = None,
231        client_options: Optional[client_options_lib.ClientOptions] = None,
232        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
233    ) -> None:
234        """Instantiates the operations client.
235
236        Args:
237            credentials (Optional[google.auth.credentials.Credentials]): The
238                authorization credentials to attach to requests. These
239                credentials identify the application to the service; if none
240                are specified, the client will attempt to ascertain the
241                credentials from the environment.
242            transport (Union[str, OperationsTransport]): The
243                transport to use. If set to None, a transport is chosen
244                automatically.
245            client_options (google.api_core.client_options.ClientOptions): Custom options for the
246                client. It won't take effect if a ``transport`` instance is provided.
247                (1) The ``api_endpoint`` property can be used to override the
248                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
249                environment variable can also be used to override the endpoint:
250                "always" (always use the default mTLS endpoint), "never" (always
251                use the default regular endpoint) and "auto" (auto switch to the
252                default mTLS endpoint if client certificate is present, this is
253                the default value). However, the ``api_endpoint`` property takes
254                precedence if provided.
255                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
256                is "true", then the ``client_cert_source`` property can be used
257                to provide client certificate for mutual TLS transport. If
258                not provided, the default SSL client certificate will be used if
259                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
260                set, no client certificate will be used.
261            client_info (google.api_core.gapic_v1.client_info.ClientInfo):
262                The client info used to send a user-agent string along with
263                API requests. If ``None``, then default info will be used.
264                Generally, you only need to set this if you're developing
265                your own client library.
266
267        Raises:
268            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
269                creation failed for any reason.
270        """
271        if isinstance(client_options, dict):
272            client_options = client_options_lib.from_dict(client_options)
273        if client_options is None:
274            client_options = client_options_lib.ClientOptions()
275
276        # Create SSL credentials for mutual TLS if needed.
277        use_client_cert = bool(
278            util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"))
279        )
280
281        client_cert_source_func = None
282        is_mtls = False
283        if use_client_cert:
284            if client_options.client_cert_source:
285                is_mtls = True
286                client_cert_source_func = client_options.client_cert_source
287            else:
288                is_mtls = mtls.has_default_client_cert_source()
289                if is_mtls:
290                    client_cert_source_func = mtls.default_client_cert_source()
291                else:
292                    client_cert_source_func = None
293
294        # Figure out which api endpoint to use.
295        if client_options.api_endpoint is not None:
296            api_endpoint = client_options.api_endpoint
297        else:
298            use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
299            if use_mtls_env == "never":
300                api_endpoint = self.DEFAULT_ENDPOINT
301            elif use_mtls_env == "always":
302                api_endpoint = self.DEFAULT_MTLS_ENDPOINT
303            elif use_mtls_env == "auto":
304                if is_mtls:
305                    api_endpoint = self.DEFAULT_MTLS_ENDPOINT
306                else:
307                    api_endpoint = self.DEFAULT_ENDPOINT
308            else:
309                raise MutualTLSChannelError(
310                    "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
311                    "values: never, auto, always"
312                )
313
314        # Save or instantiate the transport.
315        # Ordinarily, we provide the transport, but allowing a custom transport
316        # instance provides an extensibility point for unusual situations.
317        if isinstance(transport, OperationsTransport):
318            # transport is a OperationsTransport instance.
319            if credentials or client_options.credentials_file:
320                raise ValueError(
321                    "When providing a transport instance, "
322                    "provide its credentials directly."
323                )
324            if client_options.scopes:
325                raise ValueError(
326                    "When providing a transport instance, provide its scopes "
327                    "directly."
328                )
329            self._transport = transport
330        else:
331            Transport = type(self).get_transport_class(transport)
332            self._transport = Transport(
333                credentials=credentials,
334                credentials_file=client_options.credentials_file,
335                host=api_endpoint,
336                scopes=client_options.scopes,
337                client_cert_source_for_mtls=client_cert_source_func,
338                quota_project_id=client_options.quota_project_id,
339                client_info=client_info,
340                always_use_jwt_access=True,
341            )
342
343    def list_operations(
344        self,
345        name: str,
346        filter_: Optional[str] = None,
347        *,
348        page_size: Optional[int] = None,
349        page_token: Optional[str] = None,
350        retry: OptionalRetry = gapic_v1.method.DEFAULT,
351        timeout: Optional[float] = None,
352        metadata: Sequence[Tuple[str, str]] = (),
353    ) -> pagers.ListOperationsPager:
354        r"""Lists operations that match the specified filter in the request.
355        If the server doesn't support this method, it returns
356        ``UNIMPLEMENTED``.
357
358        NOTE: the ``name`` binding allows API services to override the
359        binding to use different resource name schemes, such as
360        ``users/*/operations``. To override the binding, API services
361        can add a binding such as ``"/v1/{name=users/*}/operations"`` to
362        their service configuration. For backwards compatibility, the
363        default name includes the operations collection id, however
364        overriding users must ensure the name binding is the parent
365        resource, without the operations collection id.
366
367        Args:
368            name (str):
369                The name of the operation's parent
370                resource.
371            filter_ (str):
372                The standard list filter.
373                This corresponds to the ``filter`` field
374                on the ``request`` instance; if ``request`` is provided, this
375                should not be set.
376            retry (google.api_core.retry.Retry): Designation of what errors, if any,
377                should be retried.
378            timeout (float): The timeout for this request.
379            metadata (Sequence[Tuple[str, str]]): Strings which should be
380                sent along with the request as metadata.
381
382        Returns:
383            google.api_core.operations_v1.pagers.ListOperationsPager:
384                The response message for
385                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
386
387                Iterating over this object will yield results and
388                resolve additional pages automatically.
389
390        """
391        # Create a protobuf request object.
392        request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)
393        if page_size is not None:
394            request.page_size = page_size
395        if page_token is not None:
396            request.page_token = page_token
397
398        # Wrap the RPC method; this adds retry and timeout information,
399        # and friendly error handling.
400        rpc = self._transport._wrapped_methods[self._transport.list_operations]
401
402        # Certain fields should be provided within the metadata header;
403        # add these here.
404        metadata = tuple(metadata or ()) + (
405            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
406        )
407
408        # Send the request.
409        response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
410
411        # This method is paged; wrap the response in a pager, which provides
412        # an `__iter__` convenience method.
413        response = pagers.ListOperationsPager(
414            method=rpc, request=request, response=response, metadata=metadata,
415        )
416
417        # Done; return the response.
418        return response
419
420    def get_operation(
421        self,
422        name: str,
423        *,
424        retry: OptionalRetry = gapic_v1.method.DEFAULT,
425        timeout: Optional[float] = None,
426        metadata: Sequence[Tuple[str, str]] = (),
427    ) -> operations_pb2.Operation:
428        r"""Gets the latest state of a long-running operation.
429        Clients can use this method to poll the operation result
430        at intervals as recommended by the API service.
431
432        Args:
433            name (str):
434                The name of the operation resource.
435            retry (google.api_core.retry.Retry): Designation of what errors, if any,
436                should be retried.
437            timeout (float): The timeout for this request.
438            metadata (Sequence[Tuple[str, str]]): Strings which should be
439                sent along with the request as metadata.
440
441        Returns:
442            google.longrunning.operations_pb2.Operation:
443                This resource represents a long-
444                unning operation that is the result of a
445                network API call.
446
447        """
448
449        request = operations_pb2.GetOperationRequest(name=name)
450
451        # Wrap the RPC method; this adds retry and timeout information,
452        # and friendly error handling.
453        rpc = self._transport._wrapped_methods[self._transport.get_operation]
454
455        # Certain fields should be provided within the metadata header;
456        # add these here.
457        metadata = tuple(metadata or ()) + (
458            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
459        )
460
461        # Send the request.
462        response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
463
464        # Done; return the response.
465        return response
466
467    def delete_operation(
468        self,
469        name: str,
470        *,
471        retry: OptionalRetry = gapic_v1.method.DEFAULT,
472        timeout: Optional[float] = None,
473        metadata: Sequence[Tuple[str, str]] = (),
474    ) -> None:
475        r"""Deletes a long-running operation. This method indicates that the
476        client is no longer interested in the operation result. It does
477        not cancel the operation. If the server doesn't support this
478        method, it returns ``google.rpc.Code.UNIMPLEMENTED``.
479
480        Args:
481            name (str):
482                The name of the operation resource to
483                be deleted.
484
485                This corresponds to the ``name`` field
486                on the ``request`` instance; if ``request`` is provided, this
487                should not be set.
488            retry (google.api_core.retry.Retry): Designation of what errors, if any,
489                should be retried.
490            timeout (float): The timeout for this request.
491            metadata (Sequence[Tuple[str, str]]): Strings which should be
492                sent along with the request as metadata.
493        """
494        # Create the request object.
495        request = operations_pb2.DeleteOperationRequest(name=name)
496
497        # Wrap the RPC method; this adds retry and timeout information,
498        # and friendly error handling.
499        rpc = self._transport._wrapped_methods[self._transport.delete_operation]
500
501        # Certain fields should be provided within the metadata header;
502        # add these here.
503        metadata = tuple(metadata or ()) + (
504            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
505        )
506
507        # Send the request.
508        rpc(
509            request, retry=retry, timeout=timeout, metadata=metadata,
510        )
511
512    def cancel_operation(
513        self,
514        name: Optional[str] = None,
515        *,
516        retry: OptionalRetry = gapic_v1.method.DEFAULT,
517        timeout: Optional[float] = None,
518        metadata: Sequence[Tuple[str, str]] = (),
519    ) -> None:
520        r"""Starts asynchronous cancellation on a long-running operation.
521        The server makes a best effort to cancel the operation, but
522        success is not guaranteed. If the server doesn't support this
523        method, it returns ``google.rpc.Code.UNIMPLEMENTED``. Clients
524        can use
525        [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation]
526        or other methods to check whether the cancellation succeeded or
527        whether the operation completed despite cancellation. On
528        successful cancellation, the operation is not deleted; instead,
529        it becomes an operation with an
530        [Operation.error][google.api_core.operations_v1.Operation.error] value with
531        a [google.rpc.Status.code][google.rpc.Status.code] of 1,
532        corresponding to ``Code.CANCELLED``.
533
534        Args:
535            name (str):
536                The name of the operation resource to
537                be cancelled.
538
539                This corresponds to the ``name`` field
540                on the ``request`` instance; if ``request`` is provided, this
541                should not be set.
542            retry (google.api_core.retry.Retry): Designation of what errors, if any,
543                should be retried.
544            timeout (float): The timeout for this request.
545            metadata (Sequence[Tuple[str, str]]): Strings which should be
546                sent along with the request as metadata.
547        """
548        # Create the request object.
549        request = operations_pb2.CancelOperationRequest(name=name)
550
551        # Wrap the RPC method; this adds retry and timeout information,
552        # and friendly error handling.
553        rpc = self._transport._wrapped_methods[self._transport.cancel_operation]
554
555        # Certain fields should be provided within the metadata header;
556        # add these here.
557        metadata = tuple(metadata or ()) + (
558            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
559        )
560
561        # Send the request.
562        rpc(
563            request, retry=retry, timeout=timeout, metadata=metadata,
564        )
565