apache-airflow-providers-cncf-kubernetes 10.3.1rc1__py3-none-any.whl → 10.4.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apache-airflow-providers-cncf-kubernetes might be problematic. Click here for more details.

@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "10.3.1"
32
+ __version__ = "10.4.0"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
35
  "2.9.0"
@@ -27,8 +27,9 @@ def get_provider_info():
27
27
  "name": "Kubernetes",
28
28
  "description": "`Kubernetes <https://kubernetes.io/>`__\n",
29
29
  "state": "ready",
30
- "source-date-epoch": 1741508564,
30
+ "source-date-epoch": 1742480644,
31
31
  "versions": [
32
+ "10.4.0",
32
33
  "10.3.1",
33
34
  "10.3.0",
34
35
  "10.1.0",
@@ -213,7 +213,9 @@ class KubernetesPodOperator(BaseOperator):
213
213
  :param base_container_name: The name of the base container in the pod. This container's logs
214
214
  will appear as part of this task's logs if get_logs is True. Defaults to None. If None,
215
215
  will consult the class variable BASE_CONTAINER_NAME (which defaults to "base") for the base
216
- container name to use.
216
+ container name to use. (templated)
217
+ :param base_container_status_polling_interval: Polling period in seconds to check for the pod base
218
+ container status. Default to 1s.
217
219
  :param deferrable: Run operator in the deferrable mode.
218
220
  :param poll_interval: Polling period in seconds to check for the status. Used only in deferrable mode.
219
221
  :param log_pod_spec_on_failure: Log the pod's specification if a failure occurs
@@ -261,6 +263,7 @@ class KubernetesPodOperator(BaseOperator):
261
263
  "env_from",
262
264
  "node_selector",
263
265
  "kubernetes_conn_id",
266
+ "base_container_name",
264
267
  )
265
268
  template_fields_renderers = {"env_vars": "py"}
266
269
 
@@ -288,6 +291,7 @@ class KubernetesPodOperator(BaseOperator):
288
291
  startup_check_interval_seconds: int = 5,
289
292
  get_logs: bool = True,
290
293
  base_container_name: str | None = None,
294
+ base_container_status_polling_interval: float = 1,
291
295
  init_container_logs: Iterable[str] | str | Literal[True] | None = None,
292
296
  container_logs: Iterable[str] | str | Literal[True] | None = None,
293
297
  image_pull_policy: str | None = None,
@@ -365,6 +369,7 @@ class KubernetesPodOperator(BaseOperator):
365
369
  # Fallback to the class variable BASE_CONTAINER_NAME here instead of via default argument value
366
370
  # in the init method signature, to be compatible with subclasses overloading the class variable value.
367
371
  self.base_container_name = base_container_name or self.BASE_CONTAINER_NAME
372
+ self.base_container_status_polling_interval = base_container_status_polling_interval
368
373
  self.init_container_logs = init_container_logs
369
374
  self.container_logs = container_logs or self.base_container_name
370
375
  self.image_pull_policy = image_pull_policy
@@ -720,7 +725,11 @@ class KubernetesPodOperator(BaseOperator):
720
725
  if not self.get_logs or (
721
726
  self.container_logs is not True and self.base_container_name not in self.container_logs
722
727
  ):
723
- self.pod_manager.await_container_completion(pod=pod, container_name=self.base_container_name)
728
+ self.pod_manager.await_container_completion(
729
+ pod=pod,
730
+ container_name=self.base_container_name,
731
+ polling_time=self.base_container_status_polling_interval,
732
+ )
724
733
  except kubernetes.client.exceptions.ApiException as exc:
725
734
  self._handle_api_exception(exc, pod)
726
735
 
@@ -834,6 +843,10 @@ class KubernetesPodOperator(BaseOperator):
834
843
  last_log_time = event.get("last_log_time")
835
844
 
836
845
  if event["status"] in ("error", "failed", "timeout"):
846
+ event_message = event.get("message", "No message provided")
847
+ self.log.error(
848
+ "Trigger emitted an %s event, failing the task: %s", event["status"], event_message
849
+ )
837
850
  # fetch some logs when pod is failed
