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 bdfc02b8ffb Fix GKE operator imports with older Kubernetes providers 
(#74505)
bdfc02b8ffb is described below

commit bdfc02b8ffb647da6bc56dfc1e3a563887e7ec33
Author: Alejandro Morgante <[email protected]>
AuthorDate: Fri Oct 9 15:45:15 2026 -0300

    Fix GKE operator imports with older Kubernetes providers (#74505)
    
    * Keep GKE operators importable with older Kubernetes providers
    
    Older Kubernetes provider versions lack the Pod exec operator. That 
optional feature must not prevent users from importing or creating existing GKE 
operators.
    
    * Exclude the GKE compatibility fallback from example coverage
    
    The compatibility placeholder cannot be used independently and therefore 
has no system example.
    
    * Chain the pod_exec ImportError and split the fallback test
    
    Keep the original ImportError as the cause of the missing-provider
    error, import AirflowOptionalProviderFeatureException at module top,
    and split the fallback test so each behaviour is asserted separately.
    
    Generated-by: Claude Opus 5
    
    ---------
    
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 .../tests/unit/always/test_project_structure.py    |  2 ++
 .../google/cloud/operators/kubernetes_engine.py    | 26 ++++++++++-----
 .../cloud/operators/test_kubernetes_engine.py      | 39 ++++++++++++++++++++--
 3 files changed, 55 insertions(+), 12 deletions(-)

diff --git a/airflow-core/tests/unit/always/test_project_structure.py 
b/airflow-core/tests/unit/always/test_project_structure.py
index ad6629acb2f..8149b3806d6 100644
--- a/airflow-core/tests/unit/always/test_project_structure.py
+++ b/airflow-core/tests/unit/always/test_project_structure.py
@@ -391,6 +391,8 @@ class 
TestGoogleProviderProjectStructure(ExampleCoverageTest, AssetsCoverageTest
         
"airflow.providers.google.cloud.operators.dataproc._DataprocStartStopClusterBaseOperator",
         
"airflow.providers.google.cloud.operators.dataplex.DataplexCatalogBaseOperator",
         
"airflow.providers.google.cloud.operators.managed_kafka.ManagedKafkaBaseOperator",
+        # Compatibility fallback for GKEPodExecOperator, not a standalone 
Google operator.
+        
"airflow.providers.google.cloud.operators.kubernetes_engine.KubernetesPodExecOperator",
         
"airflow.providers.google.cloud.operators.vertex_ai.custom_job.CustomTrainingJobBaseOperator",
         
"airflow.providers.google.cloud.operators.vertex_ai.ray.RayBaseOperator",
         "airflow.providers.google.cloud.operators.ray.RayJobBaseOperator",
diff --git 
a/providers/google/src/airflow/providers/google/cloud/operators/kubernetes_engine.py
 
b/providers/google/src/airflow/providers/google/cloud/operators/kubernetes_engine.py
index cfdb01d28d6..26842379936 100644
--- 
a/providers/google/src/airflow/providers/google/cloud/operators/kubernetes_engine.py
+++ 
b/providers/google/src/airflow/providers/google/cloud/operators/kubernetes_engine.py
@@ -41,7 +41,13 @@ from airflow.providers.cncf.kubernetes.operators.resource 
import (
     KubernetesDeleteResourceOperator,
 )
 from airflow.providers.cncf.kubernetes.utils.pod_manager import OnFinishAction
-from airflow.providers.common.compat.sdk import AirflowException, conf, 
timezone
+from airflow.providers.common.compat.sdk import (
+    AirflowException,
+    AirflowOptionalProviderFeatureException,
+    BaseOperator,
+    conf,
+    timezone,
+)
 from airflow.providers.google.cloud.hooks.kubernetes_engine import (
     GKEHook,
     GKEKubernetesHook,
@@ -63,19 +69,21 @@ from airflow.providers_manager import ProvidersManager
 
 try:
     from airflow.providers.cncf.kubernetes.operators.pod_exec import 
KubernetesPodExecOperator
-except ImportError:
-    from airflow.providers.common.compat.sdk import 
AirflowOptionalProviderFeatureException
+except ImportError as e:
+    _pod_exec_import_error = e
+
+    class KubernetesPodExecOperator(BaseOperator):  # type: ignore[no-redef]
+        """Keep existing GKE operators importable with older Kubernetes 
providers."""
+
+        def __init__(self, **kwargs) -> None:
+            raise AirflowOptionalProviderFeatureException(
+                "GKEPodExecOperator requires 
apache-airflow-providers-cncf-kubernetes>=10.22.0."
+            ) from _pod_exec_import_error
 
-    raise AirflowOptionalProviderFeatureException(
-        "Failed to import KubernetesPodExecOperator. This operator is only 
available in cncf-kubernetes "
-        "provider version >=10.22.0"
-    )
 
 try:
     from airflow.providers.cncf.kubernetes.operators.job import 
KubernetesDeleteJobOperator
 except ImportError:
-    from airflow.providers.common.compat.sdk import 
AirflowOptionalProviderFeatureException
-
     raise AirflowOptionalProviderFeatureException(
         "Failed to import KubernetesDeleteJobOperator. This operator is only 
available in cncf-kubernetes "
         "provider version >=8.1.0"
diff --git 
a/providers/google/tests/unit/google/cloud/operators/test_kubernetes_engine.py 
b/providers/google/tests/unit/google/cloud/operators/test_kubernetes_engine.py
index 0008fc31c37..f820e493bfb 100644
--- 
a/providers/google/tests/unit/google/cloud/operators/test_kubernetes_engine.py
+++ 
b/providers/google/tests/unit/google/cloud/operators/test_kubernetes_engine.py
@@ -829,12 +829,45 @@ class TestGKEStartKueueInsideClusterOperator:
         )
 
 
[email protected]("sys.modules", 
{"airflow.providers.cncf.kubernetes.operators.pod_exec": None})
+def 
test_missing_kubernetes_pod_exec_operator_keeps_other_gke_operators_usable():
+    operators = runpy.run_path(kubernetes_engine.__file__)
+    create_cluster = operators["GKECreateClusterOperator"](
+        task_id=TEST_TASK_ID,
+        project_id=TEST_PROJECT_ID,
+        location=TEST_LOCATION,
+        body={"name": GKE_CLUSTER_NAME, "node_pools": [{"name": "pool", 
"initial_node_count": 1}]},
+    )
+    start_pod = operators["GKEStartPodOperator"](
+        task_id=TEST_TASK_ID,
+        project_id=TEST_PROJECT_ID,
+        location=TEST_LOCATION,
+        cluster_name=GKE_CLUSTER_NAME,
+        image=TEST_IMAGE,
+    )
+
+    assert create_cluster.location == TEST_LOCATION
+    assert start_pod.cluster_name == GKE_CLUSTER_NAME
+    assert start_pod.image == TEST_IMAGE
+
+
 @mock.patch.dict("sys.modules", 
{"airflow.providers.cncf.kubernetes.operators.pod_exec": None})
 def 
test_missing_kubernetes_pod_exec_operator_reports_required_provider_version():
+    operators = runpy.run_path(kubernetes_engine.__file__)
+
     with pytest.raises(
-        AirflowOptionalProviderFeatureException, match="cncf-kubernetes 
provider version >=10.22.0"
-    ):
-        runpy.run_path(kubernetes_engine.__file__)
+        AirflowOptionalProviderFeatureException,
+        match=r"GKEPodExecOperator requires 
apache-airflow-providers-cncf-kubernetes>=10\.22\.0",
+    ) as exc_info:
+        operators["GKEPodExecOperator"](
+            task_id=TEST_TASK_ID,
+            location=TEST_LOCATION,
+            cluster_name=GKE_CLUSTER_NAME,
+            pod_name=K8S_POD_NAME,
+            command=["echo", "hello"],
+        )
+
+    assert isinstance(exc_info.value.__cause__, ImportError)
 
 
 class TestGKEPodExecOperator:

Reply via email to