apache-airflow-providers-cncf-kubernetes 3.1.0__py3-none-any.whl → 10.10.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.
Files changed (72) hide show
  1. airflow/providers/cncf/kubernetes/__init__.py +18 -23
  2. airflow/providers/cncf/kubernetes/backcompat/__init__.py +17 -0
  3. airflow/providers/cncf/kubernetes/backcompat/backwards_compat_converters.py +31 -49
  4. airflow/providers/cncf/kubernetes/callbacks.py +200 -0
  5. airflow/providers/cncf/kubernetes/cli/__init__.py +16 -0
  6. airflow/providers/cncf/kubernetes/cli/kubernetes_command.py +195 -0
  7. airflow/providers/cncf/kubernetes/decorators/kubernetes.py +163 -0
  8. airflow/providers/cncf/kubernetes/decorators/kubernetes_cmd.py +118 -0
  9. airflow/providers/cncf/kubernetes/exceptions.py +37 -0
  10. airflow/providers/cncf/kubernetes/executors/__init__.py +17 -0
  11. airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py +831 -0
  12. airflow/providers/cncf/kubernetes/executors/kubernetes_executor_types.py +91 -0
  13. airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py +736 -0
  14. airflow/providers/cncf/kubernetes/executors/local_kubernetes_executor.py +306 -0
  15. airflow/providers/cncf/kubernetes/get_provider_info.py +249 -50
  16. airflow/providers/cncf/kubernetes/hooks/kubernetes.py +846 -112
  17. airflow/providers/cncf/kubernetes/k8s_model.py +62 -0
  18. airflow/providers/cncf/kubernetes/kube_client.py +156 -0
  19. airflow/providers/cncf/kubernetes/kube_config.py +125 -0
  20. airflow/providers/cncf/kubernetes/kubernetes_executor_templates/__init__.py +16 -0
  21. airflow/providers/cncf/kubernetes/kubernetes_executor_templates/basic_template.yaml +79 -0
  22. airflow/providers/cncf/kubernetes/kubernetes_helper_functions.py +165 -0
  23. airflow/providers/cncf/kubernetes/operators/custom_object_launcher.py +368 -0
  24. airflow/providers/cncf/kubernetes/operators/job.py +646 -0
  25. airflow/providers/cncf/kubernetes/operators/kueue.py +132 -0
  26. airflow/providers/cncf/kubernetes/operators/pod.py +1417 -0
  27. airflow/providers/cncf/kubernetes/operators/resource.py +191 -0
  28. airflow/providers/cncf/kubernetes/operators/spark_kubernetes.py +336 -35
  29. airflow/providers/cncf/kubernetes/pod_generator.py +592 -0
  30. airflow/providers/cncf/kubernetes/pod_template_file_examples/__init__.py +16 -0
  31. airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_image_template.yaml +68 -0
  32. airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_volume_template.yaml +74 -0
  33. airflow/providers/cncf/kubernetes/pod_template_file_examples/git_sync_template.yaml +95 -0
  34. airflow/providers/cncf/kubernetes/python_kubernetes_script.jinja2 +51 -0
  35. airflow/providers/cncf/kubernetes/python_kubernetes_script.py +92 -0
  36. airflow/providers/cncf/kubernetes/resource_convert/__init__.py +16 -0
  37. airflow/providers/cncf/kubernetes/resource_convert/configmap.py +52 -0
  38. airflow/providers/cncf/kubernetes/resource_convert/env_variable.py +39 -0
  39. airflow/providers/cncf/kubernetes/resource_convert/secret.py +40 -0
  40. airflow/providers/cncf/kubernetes/secret.py +128 -0
  41. airflow/providers/cncf/kubernetes/sensors/spark_kubernetes.py +30 -14
  42. airflow/providers/cncf/kubernetes/template_rendering.py +81 -0
  43. airflow/providers/cncf/kubernetes/triggers/__init__.py +16 -0
  44. airflow/providers/cncf/kubernetes/triggers/job.py +176 -0
  45. airflow/providers/cncf/kubernetes/triggers/pod.py +344 -0
  46. airflow/providers/cncf/kubernetes/utils/__init__.py +3 -0
  47. airflow/providers/cncf/kubernetes/utils/container.py +118 -0
  48. airflow/providers/cncf/kubernetes/utils/delete_from.py +154 -0
  49. airflow/providers/cncf/kubernetes/utils/k8s_resource_iterator.py +46 -0
  50. airflow/providers/cncf/kubernetes/utils/pod_manager.py +887 -152
  51. airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py +25 -16
  52. airflow/providers/cncf/kubernetes/version_compat.py +38 -0
  53. apache_airflow_providers_cncf_kubernetes-10.10.0rc1.dist-info/METADATA +125 -0
  54. apache_airflow_providers_cncf_kubernetes-10.10.0rc1.dist-info/RECORD +62 -0
  55. {apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info → apache_airflow_providers_cncf_kubernetes-10.10.0rc1.dist-info}/WHEEL +1 -2
  56. apache_airflow_providers_cncf_kubernetes-10.10.0rc1.dist-info/entry_points.txt +3 -0
  57. apache_airflow_providers_cncf_kubernetes-10.10.0rc1.dist-info/licenses/NOTICE +5 -0
  58. airflow/providers/cncf/kubernetes/backcompat/pod.py +0 -119
  59. airflow/providers/cncf/kubernetes/backcompat/pod_runtime_info_env.py +0 -56
  60. airflow/providers/cncf/kubernetes/backcompat/volume.py +0 -62
  61. airflow/providers/cncf/kubernetes/backcompat/volume_mount.py +0 -58
  62. airflow/providers/cncf/kubernetes/example_dags/example_kubernetes.py +0 -163
  63. airflow/providers/cncf/kubernetes/example_dags/example_spark_kubernetes.py +0 -66
  64. airflow/providers/cncf/kubernetes/example_dags/example_spark_kubernetes_spark_pi.yaml +0 -57
  65. airflow/providers/cncf/kubernetes/operators/kubernetes_pod.py +0 -622
  66. apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info/METADATA +0 -452
  67. apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info/NOTICE +0 -6
  68. apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info/RECORD +0 -29
  69. apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info/entry_points.txt +0 -3
  70. apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info/top_level.txt +0 -1
  71. /airflow/providers/cncf/kubernetes/{example_dags → decorators}/__init__.py +0 -0
  72. {apache_airflow_providers_cncf_kubernetes-3.1.0.dist-info → apache_airflow_providers_cncf_kubernetes-10.10.0rc1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,62 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ """Classes for interacting with Kubernetes API."""
18
+
19
+ from __future__ import annotations
20
+
21
+ from abc import ABC, abstractmethod
22
+ from functools import reduce
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from kubernetes.client import models as k8s
27
+
28
+
29
+ class K8SModel(ABC):
30
+ """
31
+ Airflow Kubernetes models are here for backwards compatibility reasons only.
32
+
33
+ Ideally clients should use the kubernetes API
34
+ and the process of
35
+
36
+ client input -> Airflow k8s models -> k8s models
37
+
38
+ can be avoided. All of these models implement the
39
+ `attach_to_pod` method so that they integrate with the kubernetes client.
40
+ """
41
+
42
+ @abstractmethod
43
+ def attach_to_pod(self, pod: k8s.V1Pod) -> k8s.V1Pod:
44
+ """
45
+ Attaches to pod.
46
+
47
+ :param pod: A pod to attach this Kubernetes object to
48
+ :return: The pod with the object attached
49
+ """
50
+
51
+
52
+ def append_to_pod(pod: k8s.V1Pod, k8s_objects: list[K8SModel] | None):
53
+ """
54
+ Attach additional specs to an existing pod object.
55
+
56
+ :param pod: A pod to attach a list of Kubernetes objects to
57
+ :param k8s_objects: a potential None list of K8SModels
58
+ :return: pod with the objects attached if they exist
59
+ """
60
+ if not k8s_objects:
61
+ return pod
62
+ return reduce(lambda p, o: o.attach_to_pod(p), k8s_objects, pod)
@@ -0,0 +1,156 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ """Client for kubernetes communication."""
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+
23
+ import urllib3.util
24
+
25
+ from airflow.configuration import conf
26
+
27
+ log = logging.getLogger(__name__)
28
+
29
+ try:
30
+ from kubernetes import client, config
31
+ from kubernetes.client import Configuration
32
+ from kubernetes.client.rest import ApiException
33
+
34
+ has_kubernetes = True
35
+
36
+ def _get_default_configuration() -> Configuration:
37
+ if hasattr(Configuration, "get_default_copy"):
38
+ return Configuration.get_default_copy()
39
+ return Configuration()
40
+
41
+ def _disable_verify_ssl() -> None:
42
+ configuration = _get_default_configuration()
43
+ configuration.verify_ssl = False
44
+ Configuration.set_default(configuration)
45
+
46
+ except ImportError as e:
47
+ # We need an exception class to be able to use it in ``except`` elsewhere
48
+ # in the code base
49
+ ApiException = BaseException
50
+ has_kubernetes = False
51
+ _import_err = e
52
+
53
+
54
+ def _enable_tcp_keepalive() -> None:
55
+ """
56
+ Enable TCP keepalive mechanism.
57
+
58
+ This prevents urllib3 connection to hang indefinitely when idle connection
59
+ is time-outed on services like cloud load balancers or firewalls.
60
+
61
+ See https://github.com/apache/airflow/pull/11406 for detailed explanation.
62
+
63
+ Please ping @michalmisiewicz or @dimberman in the PR if you want to modify this function.
64
+ """
65
+ import socket
66
+
67
+ from urllib3.connection import HTTPConnection, HTTPSConnection
68
+
69
+ tcp_keep_idle = conf.getint("kubernetes_executor", "tcp_keep_idle")
70
+ tcp_keep_intvl = conf.getint("kubernetes_executor", "tcp_keep_intvl")
71
+ tcp_keep_cnt = conf.getint("kubernetes_executor", "tcp_keep_cnt")
72
+
73
+ socket_options = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]
74
+
75
+ if hasattr(socket, "TCP_KEEPIDLE"):
76
+ socket_options.append((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, tcp_keep_idle))
77
+ else:
78
+ log.debug("Unable to set TCP_KEEPIDLE on this platform")
79
+
80
+ if hasattr(socket, "TCP_KEEPINTVL"):
81
+ socket_options.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, tcp_keep_intvl))
82
+ else:
83
+ log.debug("Unable to set TCP_KEEPINTVL on this platform")
84
+
85
+ if hasattr(socket, "TCP_KEEPCNT"):
86
+ socket_options.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, tcp_keep_cnt))
87
+ else:
88
+ log.debug("Unable to set TCP_KEEPCNT on this platform")
89
+
90
+ # Cast both the default options and our socket options
91
+ socket_options_cast: list[tuple[int, int, int | bytes]] = [
92
+ (level, opt, val) for level, opt, val in socket_options
93
+ ]
94
+ default_options_cast: list[tuple[int, int, int | bytes]] = [
95
+ (level, opt, val) for level, opt, val in HTTPSConnection.default_socket_options
96
+ ]
97
+
98
+ # Then use the cast versions for both HTTPS and HTTP
99
+ HTTPSConnection.default_socket_options = default_options_cast + socket_options_cast
100
+ HTTPConnection.default_socket_options = default_options_cast + socket_options_cast
101
+
102
+
103
+ def get_kube_client(
104
+ in_cluster: bool | None = None,
105
+ cluster_context: str | None = None,
106
+ config_file: str | None = None,
107
+ ) -> client.CoreV1Api:
108
+ """
109
+ Retrieve Kubernetes client.
110
+
111
+ :param in_cluster: whether we are in cluster
112
+ :param cluster_context: context of the cluster
113
+ :param config_file: configuration file
114
+ :return: kubernetes client
115
+ """
116
+ if in_cluster is None:
117
+ in_cluster = conf.getboolean("kubernetes_executor", "in_cluster")
118
+ if not has_kubernetes:
119
+ raise _import_err
120
+
121
+ if conf.getboolean("kubernetes_executor", "enable_tcp_keepalive"):
122
+ _enable_tcp_keepalive()
123
+
124
+ configuration = _get_default_configuration()
125
+ api_client_retry_configuration = conf.getjson(
126
+ "kubernetes_executor", "api_client_retry_configuration", fallback={}
127
+ )
128
+
129
+ if not conf.getboolean("kubernetes_executor", "verify_ssl"):
130
+ _disable_verify_ssl()
131
+
132
+ if isinstance(api_client_retry_configuration, dict):
133
+ configuration.retries = urllib3.util.Retry(**api_client_retry_configuration)
134
+ else:
135
+ raise ValueError("api_client_retry_configuration should be a dictionary")
136
+
137
+ if in_cluster:
138
+ config.load_incluster_config(client_configuration=configuration)
139
+ else:
140
+ if cluster_context is None:
141
+ cluster_context = conf.get("kubernetes_executor", "cluster_context", fallback=None)
142
+ if config_file is None:
143
+ config_file = conf.get("kubernetes_executor", "config_file", fallback=None)
144
+ config.load_kube_config(
145
+ config_file=config_file, context=cluster_context, client_configuration=configuration
146
+ )
147
+
148
+ if not conf.getboolean("kubernetes_executor", "verify_ssl"):
149
+ configuration.verify_ssl = False
150
+
151
+ ssl_ca_cert = conf.get("kubernetes_executor", "ssl_ca_cert")
152
+ if ssl_ca_cert:
153
+ configuration.ssl_ca_cert = ssl_ca_cert
154
+
155
+ api_client = client.ApiClient(configuration=configuration)
156
+ return client.CoreV1Api(api_client)
@@ -0,0 +1,125 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ from __future__ import annotations
18
+
19
+ import warnings
20
+
21
+ from airflow.configuration import conf
22
+ from airflow.exceptions import AirflowConfigException, AirflowProviderDeprecationWarning
23
+ from airflow.settings import AIRFLOW_HOME
24
+
25
+
26
+ class KubeConfig:
27
+ """Configuration for Kubernetes."""
28
+
29
+ core_section = "core"
30
+ kubernetes_section = "kubernetes_executor"
31
+ logging_section = "logging"
32
+
33
+ def __init__(self):
34
+ configuration_dict = conf.as_dict(display_sensitive=True)
35
+ self.core_configuration = configuration_dict[self.core_section]
36
+ self.airflow_home = AIRFLOW_HOME
37
+ self.dags_folder = conf.get(self.core_section, "dags_folder")
38
+ self.parallelism = conf.getint(self.core_section, "parallelism")
39
+ self.pod_template_file = conf.get(self.kubernetes_section, "pod_template_file", fallback=None)
40
+
41
+ self.delete_worker_pods = conf.getboolean(self.kubernetes_section, "delete_worker_pods")
42
+ self.delete_worker_pods_on_failure = conf.getboolean(
43
+ self.kubernetes_section, "delete_worker_pods_on_failure"
44
+ )
45
+ self.worker_pod_pending_fatal_container_state_reasons = []
46
+ if conf.get(self.kubernetes_section, "worker_pod_pending_fatal_container_state_reasons", fallback=""):
47
+ self.worker_pod_pending_fatal_container_state_reasons = [
48
+ r.strip()
49
+ for r in conf.get(
50
+ self.kubernetes_section, "worker_pod_pending_fatal_container_state_reasons"
51
+ ).split(",")
52
+ ]
53
+
54
+ self.worker_pods_creation_batch_size = conf.getint(
55
+ self.kubernetes_section, "worker_pods_creation_batch_size"
56
+ )
57
+ self.worker_container_repository = conf.get(self.kubernetes_section, "worker_container_repository")
58
+ if self.worker_container_repository:
59
+ warnings.warn(
60
+ "Configuration 'worker_container_repository' is deprecated. "
61
+ "Use 'pod_template_file' to specify the container image repository instead.",
62
+ AirflowProviderDeprecationWarning,
63
+ stacklevel=2,
64
+ )
65
+ self.worker_container_tag = conf.get(self.kubernetes_section, "worker_container_tag")
66
+ if self.worker_container_tag:
67
+ warnings.warn(
68
+ "Configuration 'worker_container_tag' is deprecated. "
69
+ "Use 'pod_template_file' to specify the container image tag instead.",
70
+ AirflowProviderDeprecationWarning,
71
+ stacklevel=2,
72
+ )
73
+ if self.worker_container_repository and self.worker_container_tag:
74
+ self.kube_image = f"{self.worker_container_repository}:{self.worker_container_tag}"
75
+ else:
76
+ self.kube_image = None
77
+
78
+ # The Kubernetes Namespace in which the Scheduler and Webserver reside. Note
79
+ # that if your
80
+ # cluster has RBAC enabled, your scheduler may need service account permissions to
81
+ # create, watch, get, and delete pods in this namespace.
82
+ self.kube_namespace = conf.get(self.kubernetes_section, "namespace")
83
+ if self.kube_namespace and self.kube_namespace != "default":
84
+ warnings.warn(
85
+ "Configuration 'namespace' is deprecated. "
86
+ "Use 'pod_template_file' to specify the namespace instead.",
87
+ AirflowProviderDeprecationWarning,
88
+ stacklevel=2,
89
+ )
90
+ self.multi_namespace_mode = conf.getboolean(self.kubernetes_section, "multi_namespace_mode")
91
+ if self.multi_namespace_mode and conf.get(
92
+ self.kubernetes_section, "multi_namespace_mode_namespace_list"
93
+ ):
94
+ self.multi_namespace_mode_namespace_list = conf.get(
95
+ self.kubernetes_section, "multi_namespace_mode_namespace_list"
96
+ ).split(",")
97
+ else:
98
+ self.multi_namespace_mode_namespace_list = None
99
+ # The Kubernetes Namespace in which pods will be created by the executor. Note
100
+ # that if your
101
+ # cluster has RBAC enabled, your workers may need service account permissions to
102
+ # interact with cluster components.
103
+ self.executor_namespace = conf.get(self.kubernetes_section, "namespace")
104
+
105
+ self.kube_client_request_args = conf.getjson(
106
+ self.kubernetes_section, "kube_client_request_args", fallback={}
107
+ )
108
+ if not isinstance(self.kube_client_request_args, dict):
109
+ raise AirflowConfigException(
110
+ f"[{self.kubernetes_section}] 'kube_client_request_args' expected a JSON dict, got "
111
+ + type(self.kube_client_request_args).__name__
112
+ )
113
+ if self.kube_client_request_args:
114
+ if "_request_timeout" in self.kube_client_request_args and isinstance(
115
+ self.kube_client_request_args["_request_timeout"], list
116
+ ):
117
+ self.kube_client_request_args["_request_timeout"] = tuple(
118
+ self.kube_client_request_args["_request_timeout"]
119
+ )
120
+ self.delete_option_kwargs = conf.getjson(self.kubernetes_section, "delete_option_kwargs", fallback={})
121
+ if not isinstance(self.delete_option_kwargs, dict):
122
+ raise AirflowConfigException(
123
+ f"[{self.kubernetes_section}] 'delete_option_kwargs' expected a JSON dict, got "
124
+ + type(self.delete_option_kwargs).__name__
125
+ )
@@ -0,0 +1,16 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
@@ -0,0 +1,79 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ ---
18
+ kind: Pod
19
+ apiVersion: v1
20
+ metadata:
21
+ name: placeholder-name-dont-delete
22
+ namespace: placeholder-name-dont-delete
23
+ labels:
24
+ mylabel: foo
25
+ spec:
26
+ containers:
27
+ - name: base
28
+ image: placeholder-name-dont-delete
29
+ env:
30
+ - name: AIRFLOW__CORE__EXECUTOR
31
+ value: LocalExecutor
32
+ - name: AIRFLOW_HOME
33
+ value: /opt/airflow
34
+ - name: AIRFLOW__CORE__DAGS_FOLDER
35
+ value: /opt/airflow/dags
36
+ - name: AIRFLOW__CORE__FERNET_KEY
37
+ valueFrom:
38
+ secretKeyRef:
39
+ name: airflow-fernet-key
40
+ key: fernet-key
41
+ - name: AIRFLOW__DATABASE__SQL_ALCHEMY_CONN
42
+ valueFrom:
43
+ secretKeyRef:
44
+ name: airflow-airflow-metadata
45
+ key: connection
46
+ - name: foo
47
+ value: bar
48
+ resources: {}
49
+ volumeMounts:
50
+ - name: airflow-logs
51
+ mountPath: /opt/airflow/logs
52
+ - name: airflow-config
53
+ readOnly: true
54
+ mountPath: /opt/airflow/airflow.cfg
55
+ subPath: airflow.cfg
56
+ - name: airflow-config
57
+ readOnly: true
58
+ mountPath: /opt/airflow/config/airflow_local_settings.py
59
+ subPath: airflow_local_settings.py
60
+ terminationMessagePath: /dev/termination-log
61
+ terminationMessagePolicy: File
62
+ imagePullPolicy: IfNotPresent
63
+ volumes:
64
+ - name: airflow-logs
65
+ emptyDir: {}
66
+ - name: airflow-config
67
+ configMap:
68
+ name: airflow-airflow-config
69
+ defaultMode: 420
70
+ restartPolicy: Never
71
+ terminationGracePeriodSeconds: 30
72
+ serviceAccountName: airflow-worker
73
+ serviceAccount: airflow-worker
74
+ securityContext:
75
+ runAsUser: 50000
76
+ fsGroup: 50000
77
+ imagePullSecrets:
78
+ - name: airflow-registry
79
+ schedulerName: default-scheduler
@@ -0,0 +1,165 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ import secrets
21
+ import string
22
+ from functools import cache
23
+ from typing import TYPE_CHECKING
24
+
25
+ import pendulum
26
+ from kubernetes.client.rest import ApiException
27
+ from slugify import slugify
28
+
29
+ from airflow.configuration import conf
30
+ from airflow.providers.cncf.kubernetes.backcompat import get_logical_date_key
31
+
32
+ if TYPE_CHECKING:
33
+ from airflow.models.taskinstancekey import TaskInstanceKey
34
+
35
+ log = logging.getLogger(__name__)
36
+
37
+ alphanum_lower = string.ascii_lowercase + string.digits
38
+
39
+ POD_NAME_MAX_LENGTH = 63 # Matches Linux kernel's HOST_NAME_MAX default value minus 1.
40
+
41
+
42
+ def rand_str(num):
43
+ """
44
+ Generate random lowercase alphanumeric string of length num.
45
+
46
+ :meta private:
47
+ """
48
+ return "".join(secrets.choice(alphanum_lower) for _ in range(num))
49
+
50
+
51
+ def add_unique_suffix(*, name: str, rand_len: int = 8, max_len: int = POD_NAME_MAX_LENGTH) -> str:
52
+ """
53
+ Add random string to pod or job name while staying under max length.
54
+
55
+ :param name: name of the pod or job
56
+ :param rand_len: length of the random string to append
57
+ :param max_len: maximum length of the pod name
58
+ :meta private:
59
+ """
60
+ suffix = "-" + rand_str(rand_len)
61
+ return name[: max_len - len(suffix)].strip("-.") + suffix
62
+
63
+
64
+ def create_unique_id(
65
+ dag_id: str | None = None,
66
+ task_id: str | None = None,
67
+ *,
68
+ max_length: int = POD_NAME_MAX_LENGTH,
69
+ unique: bool = True,
70
+ ) -> str:
71
+ """
72
+ Generate unique pod or job ID given a dag_id and / or task_id.
73
+
74
+ :param dag_id: DAG ID
75
+ :param task_id: Task ID
76
+ :param max_length: max number of characters
77
+ :param unique: whether a random string suffix should be added
78
+ :return: A valid identifier for a kubernetes pod name
79
+ """
80
+ if not (dag_id or task_id):
81
+ raise ValueError("Must supply either dag_id or task_id.")
82
+ name = ""
83
+ if dag_id:
84
+ name += dag_id
85
+ if task_id:
86
+ if name:
87
+ name += "-"
88
+ name += task_id
89
+ base_name = slugify(name, lowercase=True)[:max_length].strip(".-")
90
+ if unique:
91
+ return add_unique_suffix(name=base_name, rand_len=8, max_len=max_length)
92
+ return base_name
93
+
94
+
95
+ def annotations_to_key(annotations: dict[str, str]) -> TaskInstanceKey:
96
+ """Build a TaskInstanceKey based on pod annotations."""
97
+ log.debug("Creating task key for annotations %s", annotations)
98
+ dag_id = annotations["dag_id"]
99
+ task_id = annotations["task_id"]
100
+ try_number = int(annotations["try_number"])
101
+ annotation_run_id = annotations.get("run_id")
102
+ map_index = int(annotations.get("map_index", -1))
103
+
104
+ # Compat: Look up the run_id from the TI table!
105
+ from airflow.models.dagrun import DagRun
106
+ from airflow.models.taskinstance import TaskInstance, TaskInstanceKey
107
+ from airflow.settings import Session
108
+
109
+ logical_date_key = get_logical_date_key()
110
+
111
+ if not annotation_run_id and logical_date_key in annotations:
112
+ logical_date = pendulum.parse(annotations[logical_date_key])
113
+ # Do _not_ use create-session, we don't want to expunge
114
+ if Session is None:
115
+ raise RuntimeError("Session not configured. Call configure_orm() first.")
116
+ session = Session()
117
+
118
+ task_instance_run_id = (
119
+ session.query(TaskInstance.run_id)
120
+ .join(TaskInstance.dag_run)
121
+ .filter(
122
+ TaskInstance.dag_id == dag_id,
123
+ TaskInstance.task_id == task_id,
124
+ getattr(DagRun, logical_date_key) == logical_date,
125
+ )
126
+ .scalar()
127
+ )
128
+ else:
129
+ task_instance_run_id = annotation_run_id
130
+
131
+ return TaskInstanceKey(
132
+ dag_id=dag_id,
133
+ task_id=task_id,
134
+ run_id=task_instance_run_id,
135
+ try_number=try_number,
136
+ map_index=map_index,
137
+ )
138
+
139
+
140
+ @cache
141
+ def get_logs_task_metadata() -> bool:
142
+ return conf.getboolean("kubernetes_executor", "logs_task_metadata")
143
+
144
+
145
+ def annotations_for_logging_task_metadata(annotation_set):
146
+ if get_logs_task_metadata():
147
+ annotations_for_logging = annotation_set
148
+ else:
149
+ annotations_for_logging = "<omitted>"
150
+ return annotations_for_logging
151
+
152
+
153
+ def should_retry_creation(exception: BaseException) -> bool:
154
+ """
155
+ Check if an Exception indicates a transient error and warrants retrying.
156
+
157
+ This function is needed for preventing 'No agent available' error. The error appears time to time
158
+ when users try to create a Resource or Job. This issue is inside kubernetes and in the current moment
159
+ has no solution. Like a temporary solution we decided to retry Job or Resource creation request each
160
+ time when this error appears.
161
+ More about this issue here: https://github.com/cert-manager/cert-manager/issues/6457
162
+ """
163
+ if isinstance(exception, ApiException):
164
+ return str(exception.status) == "500"
165
+ return False