diff --git a/.librarian/generator-input/noxfile.py b/.librarian/generator-input/noxfile.py index c9ada0739..a00b72040 100644 --- a/.librarian/generator-input/noxfile.py +++ b/.librarian/generator-input/noxfile.py @@ -17,7 +17,6 @@ from __future__ import absolute_import import os import pathlib -import re import shutil import nox @@ -28,7 +27,13 @@ DEFAULT_PYTHON_VERSION = "3.14" SYSTEM_TEST_PYTHON_VERSIONS = ["3.10", "3.14"] -UNIT_TEST_PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"] +UNIT_TEST_PYTHON_VERSIONS = [ + "3.10", + "3.11", + "3.12", + "3.13", + "3.14", +] CONFORMANCE_TEST_PYTHON_VERSIONS = ["3.12"] CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() @@ -39,6 +44,7 @@ nox.options.sessions = [ "blacken", "conftest_retry", + "conftest_retry_bidi", "docfx", "docs", "lint", @@ -61,9 +67,7 @@ def lint(session): Returns a failure if the linters find linting errors or sufficiently serious code quality issues. """ - # Pin flake8 to 6.0.0 - # See https://github.com/googleapis/python-storage/issues/1102 - session.install("flake8==6.0.0", BLACK_VERSION) + session.install("flake8", BLACK_VERSION) session.run( "black", "--check", @@ -116,6 +120,8 @@ def default(session, install_extras=True): session.install("-e", ".", "-c", constraints_path) + session.run("python", "-m", "pip", "freeze") + # This dependency is included in setup.py for backwards compatibility only # and the client library is expected to pass all tests without it. See # setup.py and README for details. @@ -180,7 +186,14 @@ def system(session): # 2021-05-06: defer installing 'google-cloud-*' to after this package, # in order to work around Python 2.7 googolapis-common-protos # issue. - session.install("mock", "pytest", "pytest-rerunfailures", "-c", constraints_path) + session.install( + "mock", + "pytest", + "pytest-rerunfailures", + "pytest-asyncio", + "-c", + constraints_path, + ) session.install("-e", ".", "-c", constraints_path) session.install( "google-cloud-testutils", @@ -207,30 +220,75 @@ def system(session): @nox.session(python=CONFORMANCE_TEST_PYTHON_VERSIONS) def conftest_retry(session): """Run the retry conformance test suite.""" - conformance_test_folder_path = os.path.join("tests", "conformance") - conformance_test_folder_exists = os.path.exists(conformance_test_folder_path) + json_conformance_tests = "tests/conformance/test_conformance.py" # Environment check: only run tests if found. - if not conformance_test_folder_exists: + if not os.path.exists(json_conformance_tests): session.skip("Conformance tests were not found") + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + # Install all test dependencies and pytest plugin to run tests in parallel. # Then install this package in-place. - session.install("pytest", "pytest-xdist") - session.install("-e", ".") + session.install( + "pytest", + "pytest-xdist", + "-c", + constraints_path, + ) + session.install("-e", ".", "-c", constraints_path) # Run #CPU processes in parallel if no test session arguments are passed in. if session.posargs: test_cmd = [ - "py.test", - "--quiet", - conformance_test_folder_path, + "pytest", + "-vv", + "-s", + json_conformance_tests, *session.posargs, ] else: - test_cmd = ["py.test", "-n", "auto", "--quiet", conformance_test_folder_path] + test_cmd = ["pytest", "-vv", "-s", "-n", "auto", json_conformance_tests] + + # Run pytest against the conformance tests. + session.run(*test_cmd, env={"DOCKER_API_VERSION": "1.39"}) + - # Run py.test against the conformance tests. - session.run(*test_cmd) +@nox.session(python=CONFORMANCE_TEST_PYTHON_VERSIONS) +def conftest_retry_bidi(session): + """Run the retry conformance test suite.""" + + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + + # Install all test dependencies and pytest plugin to run tests in parallel. + # Then install this package in-place. + session.install( + "pytest", + "pytest-xdist", + "pytest-asyncio", + "grpcio", + "grpcio-status", + "grpc-google-iam-v1", + "-c", + constraints_path, + ) + session.install("-e", ".", "-c", constraints_path) + + bidi_tests = [ + "tests/conformance/test_bidi_reads.py", + "tests/conformance/test_bidi_writes.py", + ] + for test_file in bidi_tests: + session.run( + "pytest", + "-vv", + "-s", + test_file, + env={"DOCKER_API_VERSION": "1.39"}, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/.librarian/generator-input/setup.py b/.librarian/generator-input/setup.py index 294e63892..69fc2899e 100644 --- a/.librarian/generator-input/setup.py +++ b/.librarian/generator-input/setup.py @@ -43,10 +43,46 @@ "google-crc32c >= 1.1.3, < 2.0.0", ] extras = { + # TODO: Make these extra dependencies as mandatory once gRPC out of + # experimental in this SDK. More info in b/465352227 + "grpc": [ + "google-api-core[grpc] >= 2.27.0, < 3.0.0", + "grpcio >= 1.33.2, < 2.0.0; python_version < '3.14'", + "grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'", + "grpcio-status >= 1.76.0, < 2.0.0", + "proto-plus >= 1.22.3, <2.0.0; python_version < '3.13'", + "proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'", + "protobuf>=3.20.2,<7.0.0,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5", + "grpc-google-iam-v1 >= 0.14.0, <1.0.0", + ], "protobuf": ["protobuf >= 3.20.2, < 7.0.0"], "tracing": [ "opentelemetry-api >= 1.1.0, < 2.0.0", ], + "testing": [ + "google-cloud-testutils", + "numpy", + "psutil", + "py-cpuinfo", + "pytest-benchmark", + "PyYAML", + "mock", + "pytest", + "pytest-cov", + "pytest-asyncio", + "pytest-rerunfailures", + "pytest-xdist", + "google-cloud-testutils", + "google-cloud-iam", + "google-cloud-pubsub", + "google-cloud-kms", + "brotli", + "coverage", + "pyopenssl", + "opentelemetry-sdk", + "flake8", + "black", + ], } @@ -99,7 +135,7 @@ packages=packages, install_requires=dependencies, extras_require=extras, - python_requires=">=3.7", + python_requires=">=3.10", include_package_data=True, zip_safe=False, ) diff --git a/.librarian/state.yaml b/.librarian/state.yaml index 8c3daafaa..de8418e3d 100644 --- a/.librarian/state.yaml +++ b/.librarian/state.yaml @@ -2,7 +2,7 @@ image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-li libraries: - id: google-cloud-storage version: 3.10.1 - last_generated_commit: 5400ccce473c439885bd6bf2924fd242271bfcab + last_generated_commit: 9918ca7a6f52f62633a19885a83c91270a61725f apis: - path: google/storage/v2 service_config: storage_v2.yaml diff --git a/.repo-metadata.json b/.repo-metadata.json index 59ebe7f61..bd870f959 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -12,7 +12,7 @@ "api_id": "storage.googleapis.com", "requires_billing": true, "default_version": "v2", - "codeowner_team": "@googleapis/cloud-sdk-python-team @googleapis/gcs-team @googleapis/gcs-fs", + "codeowner_team": "@googleapis/yoshi-python @googleapis/gcs-sdk-team @googleapis/gcs-fs", "api_shortname": "storage", "api_description": "is a durable and highly available object storage service. Google Cloud Storage is almost infinitely scalable and guarantees consistency: when a write succeeds, the latest copy of the object will be returned to any GET, globally." } diff --git a/google/cloud/_storage_v2/services/storage/async_client.py b/google/cloud/_storage_v2/services/storage/async_client.py index c71ee0472..cd376a088 100644 --- a/google/cloud/_storage_v2/services/storage/async_client.py +++ b/google/cloud/_storage_v2/services/storage/async_client.py @@ -2472,8 +2472,9 @@ def bidi_read_object( across several messages. If an error occurs with any request, the stream closes with a relevant error code. Since you can have multiple outstanding requests, the error response includes a - ``BidiReadObjectRangesError`` field detailing the specific error - for each pending ``read_id``. + ``BidiReadObjectError`` proto in its ``details`` field, + reporting the specific error, if any, for each pending + ``read_id``. **IAM Permissions**: diff --git a/google/cloud/_storage_v2/services/storage/client.py b/google/cloud/_storage_v2/services/storage/client.py index cdccf3fab..ec0dcd556 100644 --- a/google/cloud/_storage_v2/services/storage/client.py +++ b/google/cloud/_storage_v2/services/storage/client.py @@ -184,34 +184,6 @@ def _get_default_mtls_endpoint(api_endpoint): _DEFAULT_ENDPOINT_TEMPLATE = "storage.{UNIVERSE_DOMAIN}" _DEFAULT_UNIVERSE = "googleapis.com" - @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): - return mtls.should_use_client_cert() - else: - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv( - "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" - ).lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" - @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -418,8 +390,12 @@ def get_mtls_endpoint_and_cert_source( ) if client_options is None: client_options = client_options_lib.ClientOptions() - use_client_cert = StorageClient._use_client_cert_effective() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) if use_mtls_endpoint not in ("auto", "never", "always"): raise MutualTLSChannelError( "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" @@ -427,7 +403,7 @@ def get_mtls_endpoint_and_cert_source( # Figure out the client cert source to use. client_cert_source = None - if use_client_cert: + if use_client_cert == "true": if client_options.client_cert_source: client_cert_source = client_options.client_cert_source elif mtls.has_default_client_cert_source(): @@ -459,14 +435,20 @@ def _read_environment_variables(): google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT is not any of ["auto", "never", "always"]. """ - use_client_cert = StorageClient._use_client_cert_effective() + use_client_cert = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) if use_mtls_endpoint not in ("auto", "never", "always"): raise MutualTLSChannelError( "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" ) - return use_client_cert, use_mtls_endpoint, universe_domain_env + return use_client_cert == "true", use_mtls_endpoint, universe_domain_env @staticmethod def _get_client_cert_source(provided_cert_source, use_cert_flag): @@ -2899,8 +2881,9 @@ def bidi_read_object( across several messages. If an error occurs with any request, the stream closes with a relevant error code. Since you can have multiple outstanding requests, the error response includes a - ``BidiReadObjectRangesError`` field detailing the specific error - for each pending ``read_id``. + ``BidiReadObjectError`` proto in its ``details`` field, + reporting the specific error, if any, for each pending + ``read_id``. **IAM Permissions**: diff --git a/google/cloud/_storage_v2/services/storage/transports/grpc.py b/google/cloud/_storage_v2/services/storage/transports/grpc.py index fae2d7949..6497959d7 100644 --- a/google/cloud/_storage_v2/services/storage/transports/grpc.py +++ b/google/cloud/_storage_v2/services/storage/transports/grpc.py @@ -946,8 +946,9 @@ def bidi_read_object( across several messages. If an error occurs with any request, the stream closes with a relevant error code. Since you can have multiple outstanding requests, the error response includes a - ``BidiReadObjectRangesError`` field detailing the specific error - for each pending ``read_id``. + ``BidiReadObjectError`` proto in its ``details`` field, + reporting the specific error, if any, for each pending + ``read_id``. **IAM Permissions**: diff --git a/google/cloud/_storage_v2/services/storage/transports/grpc_asyncio.py b/google/cloud/_storage_v2/services/storage/transports/grpc_asyncio.py index be54eb3b0..1c7743a72 100644 --- a/google/cloud/_storage_v2/services/storage/transports/grpc_asyncio.py +++ b/google/cloud/_storage_v2/services/storage/transports/grpc_asyncio.py @@ -971,8 +971,9 @@ def bidi_read_object( across several messages. If an error occurs with any request, the stream closes with a relevant error code. Since you can have multiple outstanding requests, the error response includes a - ``BidiReadObjectRangesError`` field detailing the specific error - for each pending ``read_id``. + ``BidiReadObjectError`` proto in its ``details`` field, + reporting the specific error, if any, for each pending + ``read_id``. **IAM Permissions**: diff --git a/google/cloud/_storage_v2/types/storage.py b/google/cloud/_storage_v2/types/storage.py index 8602610be..d1a7c5a36 100644 --- a/google/cloud/_storage_v2/types/storage.py +++ b/google/cloud/_storage_v2/types/storage.py @@ -489,6 +489,11 @@ class ComposeObjectRequest(proto.Message): Optional. The checksums of the complete object. This is validated against the combined checksums of the component objects. + delete_source_objects (bool): + Whether the source objects should be deleted + in the compose request. + + This field is a member of `oneof`_ ``_delete_source_objects``. """ class SourceObject(proto.Message): @@ -580,6 +585,11 @@ class ObjectPreconditions(proto.Message): number=10, message="ObjectChecksums", ) + delete_source_objects: bool = proto.Field( + proto.BOOL, + number=11, + optional=True, + ) class DeleteObjectRequest(proto.Message): @@ -1460,18 +1470,18 @@ class ReadRange(proto.Message): ``ReadObjectRequest`` with ``read_offset`` = -5 and ``read_length`` = 3 would return bytes 10 through 12 of the object. Requesting a negative offset with magnitude larger - than the size of the object returns the entire object. A - ``read_offset`` larger than the size of the object results - in an ``OutOfRange`` error. + than the size of the object is equivalent to ``read_offset`` + = 0. A ``read_offset`` larger than the size of the object + results in an ``OutOfRange`` error. read_length (int): Optional. The maximum number of data bytes the server is allowed to return across all response messages with the same ``read_id``. A ``read_length`` of zero indicates to read until the resource end, and a negative ``read_length`` - causes an error. If the stream returns fewer bytes than - allowed by the ``read_length`` and no error occurred, the - stream includes all data from the ``read_offset`` to the - resource end. + causes an ``OutOfRange`` error. If the stream returns fewer + bytes than allowed by the ``read_length`` and no error + occurred, the stream includes all data from the + ``read_offset`` to the resource end. read_id (int): Required. Read identifier provided by the client. When the client issues more than one outstanding ``ReadRange`` on the @@ -4364,7 +4374,10 @@ class ObjectContexts(proto.Message): Attributes: custom (MutableMapping[str, google.cloud._storage_v2.types.ObjectCustomContextPayload]): - Optional. User-defined object contexts. + Optional. User-defined object contexts. The maximum key or + value size is ``256`` characters. The maximum number of + entries is ``50``. The maximum total serialized size of all + entries is ``25KiB``. """ custom: MutableMapping[str, "ObjectCustomContextPayload"] = proto.MapField( diff --git a/noxfile.py b/noxfile.py index 77823d28d..a00b72040 100644 --- a/noxfile.py +++ b/noxfile.py @@ -67,8 +67,6 @@ def lint(session): Returns a failure if the linters find linting errors or sufficiently serious code quality issues. """ - # Pin flake8 to 6.0.0 - # See https://github.com/googleapis/python-storage/issues/1102 session.install("flake8", BLACK_VERSION) session.run( "black", diff --git a/testing/constraints-3.14.txt b/testing/constraints-3.14.txt index 62739fc5d..2ae5a677e 100644 --- a/testing/constraints-3.14.txt +++ b/testing/constraints-3.14.txt @@ -7,7 +7,7 @@ # Then this file should have google-cloud-foo>=1 google-api-core>=2 google-auth>=2 -grpcio>=1.75.1 +grpcio>=1 proto-plus>=1 protobuf>=6 grpc-google-iam-v1>=0 diff --git a/tests/unit/gapic/storage_v2/test_storage.py b/tests/unit/gapic/storage_v2/test_storage.py index 7b6340aa7..20b680341 100644 --- a/tests/unit/gapic/storage_v2/test_storage.py +++ b/tests/unit/gapic/storage_v2/test_storage.py @@ -148,19 +148,12 @@ def test__read_environment_variables(): with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - StorageClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert StorageClient._read_environment_variables() == ( - False, - "auto", - None, - ) + with pytest.raises(ValueError) as excinfo: + StorageClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): assert StorageClient._read_environment_variables() == (False, "never", None) @@ -183,105 +176,6 @@ def test__read_environment_variables(): assert StorageClient._read_environment_variables() == (False, "auto", "foo.com") -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch( - "google.auth.transport.mtls.should_use_client_cert", return_value=True - ): - assert StorageClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch( - "google.auth.transport.mtls.should_use_client_cert", return_value=False - ): - assert StorageClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert StorageClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"} - ): - assert StorageClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert StorageClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"} - ): - assert StorageClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert StorageClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"} - ): - assert StorageClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert StorageClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"} - ): - with pytest.raises(ValueError): - StorageClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"} - ): - assert StorageClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert StorageClient._use_client_cert_effective() is False - - def test__get_client_cert_source(): mock_provided_cert_source = mock.Mock() mock_default_cert_source = mock.Mock() @@ -621,6 +515,17 @@ def test_storage_client_client_options(client_class, transport_class, transport_ == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" ) + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + client = client_class(transport=transport_name) + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + # Check the case quota_project_id is provided options = client_options.ClientOptions(quota_project_id="octopus") with mock.patch.object(transport_class, "__init__") as patched: @@ -828,119 +733,6 @@ def test_storage_client_get_mtls_endpoint_and_cert_source(client_class): assert api_endpoint == mock_api_endpoint assert cert_source is None - # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "Unsupported". - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - mock_client_cert_source = mock.Mock() - mock_api_endpoint = "foo" - options = client_options.ClientOptions( - client_cert_source=mock_client_cert_source, - api_endpoint=mock_api_endpoint, - ) - api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( - options - ) - assert api_endpoint == mock_api_endpoint - assert cert_source is None - - # Test cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset. - test_cases = [ - ( - # With workloads present in config, mTLS is enabled. - { - "version": 1, - "cert_configs": { - "workload": { - "cert_path": "path/to/cert/file", - "key_path": "path/to/key/file", - } - }, - }, - mock_client_cert_source, - ), - ( - # With workloads not present in config, mTLS is disabled. - { - "version": 1, - "cert_configs": {}, - }, - None, - ), - ] - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - for config_data, expected_cert_source in test_cases: - env = os.environ.copy() - env.pop("GOOGLE_API_USE_CLIENT_CERTIFICATE", None) - with mock.patch.dict(os.environ, env, clear=True): - config_filename = "mock_certificate_config.json" - config_file_content = json.dumps(config_data) - m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): - with mock.patch.dict( - os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} - ): - mock_api_endpoint = "foo" - options = client_options.ClientOptions( - client_cert_source=mock_client_cert_source, - api_endpoint=mock_api_endpoint, - ) - ( - api_endpoint, - cert_source, - ) = client_class.get_mtls_endpoint_and_cert_source(options) - assert api_endpoint == mock_api_endpoint - assert cert_source is expected_cert_source - - # Test cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset(empty). - test_cases = [ - ( - # With workloads present in config, mTLS is enabled. - { - "version": 1, - "cert_configs": { - "workload": { - "cert_path": "path/to/cert/file", - "key_path": "path/to/key/file", - } - }, - }, - mock_client_cert_source, - ), - ( - # With workloads not present in config, mTLS is disabled. - { - "version": 1, - "cert_configs": {}, - }, - None, - ), - ] - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - for config_data, expected_cert_source in test_cases: - env = os.environ.copy() - env.pop("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") - with mock.patch.dict(os.environ, env, clear=True): - config_filename = "mock_certificate_config.json" - config_file_content = json.dumps(config_data) - m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): - with mock.patch.dict( - os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} - ): - mock_api_endpoint = "foo" - options = client_options.ClientOptions( - client_cert_source=mock_client_cert_source, - api_endpoint=mock_api_endpoint, - ) - ( - api_endpoint, - cert_source, - ) = client_class.get_mtls_endpoint_and_cert_source(options) - assert api_endpoint == mock_api_endpoint - assert cert_source is expected_cert_source - # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() @@ -991,6 +783,18 @@ def test_storage_client_get_mtls_endpoint_and_cert_source(client_class): == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" ) + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + client_class.get_mtls_endpoint_and_cert_source() + + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + @pytest.mark.parametrize("client_class", [StorageClient, StorageAsyncClient]) @mock.patch.object(