838
851
  if self.get_logs:
839
852
  self._write_logs(self.pod, follow=follow, since_time=last_log_time)
@@ -22,7 +22,10 @@ import traceback
22
22
  from collections.abc import AsyncIterator
23
23
  from enum import Enum
24
24
  from functools import cached_property
25
- from typing import TYPE_CHECKING, Any
25
+ from typing import TYPE_CHECKING, Any, cast
26
+
27
+ import tenacity
28
+ from kubernetes_asyncio.client.models import V1Pod
26
29
 
27
30
  from airflow.providers.cncf.kubernetes.hooks.kubernetes import AsyncKubernetesHook
28
31
  from airflow.providers.cncf.kubernetes.utils.pod_manager import (
@@ -33,7 +36,6 @@ from airflow.providers.cncf.kubernetes.utils.pod_manager import (
33
36
  from airflow.triggers.base import BaseTrigger, TriggerEvent
34
37
 
35
38
  if TYPE_CHECKING:
36
- from kubernetes_asyncio.client.models import V1Pod
37
39
  from pendulum import DateTime
38
40
 
39
41
 
@@ -200,7 +202,7 @@ class KubernetesPodTrigger(BaseTrigger):
200
202
  async def _wait_for_pod_start(self) -> ContainerState:
201
203
  """Loops until pod phase leaves ``PENDING`` If timeout is reached, throws error."""
202
204
  while True:
203
- pod = await self.hook.get_pod(self.pod_name, self.pod_namespace)
205
+ pod = await self._get_pod()
204
206
  if not pod.status.phase == "Pending":
205
207
  return self.define_container_state(pod)
206
208
 
@@ -223,7 +225,7 @@ class KubernetesPodTrigger(BaseTrigger):
223
225
  if self.logging_interval is not None:
224
226
  time_get_more_logs = time_begin + datetime.timedelta(seconds=self.logging_interval)
225
227
  while True:
226
- pod = await self.hook.get_pod(self.pod_name, self.pod_namespace)
228
+ pod = await self._get_pod()
227
229
  container_state = self.define_container_state(pod)
228
230
  if container_state == ContainerState.TERMINATED:
229
231
  return TriggerEvent(
@@ -257,8 +259,16 @@ class KubernetesPodTrigger(BaseTrigger):
257
259
  self.log.debug("Sleeping for %s seconds.", self.poll_interval)
258
260
  await asyncio.sleep(self.poll_interval)
259
261
 
260
- def _get_async_hook(self) -> AsyncKubernetesHook:
261
- # TODO: Remove this method when the min version of kubernetes provider is 7.12.0 in Google provider.
262
+ @tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential(), reraise=True)
263
+ async def _get_pod(self) -> V1Pod:
264
+ """Get the pod from Kubernetes with retries."""
265
+ pod = await self.hook.get_pod(name=self.pod_name, namespace=self.pod_namespace)
266
+ # Due to AsyncKubernetesHook overriding get_pod, we need to cast the return
267
+ # value to kubernetes_asyncio.V1Pod, because it's perceived as different type
268
+ return cast(V1Pod, pod)
269
+
270
+ @cached_property
271
+ def hook(self) -> AsyncKubernetesHook:
262
272
  return AsyncKubernetesHook(
263
273
  conn_id=self.kubernetes_conn_id,
264
274
  in_cluster=self.in_cluster,
@@ -266,10 +276,6 @@ class KubernetesPodTrigger(BaseTrigger):
266
276
  cluster_context=self.cluster_context,
267
277
  )
268
278
 
269
- @cached_property
270
- def hook(self) -> AsyncKubernetesHook:
271
- return self._get_async_hook()
272
-
273
279
  def define_container_state(self, pod: V1Pod) -> ContainerState:
274
280
  pod_containers = pod.status.container_statuses
275
281
 
@@ -619,12 +619,14 @@ class PodManager(LoggingMixin):
619
619
  pod_logging_statuses.append(status)
620
620
  return pod_logging_statuses
621
621
 
622
- def await_container_completion(self, pod: V1Pod, container_name: str) -> None:
622
+ def await_container_completion(self, pod: V1Pod, container_name: str, polling_time: float = 1) -> None:
623
623
  """
624
624
  Wait for the given container in the given pod to be completed.
625
625
 
626
626
  :param pod: pod spec that will be monitored
627
627
  :param container_name: name of the container within the pod to monitor
628
+ :param polling_time: polling time between two container status checks.
629
+ Defaults to 1s.
628
630
  """
629
631
  while True:
630
632
  remote_pod = self.read_pod(pod)
@@ -632,7 +634,7 @@ class PodManager(LoggingMixin):
632
634
  if terminated:
633
635
  break
634
636
  self.log.info("Waiting for container '%s' state to be completed", container_name)
635
- time.sleep(1)
637
+ time.sleep(polling_time)
636
638
 
637
639
  def await_pod_completion(
638
640
  self, pod: V1Pod, istio_enabled: bool = False, container_name: str = "base"
@@ -805,26 +807,35 @@ class PodManager(LoggingMixin):
805
807
  )
806
808
  def extract_xcom_json(self, pod: V1Pod) -> str:
807
809
  """Retrieve XCom value and also check if xcom json is valid."""
810
+ command = (
811
+ f"if [ -s {PodDefaults.XCOM_MOUNT_PATH}/return.json ]; "
812
+ f"then cat {PodDefaults.XCOM_MOUNT_PATH}/return.json; "
813
+ f"else echo {EMPTY_XCOM_RESULT}; fi"
814
+ )
808
815
  with closing(
809
816
  kubernetes_stream(
810
817
  self._client.connect_get_namespaced_pod_exec,
811
818
  pod.metadata.name,
812
819
  pod.metadata.namespace,
813
820
  container=PodDefaults.SIDECAR_CONTAINER_NAME,
814
- command=["/bin/sh"],
815
- stdin=True,
821
+ command=[
822
+ "/bin/sh",
823
+ "-c",
824
+ command,
825
+ ],
826
+ stdin=False,
816
827
  stdout=True,
817
828
  stderr=True,
818
829
  tty=False,
819
830
  _preload_content=False,
820
831
  )
821
- ) as resp:
822
- result = self._exec_pod_command(
823
- resp,
824
- f"if [ -s {PodDefaults.XCOM_MOUNT_PATH}/return.json ]; "
825
- f"then cat {PodDefaults.XCOM_MOUNT_PATH}/return.json; "
826
- f"else echo {EMPTY_XCOM_RESULT}; fi",
827
- )
832
+ ) as client:
833
+ self.log.info("Running command... %s", command)
834
+ client.run_forever()
835
+ if client.peek_stderr():
836
+ stderr = client.read_stderr()
837
+ self.log.error("stderr from command: %s", stderr)
838
+ result = client.read_all()
828
839
  if result and result.rstrip() != EMPTY_XCOM_RESULT:
829
840
  # Note: result string is parsed to check if its valid json.
830
841
  # This function still returns a string which is converted into json in the calling method.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apache-airflow-providers-cncf-kubernetes
3
- Version: 10.3.1rc1
3
+ Version: 10.4.0rc1
4
4
  Summary: Provider package apache-airflow-providers-cncf-kubernetes for Apache Airflow
5
5
  Keywords: airflow-provider,cncf.kubernetes,airflow,integration
6
6
  Author-email: Apache Software Foundation <dev@airflow.apache.org>
@@ -27,11 +27,11 @@ Requires-Dist: cryptography>=41.0.0
27
27
  Requires-Dist: kubernetes>=29.0.0,<=31.0.0
28
28
  Requires-Dist: kubernetes_asyncio>=29.0.0,<=31.0.0
29
29
  Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
30
- Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.3.1/changelog.html
31
- Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.3.1
30
+ Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.4.0/changelog.html
31
+ Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.4.0
32
+ Project-URL: Mastodon, https://fosstodon.org/@airflow
32
33
  Project-URL: Slack Chat, https://s.apache.org/airflow-slack
33
34
  Project-URL: Source Code, https://github.com/apache/airflow
34
- Project-URL: Twitter, https://x.com/ApacheAirflow
35
35
  Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
36
36
 
37
37
 
@@ -59,7 +59,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
59
59
 
60
60
  Package ``apache-airflow-providers-cncf-kubernetes``
61
61
 
62
- Release: ``10.3.1``
62
+ Release: ``10.4.0``
63
63
 
64
64
 
65
65
  `Kubernetes <https://kubernetes.io/>`__
@@ -72,7 +72,7 @@ This is a provider package for ``cncf.kubernetes`` provider. All classes for thi
72
72
  are in ``airflow.providers.cncf.kubernetes`` python package.
73
73
 
74
74
  You can find package information and changelog for the provider
75
- in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.3.1/>`_.
75
+ in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.4.0/>`_.
76
76
 
77
77
  Installation
78
78
  ------------
@@ -98,5 +98,5 @@ PIP package Version required
98
98
  ====================== =====================
99
99
 
100
100
  The changelog for the provider package can be found in the
101
- `changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.3.1/changelog.html>`_.
101
+ `changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.4.0/changelog.html>`_.
102
102
 
@@ -1,8 +1,8 @@
1
1
  airflow/providers/cncf/kubernetes/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
2
- airflow/providers/cncf/kubernetes/__init__.py,sha256=dh4Bxuix8J3KKJlqsC94wTE9mXFj35O10tZtnu_B-nI,1503
2
+ airflow/providers/cncf/kubernetes/__init__.py,sha256=J9O8rOoYu-94ZUrjAZ_noDh1WJF4nDtP6wIgWvgVpeI,1503
3
3
  airflow/providers/cncf/kubernetes/callbacks.py,sha256=5zGmQthojdT9iBEV3LIyBq-oKzjv2D4dOYCjYRbb61c,6076
4
4
  airflow/providers/cncf/kubernetes/exceptions.py,sha256=3cNEZTnrltBsqwzHiLfckwYYc_IWY1g4PcRs6zuMWWA,1137
5
- airflow/providers/cncf/kubernetes/get_provider_info.py,sha256=z5ETfTSpSYyt8ZQJrRSGYrWU8-gSfNx_K-fiMSH6Bl0,17748
5
+ airflow/providers/cncf/kubernetes/get_provider_info.py,sha256=-jfmOWThKJwsuEFZbB-1zZfjfEFEDF4QQfAm0g0Y0_o,17770
6
6
  airflow/providers/cncf/kubernetes/k8s_model.py,sha256=xmdFhX29DjegoZ-cq8-KDL9soVYXf4OpU6fAGr3cPTU,2101
7
7
  airflow/providers/cncf/kubernetes/kube_client.py,sha256=yflZxLousXA9d7t67KrEy55qzb1cUhEyy6yCPkEem28,5329
8
8
  airflow/providers/cncf/kubernetes/kube_config.py,sha256=3qWdCp2z4g8gX_sIOProgwp52UxM5kAIYabkxaX297g,5079
@@ -32,7 +32,7 @@ airflow/providers/cncf/kubernetes/operators/__init__.py,sha256=mlJxuZLkd5x-iq2SB
32
32
  airflow/providers/cncf/kubernetes/operators/custom_object_launcher.py,sha256=CS8VNDq0ZBvq25AkLn90Au1L75PrWM6sdz81PNIYG0U,15315
33
33
  airflow/providers/cncf/kubernetes/operators/job.py,sha256=GXtTHoK2y7mG7rvGdHt0KIp_0V8d0wKzZ2rUV1zg56g,23773
34
34
  airflow/providers/cncf/kubernetes/operators/kueue.py,sha256=kBKw3R2IDMNOaAQ3oMNaerPBxWhCW_xJT4WxVVDhBGo,4565
35
- airflow/providers/cncf/kubernetes/operators/pod.py,sha256=OEyrFOc61yeoGD4AH6K6dyRM6u5gbK914bbrB8xt2W8,57108
35
+ airflow/providers/cncf/kubernetes/operators/pod.py,sha256=RHLl00cOmnXRtGUDPqvDa4ay69c0IBDrnhTnr4mCkhE,57814
36
36
  airflow/providers/cncf/kubernetes/operators/resource.py,sha256=4nS-eiVEGlotp-gCkHlwRuenj3pnKhZ4khh9s2cjZms,7597
37
37
  airflow/providers/cncf/kubernetes/operators/spark_kubernetes.py,sha256=z1CF8jRNVwUWBAWhP7Qgl_Wly24hBA14ONu2N9zEdFg,13847
38
38
  airflow/providers/cncf/kubernetes/pod_template_file_examples/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
@@ -47,13 +47,13 @@ airflow/providers/cncf/kubernetes/sensors/__init__.py,sha256=9hdXHABrVpkbpjZgUft
47
47
  airflow/providers/cncf/kubernetes/sensors/spark_kubernetes.py,sha256=q5VZPf037pbsunFUjUrtz7M5oJW7waZ1q8snNAugCIk,5380
48
48
  airflow/providers/cncf/kubernetes/triggers/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
49
49
  airflow/providers/cncf/kubernetes/triggers/job.py,sha256=DGbC1FZktBF-00Lb0pU9iIKQnmdW8HWklp5Wwq54OEY,6754
50
- airflow/providers/cncf/kubernetes/triggers/pod.py,sha256=7vLj9SvOOwh9p5XSZIr7mj47YWe_aPQgtN7uEZhma68,12653
50
+ airflow/providers/cncf/kubernetes/triggers/pod.py,sha256=C3siy1RfpmN8dpTh1sZEanDRn37k3-hRVvh5MzxUmsc,12887
51
51
  airflow/providers/cncf/kubernetes/utils/__init__.py,sha256=ClZN0VPjWySdVwS_ktH7rrgL9VLAcs3OSJSB9s3zaYw,863
52
52
  airflow/providers/cncf/kubernetes/utils/delete_from.py,sha256=poObZSoEJwQyaYWilEURs8f4CDY2sn_pfwS31Lf579A,5195
53
53
  airflow/providers/cncf/kubernetes/utils/k8s_resource_iterator.py,sha256=DLypjkD_3YDixRTcsxEjgvHZNbbG9qamlz05eBqaWzU,1955
54
- airflow/providers/cncf/kubernetes/utils/pod_manager.py,sha256=JGyp9PbJZW6hgdbioodBzwTsu_jue2A0cFKYn5yWwo4,36474
54
+ airflow/providers/cncf/kubernetes/utils/pod_manager.py,sha256=Lk4RWe1pa9KzSR4x_Mm9CFSuQwL74VNYqLHpQ5172gM,36931
55
55
  airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py,sha256=k6bdmVJ21OrAwGmWwledRrAmaty9ZrmbuM-IbaI4mqo,2519
56
- apache_airflow_providers_cncf_kubernetes-10.3.1rc1.dist-info/entry_points.txt,sha256=ByD3QJJyP9CfmTYtpNI1953akD38RUDgpGXLaq9vpOw,111
57
- apache_airflow_providers_cncf_kubernetes-10.3.1rc1.dist-info/WHEEL,sha256=_2ozNFCLWc93bK4WKHCO-eDUENDlo-dgc9cU3qokYO4,82
58
- apache_airflow_providers_cncf_kubernetes-10.3.1rc1.dist-info/METADATA,sha256=bjIhcTachOTR_a0non6JVd3kJIC5_cnHyzikwn2XmJI,4308
59
- apache_airflow_providers_cncf_kubernetes-10.3.1rc1.dist-info/RECORD,,
56
+ apache_airflow_providers_cncf_kubernetes-10.4.0rc1.dist-info/entry_points.txt,sha256=ByD3QJJyP9CfmTYtpNI1953akD38RUDgpGXLaq9vpOw,111
57
+ apache_airflow_providers_cncf_kubernetes-10.4.0rc1.dist-info/WHEEL,sha256=_2ozNFCLWc93bK4WKHCO-eDUENDlo-dgc9cU3qokYO4,82
58
+ apache_airflow_providers_cncf_kubernetes-10.4.0rc1.dist-info/METADATA,sha256=Sv4IRzUYFIJ9T9spK9Z1kKlwGogQpawgnHk5guioA8c,4312
59
+ apache_airflow_providers_cncf_kubernetes-10.4.0rc1.dist-info/RECORD,,