This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new d1d5b1af26e Adapt GCP BigtableHook to run in private cloud (#73181)
d1d5b1af26e is described below

commit d1d5b1af26e9308034a8c3116e7ef865d3381e5a
Author: olegkachur-e <[email protected]>
AuthorDate: Sun Oct 4 16:35:22 2026 +0000

    Adapt GCP BigtableHook to run in private cloud (#73181)
    
    * Adapt GCP BigtableHook to run in private cloud
    
    - Customize ClientOptions generation,
    for the non-default universal domain.
    
    * Explain the Bigtable admin endpoint override and log the ignored override
    
    The override exists because google.cloud.bigtable.Client ignores 
universe_domain when it builds the admin channel; without saying so it is easy 
to simplify away. Dropping api_endpoint_override silently also differed from 
the base hook, which logs it.
    
    Generated-by: Claude Opus 5
    
    ---------
    
    Co-authored-by: Oleg Kachur <[email protected]>
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 .../providers/google/cloud/hooks/bigtable.py       | 24 ++++++++++++++++++++
 .../tests/unit/google/cloud/hooks/test_bigtable.py | 26 ++++++++++++++++++++++
 2 files changed, 50 insertions(+)

diff --git 
a/providers/google/src/airflow/providers/google/cloud/hooks/bigtable.py 
b/providers/google/src/airflow/providers/google/cloud/hooks/bigtable.py
index a336381ee24..0e79bdbce27 100644
--- a/providers/google/src/airflow/providers/google/cloud/hooks/bigtable.py
+++ b/providers/google/src/airflow/providers/google/cloud/hooks/bigtable.py
@@ -19,10 +19,12 @@
 
 from __future__ import annotations
 
+import os
 from collections.abc import Sequence
 from typing import TYPE_CHECKING
 
 import google.api_core.exceptions
+from google.api_core.client_options import ClientOptions
 from google.cloud.bigtable import Client, enums
 from google.cloud.bigtable.cluster import Cluster
 from google.cloud.bigtable.instance import Instance
@@ -59,6 +61,28 @@ class BigtableHook(GoogleBaseHook):
         )
         self._client: Client | None = None
 
+    def get_client_options(
+        self,
+        api_endpoint_override: str | None = None,
+    ) -> ClientOptions:
+        """
+        Return the ClientOptions object for Google Bigtable Admin API.
+
+        The returned options point at the Bigtable Admin API. ``Client`` 
passes the same options to its
+        data client, so data-plane methods added to this hook would need their 
own endpoint.
+        """
+        if not self.is_default_universe():
+            if api_endpoint_override:
+                self.log.info(
+                    "Ignoring api_endpoint_override because the universe 
domain is not Google default universe."
+                )
+            global_universe_domain = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
+            # google.cloud.bigtable.Client builds the admin channel from 
api_endpoint only and ignores
+            # universe_domain, so the base hook's 
ClientOptions(universe_domain=...) would still reach
+            # bigtableadmin.googleapis.com.
+            return 
ClientOptions(api_endpoint=f"bigtableadmin.{global_universe_domain}")
+        return 
super().get_client_options(api_endpoint_override=api_endpoint_override)
+
     def _get_client(self, project_id: str) -> Client:
         if not self._client:
             self._client = Client(
diff --git a/providers/google/tests/unit/google/cloud/hooks/test_bigtable.py 
b/providers/google/tests/unit/google/cloud/hooks/test_bigtable.py
index b5c5420bf2c..b30d82cae27 100644
--- a/providers/google/tests/unit/google/cloud/hooks/test_bigtable.py
+++ b/providers/google/tests/unit/google/cloud/hooks/test_bigtable.py
@@ -59,6 +59,32 @@ class TestBigtableHookNoDefaultProjectId:
         ):
             self.bigtable_hook_no_default_project_id = 
BigtableHook(gcp_conn_id="test")
 
+    @pytest.mark.parametrize("api_endpoint_override", [None, 
"custom-override-api_endpoint"])
+    def test_get_client_options_non_default_universe(self, monkeypatch, 
api_endpoint_override):
+        monkeypatch.setenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN", 
"custom-universe.com")
+        with mock.patch.object(BigtableHook, "log", new_callable=PropertyMock) 
as mock_log_property:
+            result = 
self.bigtable_hook_no_default_project_id.get_client_options(
+                api_endpoint_override=api_endpoint_override
+            )
+
+        assert result.api_endpoint == "bigtableadmin.custom-universe.com"
+        assert mock_log_property.return_value.info.called is 
(api_endpoint_override is not None)
+
+    
@mock.patch("airflow.providers.google.common.hooks.base_google.GoogleBaseHook.get_client_options")
+    
@mock.patch("airflow.providers.google.cloud.hooks.bigtable.BigtableHook.is_default_universe")
+    def test_get_client_options_default_universe(
+        self, mock_is_default_universe, mock_base_get_client_options
+    ):
+        mock_is_default_universe.return_value = True
+        test_api_endpoint = "custom-override-api_endpoint"
+        result = self.bigtable_hook_no_default_project_id.get_client_options(
+            api_endpoint_override=test_api_endpoint
+        )
+
+        mock_is_default_universe.assert_called_once()
+        
mock_base_get_client_options.assert_called_once_with(api_endpoint_override=test_api_endpoint)
+        assert result == mock_base_get_client_options.return_value
+
     
@mock.patch("airflow.providers.google.cloud.hooks.bigtable.BigtableHook.get_client_options")
     
@mock.patch("airflow.providers.google.cloud.hooks.bigtable.BigtableHook.get_credentials")
     @mock.patch("airflow.providers.google.cloud.hooks.bigtable.Client")

Reply via email to