apache-airflow-providers-cncf-kubernetes 7.11.0__tar.gz → 7.12.0__tar.gz
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.
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/PKG-INFO +6 -6
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/README.rst +3 -3
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/__init__.py +1 -1
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py +36 -11
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_types.py +4 -3
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py +8 -1
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/get_provider_info.py +2 -1
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/hooks/kubernetes.py +4 -4
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/kubernetes_helper_functions.py +4 -6
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/operators/pod.py +19 -4
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_generator.py +8 -4
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/triggers/pod.py +17 -13
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/utils/pod_manager.py +35 -13
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/pyproject.toml +3 -3
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/LICENSE +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/backcompat/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/backcompat/backwards_compat_converters.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/decorators/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/decorators/kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/executors/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/executors/local_kubernetes_executor.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/hooks/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/k8s_model.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/kube_client.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/kube_config.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/kubernetes_executor_templates/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/kubernetes_executor_templates/basic_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/operators/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/operators/kubernetes_pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/operators/resource.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/operators/spark_kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_generator_deprecated.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_launcher_deprecated.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_template_file_examples/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_image_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_volume_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/pod_template_file_examples/git_sync_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/python_kubernetes_script.jinja2 +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/python_kubernetes_script.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/secret.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/sensors/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/sensors/spark_kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/template_rendering.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/triggers/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/triggers/kubernetes_pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/utils/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/utils/delete_from.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/utils/k8s_resource_iterator.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-7.11.0 → apache_airflow_providers_cncf_kubernetes-7.12.0}/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: apache-airflow-providers-cncf-kubernetes
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.12.0
|
|
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>
|
|
@@ -28,8 +28,8 @@ Requires-Dist: google-re2>=1.0
|
|
|
28
28
|
Requires-Dist: kubernetes>=21.7.0,<24
|
|
29
29
|
Requires-Dist: kubernetes_asyncio>=18.20.1,<25
|
|
30
30
|
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
|
31
|
-
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
32
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
31
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0/changelog.html
|
|
32
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0
|
|
33
33
|
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
|
34
34
|
Project-URL: Source Code, https://github.com/apache/airflow
|
|
35
35
|
Project-URL: Twitter, https://twitter.com/ApacheAirflow
|
|
@@ -79,7 +79,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
|
|
|
79
79
|
|
|
80
80
|
Package ``apache-airflow-providers-cncf-kubernetes``
|
|
81
81
|
|
|
82
|
-
Release: ``7.
|
|
82
|
+
Release: ``7.12.0``
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
`Kubernetes <https://kubernetes.io/>`__
|
|
@@ -92,7 +92,7 @@ This is a provider package for ``cncf.kubernetes`` provider. All classes for thi
|
|
|
92
92
|
are in ``airflow.providers.cncf.kubernetes`` python package.
|
|
93
93
|
|
|
94
94
|
You can find package information and changelog for the provider
|
|
95
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
95
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0/>`_.
|
|
96
96
|
|
|
97
97
|
Installation
|
|
98
98
|
------------
|
|
@@ -119,4 +119,4 @@ PIP package Version required
|
|
|
119
119
|
====================== ==================
|
|
120
120
|
|
|
121
121
|
The changelog for the provider package can be found in the
|
|
122
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
122
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0/changelog.html>`_.
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
Package ``apache-airflow-providers-cncf-kubernetes``
|
|
44
44
|
|
|
45
|
-
Release: ``7.
|
|
45
|
+
Release: ``7.12.0``
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
`Kubernetes <https://kubernetes.io/>`__
|
|
@@ -55,7 +55,7 @@ This is a provider package for ``cncf.kubernetes`` provider. All classes for thi
|
|
|
55
55
|
are in ``airflow.providers.cncf.kubernetes`` python package.
|
|
56
56
|
|
|
57
57
|
You can find package information and changelog for the provider
|
|
58
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
58
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0/>`_.
|
|
59
59
|
|
|
60
60
|
Installation
|
|
61
61
|
------------
|
|
@@ -82,4 +82,4 @@ PIP package Version required
|
|
|
82
82
|
====================== ==================
|
|
83
83
|
|
|
84
84
|
The changelog for the provider package can be found in the
|
|
85
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
85
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0/changelog.html>`_.
|
|
@@ -34,6 +34,7 @@ from datetime import datetime
|
|
|
34
34
|
from queue import Empty, Queue
|
|
35
35
|
from typing import TYPE_CHECKING, Any, Sequence
|
|
36
36
|
|
|
37
|
+
from kubernetes.dynamic import DynamicClient
|
|
37
38
|
from sqlalchemy import select, update
|
|
38
39
|
|
|
39
40
|
from airflow.providers.cncf.kubernetes.pod_generator import PodMutationHookException, PodReconciliationError
|
|
@@ -74,7 +75,10 @@ except ImportError:
|
|
|
74
75
|
raise
|
|
75
76
|
from airflow.configuration import conf
|
|
76
77
|
from airflow.executors.base_executor import BaseExecutor
|
|
77
|
-
from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import
|
|
78
|
+
from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import (
|
|
79
|
+
ADOPTED,
|
|
80
|
+
POD_EXECUTOR_DONE_KEY,
|
|
81
|
+
)
|
|
78
82
|
from airflow.providers.cncf.kubernetes.kube_config import KubeConfig
|
|
79
83
|
from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import annotations_to_key
|
|
80
84
|
from airflow.utils.event_scheduler import EventScheduler
|
|
@@ -160,19 +164,31 @@ class KubernetesExecutor(BaseExecutor):
|
|
|
160
164
|
super().__init__(parallelism=self.kube_config.parallelism)
|
|
161
165
|
|
|
162
166
|
def _list_pods(self, query_kwargs):
|
|
167
|
+
query_kwargs["header_params"] = {
|
|
168
|
+
"Accept": "application/json;as=PartialObjectMetadataList;v=v1;g=meta.k8s.io"
|
|
169
|
+
}
|
|
170
|
+
dynamic_client = DynamicClient(self.kube_client.api_client)
|
|
171
|
+
pod_resource = dynamic_client.resources.get(api_version="v1", kind="Pod")
|
|
163
172
|
if self.kube_config.multi_namespace_mode:
|
|
164
173
|
if self.kube_config.multi_namespace_mode_namespace_list:
|
|
165
|
-
|
|
166
|
-
for namespace in self.kube_config.multi_namespace_mode_namespace_list:
|
|
167
|
-
pods.extend(
|
|
168
|
-
self.kube_client.list_namespaced_pod(namespace=namespace, **query_kwargs).items
|
|
169
|
-
)
|
|
174
|
+
namespaces = self.kube_config.multi_namespace_mode_namespace_list
|
|
170
175
|
else:
|
|
171
|
-
|
|
176
|
+
namespaces = [None]
|
|
172
177
|
else:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
namespaces = [self.kube_config.kube_namespace]
|
|
179
|
+
|
|
180
|
+
pods = []
|
|
181
|
+
for namespace in namespaces:
|
|
182
|
+
# Dynamic Client list pods is throwing TypeError when there are no matching pods to return
|
|
183
|
+
# This bug was fixed in MR https://github.com/kubernetes-client/python/pull/2155
|
|
184
|
+
# TODO: Remove the try-except clause once we upgrade the K8 Python client version which
|
|
185
|
+
# includes the above MR
|
|
186
|
+
try:
|
|
187
|
+
pods.extend(
|
|
188
|
+
dynamic_client.get(resource=pod_resource, namespace=namespace, **query_kwargs).items
|
|
189
|
+
)
|
|
190
|
+
except TypeError:
|
|
191
|
+
continue
|
|
176
192
|
|
|
177
193
|
return pods
|
|
178
194
|
|
|
@@ -450,7 +466,7 @@ class KubernetesExecutor(BaseExecutor):
|
|
|
450
466
|
def _change_state(
|
|
451
467
|
self,
|
|
452
468
|
key: TaskInstanceKey,
|
|
453
|
-
state: TaskInstanceState | None,
|
|
469
|
+
state: TaskInstanceState | str | None,
|
|
454
470
|
pod_name: str,
|
|
455
471
|
namespace: str,
|
|
456
472
|
session: Session = NEW_SESSION,
|
|
@@ -458,6 +474,15 @@ class KubernetesExecutor(BaseExecutor):
|
|
|
458
474
|
if TYPE_CHECKING:
|
|
459
475
|
assert self.kube_scheduler
|
|
460
476
|
|
|
477
|
+
if state == ADOPTED:
|
|
478
|
+
# When the task pod is adopted by another executor,
|
|
479
|
+
# then remove the task from the current executor running queue.
|
|
480
|
+
try:
|
|
481
|
+
self.running.remove(key)
|
|
482
|
+
except KeyError:
|
|
483
|
+
self.log.debug("TI key not in running: %s", key)
|
|
484
|
+
return
|
|
485
|
+
|
|
461
486
|
if state == TaskInstanceState.RUNNING:
|
|
462
487
|
self.event_buffer[key] = state, None
|
|
463
488
|
return
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
# under the License.
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
|
|
20
20
|
|
|
21
|
+
ADOPTED = "adopted"
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from airflow.executors.base_executor import CommandType
|
|
23
24
|
from airflow.models.taskinstance import TaskInstanceKey
|
|
@@ -27,10 +28,10 @@ if TYPE_CHECKING:
|
|
|
27
28
|
KubernetesJobType = Tuple[TaskInstanceKey, CommandType, Any, Optional[str]]
|
|
28
29
|
|
|
29
30
|
# key, pod state, pod_name, namespace, resource_version
|
|
30
|
-
KubernetesResultsType = Tuple[TaskInstanceKey, Optional[TaskInstanceState], str, str, str]
|
|
31
|
+
KubernetesResultsType = Tuple[TaskInstanceKey, Optional[Union[TaskInstanceState, str]], str, str, str]
|
|
31
32
|
|
|
32
33
|
# pod_name, namespace, pod state, annotations, resource_version
|
|
33
|
-
KubernetesWatchType = Tuple[str, str, Optional[TaskInstanceState], Dict[str, str], str]
|
|
34
|
+
KubernetesWatchType = Tuple[str, str, Optional[Union[TaskInstanceState, str]], Dict[str, str], str]
|
|
34
35
|
|
|
35
36
|
ALL_NAMESPACES = "ALL_NAMESPACES"
|
|
36
37
|
POD_EXECUTOR_DONE_KEY = "airflow_executor_done"
|
|
@@ -40,6 +40,7 @@ from airflow.utils.state import TaskInstanceState
|
|
|
40
40
|
|
|
41
41
|
try:
|
|
42
42
|
from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import (
|
|
43
|
+
ADOPTED,
|
|
43
44
|
ALL_NAMESPACES,
|
|
44
45
|
POD_EXECUTOR_DONE_KEY,
|
|
45
46
|
)
|
|
@@ -220,7 +221,13 @@ class KubernetesJobWatcher(multiprocessing.Process, LoggingMixin):
|
|
|
220
221
|
pod = event["object"]
|
|
221
222
|
annotations_string = annotations_for_logging_task_metadata(annotations)
|
|
222
223
|
"""Process status response."""
|
|
223
|
-
if
|
|
224
|
+
if event["type"] == "DELETED" and not pod.metadata.deletion_timestamp:
|
|
225
|
+
# This will happen only when the task pods are adopted by another executor.
|
|
226
|
+
# So, there is no change in the pod state.
|
|
227
|
+
# However, need to free the executor slot from the current executor.
|
|
228
|
+
self.log.info("Event: pod %s adopted, annotations: %s", pod_name, annotations_string)
|
|
229
|
+
self.watcher_queue.put((pod_name, namespace, ADOPTED, annotations, resource_version))
|
|
230
|
+
elif status == "Pending":
|
|
224
231
|
# deletion_timestamp is set by kube server when a graceful deletion is requested.
|
|
225
232
|
# since kube server have received request to delete pod set TI state failed
|
|
226
233
|
if event["type"] == "DELETED" and pod.metadata.deletion_timestamp:
|
|
@@ -28,8 +28,9 @@ def get_provider_info():
|
|
|
28
28
|
"name": "Kubernetes",
|
|
29
29
|
"description": "`Kubernetes <https://kubernetes.io/>`__\n",
|
|
30
30
|
"suspended": False,
|
|
31
|
-
"source-date-epoch":
|
|
31
|
+
"source-date-epoch": 1703288121,
|
|
32
32
|
"versions": [
|
|
33
|
+
"7.12.0",
|
|
33
34
|
"7.11.0",
|
|
34
35
|
"7.10.0",
|
|
35
36
|
"7.9.0",
|
|
@@ -86,8 +86,8 @@ class KubernetesHook(BaseHook, PodOperatorHookProtocol):
|
|
|
86
86
|
|
|
87
87
|
DEFAULT_NAMESPACE = "default"
|
|
88
88
|
|
|
89
|
-
@
|
|
90
|
-
def get_connection_form_widgets() -> dict[str, Any]:
|
|
89
|
+
@classmethod
|
|
90
|
+
def get_connection_form_widgets(cls) -> dict[str, Any]:
|
|
91
91
|
"""Return connection widgets to add to connection form."""
|
|
92
92
|
from flask_appbuilder.fieldwidgets import BS3TextFieldWidget
|
|
93
93
|
from flask_babel import lazy_gettext
|
|
@@ -111,8 +111,8 @@ class KubernetesHook(BaseHook, PodOperatorHookProtocol):
|
|
|
111
111
|
),
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
@
|
|
115
|
-
def get_ui_field_behaviour() -> dict[str, Any]:
|
|
114
|
+
@classmethod
|
|
115
|
+
def get_ui_field_behaviour(cls) -> dict[str, Any]:
|
|
116
116
|
"""Return custom field behaviour."""
|
|
117
117
|
return {
|
|
118
118
|
"hidden_fields": ["host", "schema", "login", "password", "port", "extra"],
|
|
@@ -34,6 +34,8 @@ log = logging.getLogger(__name__)
|
|
|
34
34
|
|
|
35
35
|
alphanum_lower = string.ascii_lowercase + string.digits
|
|
36
36
|
|
|
37
|
+
POD_NAME_MAX_LENGTH = 63 # Matches Linux kernel's HOST_NAME_MAX default value minus 1.
|
|
38
|
+
|
|
37
39
|
|
|
38
40
|
def rand_str(num):
|
|
39
41
|
"""Generate random lowercase alphanumeric string of length num.
|
|
@@ -43,7 +45,7 @@ def rand_str(num):
|
|
|
43
45
|
return "".join(secrets.choice(alphanum_lower) for _ in range(num))
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
def add_pod_suffix(*, pod_name: str, rand_len: int = 8, max_len: int =
|
|
48
|
+
def add_pod_suffix(*, pod_name: str, rand_len: int = 8, max_len: int = POD_NAME_MAX_LENGTH) -> str:
|
|
47
49
|
"""Add random string to pod name while staying under max length.
|
|
48
50
|
|
|
49
51
|
:param pod_name: name of the pod
|
|
@@ -59,16 +61,12 @@ def create_pod_id(
|
|
|
59
61
|
dag_id: str | None = None,
|
|
60
62
|
task_id: str | None = None,
|
|
61
63
|
*,
|
|
62
|
-
max_length: int =
|
|
64
|
+
max_length: int = POD_NAME_MAX_LENGTH,
|
|
63
65
|
unique: bool = True,
|
|
64
66
|
) -> str:
|
|
65
67
|
"""
|
|
66
68
|
Generate unique pod ID given a dag_id and / or task_id.
|
|
67
69
|
|
|
68
|
-
The default of 80 for max length is somewhat arbitrary, mainly a balance between
|
|
69
|
-
content and not overwhelming terminal windows of reasonable width. The true
|
|
70
|
-
upper limit is 253, and this is enforced in construct_pod.
|
|
71
|
-
|
|
72
70
|
:param dag_id: DAG ID
|
|
73
71
|
:param task_id: Task ID
|
|
74
72
|
:param max_length: max number of characters
|
|
@@ -51,6 +51,7 @@ from airflow.providers.cncf.kubernetes.backcompat.backwards_compat_converters im
|
|
|
51
51
|
convert_volume_mount,
|
|
52
52
|
)
|
|
53
53
|
from airflow.providers.cncf.kubernetes.hooks.kubernetes import KubernetesHook
|
|
54
|
+
from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import POD_NAME_MAX_LENGTH
|
|
54
55
|
from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator
|
|
55
56
|
from airflow.providers.cncf.kubernetes.triggers.pod import KubernetesPodTrigger
|
|
56
57
|
from airflow.providers.cncf.kubernetes.utils import xcom_sidecar # type: ignore[attr-defined]
|
|
@@ -92,7 +93,7 @@ def _rand_str(num):
|
|
|
92
93
|
return "".join(secrets.choice(alphanum_lower) for _ in range(num))
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
def _add_pod_suffix(*, pod_name, rand_len=8, max_len=
|
|
96
|
+
def _add_pod_suffix(*, pod_name, rand_len=8, max_len=POD_NAME_MAX_LENGTH):
|
|
96
97
|
"""Add random string to pod name while staying under max len.
|
|
97
98
|
|
|
98
99
|
TODO: when min airflow version >= 2.5, delete this function and import from kubernetes_helper_functions.
|
|
@@ -107,7 +108,7 @@ def _create_pod_id(
|
|
|
107
108
|
dag_id: str | None = None,
|
|
108
109
|
task_id: str | None = None,
|
|
109
110
|
*,
|
|
110
|
-
max_length: int =
|
|
111
|
+
max_length: int = POD_NAME_MAX_LENGTH,
|
|
111
112
|
unique: bool = True,
|
|
112
113
|
) -> str:
|
|
113
114
|
"""
|
|
@@ -218,6 +219,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
218
219
|
/airflow/xcom/return.json in the container will also be pushed to an
|
|
219
220
|
XCom when the container completes.
|
|
220
221
|
:param pod_template_file: path to pod template file (templated)
|
|
222
|
+
:param pod_template_dict: pod template dictionary (templated)
|
|
221
223
|
:param priority_class_name: priority class name for the launched Pod
|
|
222
224
|
:param pod_runtime_info_envs: (Optional) A list of environment variables,
|
|
223
225
|
to be set in the container.
|
|
@@ -267,6 +269,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
267
269
|
"labels",
|
|
268
270
|
"config_file",
|
|
269
271
|
"pod_template_file",
|
|
272
|
+
"pod_template_dict",
|
|
270
273
|
"namespace",
|
|
271
274
|
"container_resources",
|
|
272
275
|
"volumes",
|
|
@@ -322,6 +325,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
322
325
|
log_events_on_failure: bool = False,
|
|
323
326
|
do_xcom_push: bool = False,
|
|
324
327
|
pod_template_file: str | None = None,
|
|
328
|
+
pod_template_dict: dict | None = None,
|
|
325
329
|
priority_class_name: str | None = None,
|
|
326
330
|
pod_runtime_info_envs: list[k8s.V1EnvVar] | None = None,
|
|
327
331
|
termination_grace_period: int | None = None,
|
|
@@ -404,6 +408,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
404
408
|
self.log_events_on_failure = log_events_on_failure
|
|
405
409
|
self.priority_class_name = priority_class_name
|
|
406
410
|
self.pod_template_file = pod_template_file
|
|
411
|
+
self.pod_template_dict = pod_template_dict
|
|
407
412
|
self.name = self._set_name(name)
|
|
408
413
|
self.random_name_suffix = random_name_suffix
|
|
409
414
|
self.termination_grace_period = termination_grace_period
|
|
@@ -672,6 +677,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
672
677
|
)
|
|
673
678
|
|
|
674
679
|
def execute_complete(self, context: Context, event: dict, **kwargs):
|
|
680
|
+
self.log.debug("Triggered with event: %s", event)
|
|
675
681
|
pod = None
|
|
676
682
|
try:
|
|
677
683
|
pod = self.hook.get_pod(
|
|
@@ -682,7 +688,11 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
682
688
|
# fetch some logs when pod is failed
|
|
683
689
|
if self.get_logs:
|
|
684
690
|
self.write_logs(pod)
|
|
685
|
-
|
|
691
|
+
if "stack_trace" in event:
|
|
692
|
+
message = f"{event['message']}\n{event['stack_trace']}"
|
|
693
|
+
else:
|
|
694
|
+
message = event["message"]
|
|
695
|
+
raise AirflowException(message)
|
|
686
696
|
elif event["status"] == "success":
|
|
687
697
|
# fetch some logs when pod is executed successfully
|
|
688
698
|
if self.get_logs:
|
|
@@ -892,6 +902,11 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
892
902
|
pod_template = pod_generator.PodGenerator.deserialize_model_file(self.pod_template_file)
|
|
893
903
|
if self.full_pod_spec:
|
|
894
904
|
pod_template = PodGenerator.reconcile_pods(pod_template, self.full_pod_spec)
|
|
905
|
+
elif self.pod_template_dict:
|
|
906
|
+
self.log.debug("Pod template dict found, will parse for base pod")
|
|
907
|
+
pod_template = pod_generator.PodGenerator.deserialize_model_dict(self.pod_template_dict)
|
|
908
|
+
if self.full_pod_spec:
|
|
909
|
+
pod_template = PodGenerator.reconcile_pods(pod_template, self.full_pod_spec)
|
|
895
910
|
elif self.full_pod_spec:
|
|
896
911
|
pod_template = self.full_pod_spec
|
|
897
912
|
else:
|
|
@@ -948,7 +963,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
948
963
|
|
|
949
964
|
if not pod.metadata.name:
|
|
950
965
|
pod.metadata.name = _create_pod_id(
|
|
951
|
-
task_id=self.task_id, unique=self.random_name_suffix, max_length=
|
|
966
|
+
task_id=self.task_id, unique=self.random_name_suffix, max_length=POD_NAME_MAX_LENGTH
|
|
952
967
|
)
|
|
953
968
|
elif self.random_name_suffix:
|
|
954
969
|
# user has supplied pod name, we're just adding suffix
|
|
@@ -41,7 +41,11 @@ from airflow.exceptions import (
|
|
|
41
41
|
AirflowException,
|
|
42
42
|
RemovedInAirflow3Warning,
|
|
43
43
|
)
|
|
44
|
-
from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import
|
|
44
|
+
from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import (
|
|
45
|
+
POD_NAME_MAX_LENGTH,
|
|
46
|
+
add_pod_suffix,
|
|
47
|
+
rand_str,
|
|
48
|
+
)
|
|
45
49
|
from airflow.providers.cncf.kubernetes.pod_generator_deprecated import (
|
|
46
50
|
PodDefaults,
|
|
47
51
|
PodGenerator as PodGeneratorDeprecated,
|
|
@@ -380,11 +384,11 @@ class PodGenerator:
|
|
|
380
384
|
- executor_config
|
|
381
385
|
- dynamic arguments
|
|
382
386
|
"""
|
|
383
|
-
if len(pod_id) >
|
|
387
|
+
if len(pod_id) > POD_NAME_MAX_LENGTH:
|
|
384
388
|
warnings.warn(
|
|
385
|
-
"pod_id supplied is longer than
|
|
389
|
+
f"pod_id supplied is longer than {POD_NAME_MAX_LENGTH} characters; truncating and adding unique suffix."
|
|
386
390
|
)
|
|
387
|
-
pod_id = add_pod_suffix(pod_name=pod_id, max_len=
|
|
391
|
+
pod_id = add_pod_suffix(pod_name=pod_id, max_len=POD_NAME_MAX_LENGTH)
|
|
388
392
|
try:
|
|
389
393
|
image = pod_override_object.spec.containers[0].image # type: ignore
|
|
390
394
|
if not image:
|
|
@@ -18,9 +18,11 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import asyncio
|
|
20
20
|
import datetime
|
|
21
|
+
import traceback
|
|
21
22
|
import warnings
|
|
22
23
|
from asyncio import CancelledError
|
|
23
24
|
from enum import Enum
|
|
25
|
+
from functools import cached_property
|
|
24
26
|
from typing import TYPE_CHECKING, Any, AsyncIterator
|
|
25
27
|
|
|
26
28
|
from airflow.exceptions import AirflowProviderDeprecationWarning
|
|
@@ -115,7 +117,6 @@ class KubernetesPodTrigger(BaseTrigger):
|
|
|
115
117
|
self.on_finish_action = OnFinishAction(on_finish_action)
|
|
116
118
|
self.should_delete_pod = self.on_finish_action == OnFinishAction.DELETE_POD
|
|
117
119
|
|
|
118
|
-
self._hook: AsyncKubernetesHook | None = None
|
|
119
120
|
self._since_time = None
|
|
120
121
|
|
|
121
122
|
def serialize(self) -> tuple[str, dict[str, Any]]:
|
|
@@ -141,11 +142,10 @@ class KubernetesPodTrigger(BaseTrigger):
|
|
|
141
142
|
|
|
142
143
|
async def run(self) -> AsyncIterator[TriggerEvent]: # type: ignore[override]
|
|
143
144
|
"""Get current pod status and yield a TriggerEvent."""
|
|
144
|
-
hook = self._get_async_hook()
|
|
145
145
|
self.log.info("Checking pod %r in namespace %r.", self.pod_name, self.pod_namespace)
|
|
146
146
|
try:
|
|
147
147
|
while True:
|
|
148
|
-
pod = await hook.get_pod(
|
|
148
|
+
pod = await self.hook.get_pod(
|
|
149
149
|
name=self.pod_name,
|
|
150
150
|
namespace=self.pod_namespace,
|
|
151
151
|
)
|
|
@@ -205,13 +205,13 @@ class KubernetesPodTrigger(BaseTrigger):
|
|
|
205
205
|
# That means that task was marked as failed
|
|
206
206
|
if self.get_logs:
|
|
207
207
|
self.log.info("Outputting container logs...")
|
|
208
|
-
await self.
|
|
208
|
+
await self.hook.read_logs(
|
|
209
209
|
name=self.pod_name,
|
|
210
210
|
namespace=self.pod_namespace,
|
|
211
211
|
)
|
|
212
212
|
if self.on_finish_action == OnFinishAction.DELETE_POD:
|
|
213
213
|
self.log.info("Deleting pod...")
|
|
214
|
-
await self.
|
|
214
|
+
await self.hook.delete_pod(
|
|
215
215
|
name=self.pod_name,
|
|
216
216
|
namespace=self.pod_namespace,
|
|
217
217
|
)
|
|
@@ -231,18 +231,22 @@ class KubernetesPodTrigger(BaseTrigger):
|
|
|
231
231
|
"namespace": self.pod_namespace,
|
|
232
232
|
"status": "error",
|
|
233
233
|
"message": str(e),
|
|
234
|
+
"stack_trace": traceback.format_exc(),
|
|
234
235
|
}
|
|
235
236
|
)
|
|
236
237
|
|
|
237
238
|
def _get_async_hook(self) -> AsyncKubernetesHook:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
# TODO: Remove this method when the min version of kubernetes provider is 7.12.0 in Google provider.
|
|
240
|
+
return AsyncKubernetesHook(
|
|
241
|
+
conn_id=self.kubernetes_conn_id,
|
|
242
|
+
in_cluster=self.in_cluster,
|
|
243
|
+
config_file=self.config_file,
|
|
244
|
+
cluster_context=self.cluster_context,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@cached_property
|
|
248
|
+
def hook(self) -> AsyncKubernetesHook:
|
|
249
|
+
return self._get_async_hook()
|
|
246
250
|
|
|
247
251
|
def define_container_state(self, pod: V1Pod) -> ContainerState:
|
|
248
252
|
pod_containers = pod.status.container_statuses
|
|
@@ -37,7 +37,7 @@ from kubernetes.stream import stream as kubernetes_stream
|
|
|
37
37
|
from pendulum import DateTime
|
|
38
38
|
from pendulum.parsing.exceptions import ParserError
|
|
39
39
|
from typing_extensions import Literal
|
|
40
|
-
from urllib3.exceptions import HTTPError
|
|
40
|
+
from urllib3.exceptions import HTTPError, TimeoutError
|
|
41
41
|
|
|
42
42
|
from airflow.exceptions import AirflowException, AirflowProviderDeprecationWarning
|
|
43
43
|
from airflow.providers.cncf.kubernetes.pod_generator import PodDefaults
|
|
@@ -395,7 +395,7 @@ class PodManager(LoggingMixin):
|
|
|
395
395
|
:meta private:
|
|
396
396
|
"""
|
|
397
397
|
|
|
398
|
-
def consume_logs(*, since_time: DateTime | None = None) -> DateTime | None:
|
|
398
|
+
def consume_logs(*, since_time: DateTime | None = None) -> tuple[DateTime | None, Exception | None]:
|
|
399
399
|
"""
|
|
400
400
|
Try to follow container logs until container completes.
|
|
401
401
|
|
|
@@ -404,7 +404,18 @@ class PodManager(LoggingMixin):
|
|
|
404
404
|
|
|
405
405
|
Returns the last timestamp observed in logs.
|
|
406
406
|
"""
|
|
407
|
+
exception = None
|
|
407
408
|
last_captured_timestamp = None
|
|
409
|
+
# We timeout connections after 30 minutes because otherwise they can get
|
|
410
|
+
# stuck forever. The 30 is somewhat arbitrary.
|
|
411
|
+
# As a consequence, a TimeoutError will be raised no more than 30 minutes
|
|
412
|
+
# after starting read.
|
|
413
|
+
connection_timeout = 60 * 30
|
|
414
|
+
# We set a shorter read timeout because that helps reduce *connection* timeouts
|
|
415
|
+
# (since the connection will be restarted periodically). And with read timeout,
|
|
416
|
+
# we don't need to worry about either duplicate messages or losing messages; we
|
|
417
|
+
# can safely resume from a few seconds later
|
|
418
|
+
read_timeout = 60 * 5
|
|
408
419
|
try:
|
|
409
420
|
logs = self.read_pod_logs(
|
|
410
421
|
pod=pod,
|
|
@@ -415,6 +426,7 @@ class PodManager(LoggingMixin):
|
|
|
415
426
|
),
|
|
416
427
|
follow=follow,
|
|
417
428
|
post_termination_timeout=post_termination_timeout,
|
|
429
|
+
_request_timeout=(connection_timeout, read_timeout),
|
|
418
430
|
)
|
|
419
431
|
message_to_log = None
|
|
420
432
|
message_timestamp = None
|
|
@@ -447,29 +459,37 @@ class PodManager(LoggingMixin):
|
|
|
447
459
|
self._progress_callback(line)
|
|
448
460
|
self.log.info("[%s] %s", container_name, message_to_log)
|
|
449
461
|
last_captured_timestamp = message_timestamp
|
|
450
|
-
except
|
|
462
|
+
except TimeoutError as e:
|
|
463
|
+
# in case of timeout, increment return time by 2 seconds to avoid
|
|
464
|
+
# duplicate log entries
|
|
465
|
+
if val := (last_captured_timestamp or since_time):
|
|
466
|
+
return val.add(seconds=2), e
|
|
467
|
+
except HTTPError as e:
|
|
468
|
+
exception = e
|
|
451
469
|
self.log.exception(
|
|
452
470
|
"Reading of logs interrupted for container %r; will retry.",
|
|
453
471
|
container_name,
|
|
454
472
|
)
|
|
455
|
-
return last_captured_timestamp or since_time
|
|
473
|
+
return last_captured_timestamp or since_time, exception
|
|
456
474
|
|
|
457
475
|
# note: `read_pod_logs` follows the logs, so we shouldn't necessarily *need* to
|
|
458
476
|
# loop as we do here. But in a long-running process we might temporarily lose connectivity.
|
|
459
477
|
# So the looping logic is there to let us resume following the logs.
|
|
460
478
|
last_log_time = since_time
|
|
461
479
|
while True:
|
|
462
|
-
last_log_time = consume_logs(since_time=last_log_time)
|
|
480
|
+
last_log_time, exc = consume_logs(since_time=last_log_time)
|
|
463
481
|
if not self.container_is_running(pod, container_name=container_name):
|
|
464
482
|
return PodLoggingStatus(running=False, last_log_time=last_log_time)
|
|
465
483
|
if not follow:
|
|
466
484
|
return PodLoggingStatus(running=True, last_log_time=last_log_time)
|
|
467
485
|
else:
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
486
|
+
# a timeout is a normal thing and we ignore it and resume following logs
|
|
487
|
+
if not isinstance(exc, TimeoutError):
|
|
488
|
+
self.log.warning(
|
|
489
|
+
"Pod %s log read interrupted but container %s still running",
|
|
490
|
+
pod.metadata.name,
|
|
491
|
+
container_name,
|
|
492
|
+
)
|
|
473
493
|
time.sleep(1)
|
|
474
494
|
|
|
475
495
|
def _reconcile_requested_log_containers(
|
|
@@ -610,6 +630,7 @@ class PodManager(LoggingMixin):
|
|
|
610
630
|
since_seconds: int | None = None,
|
|
611
631
|
follow=True,
|
|
612
632
|
post_termination_timeout: int = 120,
|
|
633
|
+
**kwargs,
|
|
613
634
|
) -> PodLogsConsumer:
|
|
614
635
|
"""Read log from the POD."""
|
|
615
636
|
additional_kwargs = {}
|
|
@@ -618,6 +639,7 @@ class PodManager(LoggingMixin):
|
|
|
618
639
|
|
|
619
640
|
if tail_lines:
|
|
620
641
|
additional_kwargs["tail_lines"] = tail_lines
|
|
642
|
+
additional_kwargs.update(**kwargs)
|
|
621
643
|
|
|
622
644
|
try:
|
|
623
645
|
logs = self._client.read_namespaced_pod_log(
|
|
@@ -629,7 +651,7 @@ class PodManager(LoggingMixin):
|
|
|
629
651
|
_preload_content=False,
|
|
630
652
|
**additional_kwargs,
|
|
631
653
|
)
|
|
632
|
-
except
|
|
654
|
+
except HTTPError:
|
|
633
655
|
self.log.exception("There was an error reading the kubernetes API.")
|
|
634
656
|
raise
|
|
635
657
|
|
|
@@ -658,7 +680,7 @@ class PodManager(LoggingMixin):
|
|
|
658
680
|
return self._client.list_namespaced_event(
|
|
659
681
|
namespace=pod.metadata.namespace, field_selector=f"involvedObject.name={pod.metadata.name}"
|
|
660
682
|
)
|
|
661
|
-
except
|
|
683
|
+
except HTTPError as e:
|
|
662
684
|
raise AirflowException(f"There was an error reading the kubernetes API: {e}")
|
|
663
685
|
|
|
664
686
|
@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential(), reraise=True)
|
|
@@ -666,7 +688,7 @@ class PodManager(LoggingMixin):
|
|
|
666
688
|
"""Read POD information."""
|
|
667
689
|
try:
|
|
668
690
|
return self._client.read_namespaced_pod(pod.metadata.name, pod.metadata.namespace)
|
|
669
|
-
except
|
|
691
|
+
except HTTPError as e:
|
|
670
692
|
raise AirflowException(f"There was an error reading the kubernetes API: {e}")
|
|
671
693
|
|
|
672
694
|
def await_xcom_sidecar_container_start(self, pod: V1Pod) -> None:
|
|
@@ -28,7 +28,7 @@ build-backend = "flit_core.buildapi"
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "apache-airflow-providers-cncf-kubernetes"
|
|
31
|
-
version = "7.
|
|
31
|
+
version = "7.12.0"
|
|
32
32
|
description = "Provider package apache-airflow-providers-cncf-kubernetes for Apache Airflow"
|
|
33
33
|
readme = "README.rst"
|
|
34
34
|
authors = [
|
|
@@ -65,8 +65,8 @@ dependencies = [
|
|
|
65
65
|
]
|
|
66
66
|
|
|
67
67
|
[project.urls]
|
|
68
|
-
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
69
|
-
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.
|
|
68
|
+
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0"
|
|
69
|
+
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/7.12.0/changelog.html"
|
|
70
70
|
"Bug Tracker" = "https://github.com/apache/airflow/issues"
|
|
71
71
|
"Source Code" = "https://github.com/apache/airflow"
|
|
72
72
|
"Slack Chat" = "https://s.apache.org/airflow-slack"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|