7
7
ThreadedKubernetesResourcesWatcher ,
8
8
)
9
9
from airflow_kubernetes_job_operator .event_handler import EventHandler
10
- from airflow_kubernetes_job_operator .utils import randomString , get_yaml_path_value
10
+ from airflow_kubernetes_job_operator .utils import (
11
+ randomString ,
12
+ get_yaml_path_value ,
13
+ )
11
14
12
15
JOB_RUNNER_INSTANCE_ID_LABEL = "job-runner-instance-id"
13
- KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH = (
14
- "/var/run/secrets/kubernetes.io/serviceaccount"
15
- )
16
+ KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH = "/var/run/secrets/kubernetes.io/serviceaccount"
16
17
17
18
18
19
class JobRunner (EventHandler ):
@@ -31,10 +32,11 @@ def __init__(self):
31
32
print(info.status)
32
33
print(info.yaml)
33
34
"""
35
+ self ._loaded_config_file = None
34
36
super ().__init__ ()
35
37
36
38
def load_kuberntes_configuration (
37
- self , in_cluster : bool = None , config_file : str = None , context : str = None
39
+ self , in_cluster : bool = None , config_file : str = None , context : str = None ,
38
40
):
39
41
"""Loads the appropriate kubernetes configuration into the global
40
42
context.
@@ -48,9 +50,7 @@ def load_kuberntes_configuration(
48
50
context {str} -- The context to load. If None loads the current
49
51
context (default: {None})
50
52
"""
51
- in_cluster = (
52
- in_cluster or os .environ .get ("KUBERNETES_SERVICE_HOST" , None ) is not None
53
- )
53
+ in_cluster = in_cluster or os .environ .get ("KUBERNETES_SERVICE_HOST" , None ) is not None
54
54
55
55
# loading the current config to use.
56
56
if in_cluster :
@@ -61,39 +61,42 @@ def load_kuberntes_configuration(
61
61
config_file
62
62
), f"Cannot find kubernetes configuration file @ { config_file } "
63
63
64
+ # NOTE: When loading from a config file there is no way
65
+ # (as of the current kubernetes package) to retrieve the loaded
66
+ # configuration. As such, kubernetes.config.list_kube_config_contexts
67
+ # will not provide the last loaded configuration and will revert
68
+ # to the default config. Therefore if a config file is loaded
69
+ # into the runner it will be used to retrieve the default namespace
70
+ # (or other locations)
71
+ self ._loaded_config_file = config_file
72
+
64
73
kubernetes .config .load_kube_config (config_file = config_file , context = context )
65
74
66
- @staticmethod
67
- def get_current_namespace ():
75
+ def get_current_namespace (self ):
68
76
"""Returns the current namespace.
69
77
Returns:
70
78
str
71
79
"""
72
80
namespace = ""
73
81
try :
74
- in_cluster_namespace_fpath = os .path .join (
75
- KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH , "namespace"
76
- )
82
+ in_cluster_namespace_fpath = os .path .join (KUBERNETES_IN_CLUSTER_SERVICE_ACCOUNT_PATH , "namespace" )
77
83
if os .path .exists (in_cluster_namespace_fpath ):
78
84
with open (in_cluster_namespace_fpath , "r" , encoding = "utf-8" ) as nsfile :
79
85
namespace = nsfile .read ()
80
86
else :
81
- contexts , active_context = kubernetes .config .list_kube_config_contexts ()
87
+ (contexts , active_context ,) = kubernetes .config .list_kube_config_contexts (
88
+ config_file = self ._loaded_config_file
89
+ )
82
90
namespace = (
83
- active_context ["context" ]["namespace" ]
84
- if "namespace" in active_context ["context" ]
85
- else "default"
91
+ active_context ["context" ]["namespace" ] if "namespace" in active_context ["context" ] else "default"
86
92
)
87
93
except Exception as e :
88
94
raise Exception (
89
- "Could not resolve current namespace, you must provide a namespace or a context file" ,
90
- e ,
95
+ "Could not resolve current namespace, you must provide a namespace or a context file" , e ,
91
96
)
92
97
return namespace
93
98
94
- def prepare_job_yaml (
95
- self , job_yaml , random_name_postfix_length : int = 0 , force_job_name : str = None
96
- ) -> dict :
99
+ def prepare_job_yaml (self , job_yaml , random_name_postfix_length : int = 0 , force_job_name : str = None ,) -> dict :
97
100
"""Pre-prepare the job yaml dictionary for execution,
98
101
can also accept a string input.
99
102
@@ -133,11 +136,7 @@ def prepare_job_yaml(
133
136
return
134
137
135
138
# make sure the yaml is an dict.
136
- job_yaml = (
137
- copy .deepcopy (job_yaml )
138
- if isinstance (job_yaml , dict )
139
- else yaml .safe_load (job_yaml )
140
- )
139
+ job_yaml = copy .deepcopy (job_yaml ) if isinstance (job_yaml , dict ) else yaml .safe_load (job_yaml )
141
140
142
141
def get (path_names , default = None ):
143
142
try :
@@ -149,13 +148,10 @@ def get(path_names, default=None):
149
148
150
149
def assert_defined (path_names : list , def_name = None ):
151
150
path_string = "." .join (map (lambda v : str (v ), path_names ))
152
- assert (
153
- get (path_names ) is not None
154
- ), f"job { def_name or path_names [- 1 ]} must be defined @ { path_string } "
151
+ assert get (path_names ) is not None , f"job { def_name or path_names [- 1 ]} must be defined @ { path_string } "
155
152
156
- assert get (["kind" ]) == "Job" , (
157
- "job_yaml resource must be of 'kind' 'Job', recived "
158
- + get (["kind" ], "[unknown]" )
153
+ assert get (["kind" ]) == "Job" , "job_yaml resource must be of 'kind' 'Job', recived " + get (
154
+ ["kind" ], "[unknown]"
159
155
)
160
156
161
157
assert_defined (["metadata" , "name" ])
@@ -166,18 +162,15 @@ def assert_defined(path_names: list, def_name=None):
166
162
job_yaml ["metadata" ]["name" ] = force_job_name
167
163
168
164
if random_name_postfix_length > 0 :
169
- job_yaml ["metadata" ]["name" ] += "-" + randomString (
170
- random_name_postfix_length
171
- )
165
+ job_yaml ["metadata" ]["name" ] += "-" + randomString (random_name_postfix_length )
172
166
173
167
# assign current namespace if one is not defined.
174
168
if "namespace" not in job_yaml ["metadata" ]:
175
169
try :
176
170
job_yaml ["metadata" ]["namespace" ] = self .get_current_namespace ()
177
171
except Exception as ex :
178
172
raise Exception (
179
- "Namespace was not provided in yaml and auto namespace resolution failed." ,
180
- ex ,
173
+ "Namespace was not provided in yaml and auto namespace resolution failed." , ex ,
181
174
)
182
175
183
176
# FIXME: Should be a better way to add missing values.
@@ -204,9 +197,7 @@ def assert_defined(path_names: list, def_name=None):
204
197
205
198
instance_id = randomString (15 )
206
199
job_yaml ["metadata" ]["labels" ][JOB_RUNNER_INSTANCE_ID_LABEL ] = instance_id
207
- job_yaml ["spec" ]["template" ]["metadata" ]["labels" ][
208
- JOB_RUNNER_INSTANCE_ID_LABEL
209
- ] = instance_id
200
+ job_yaml ["spec" ]["template" ]["metadata" ]["labels" ][JOB_RUNNER_INSTANCE_ID_LABEL ] = instance_id
210
201
211
202
return job_yaml
212
203
@@ -239,10 +230,7 @@ def execute_job(
239
230
"metadata" in job_yaml
240
231
and "labels" in job_yaml ["metadata" ]
241
232
and JOB_RUNNER_INSTANCE_ID_LABEL in job_yaml ["metadata" ]["labels" ]
242
- ), (
243
- "job_yaml is not configured correctly, "
244
- + "did you forget to call JobRunner.prepare_job_yaml?"
245
- )
233
+ ), ("job_yaml is not configured correctly, " + "did you forget to call JobRunner.prepare_job_yaml?" )
246
234
247
235
metadata = job_yaml ["metadata" ]
248
236
name = metadata ["name" ]
@@ -263,34 +251,26 @@ def execute_job(
263
251
pass
264
252
265
253
if status is not None :
266
- raise Exception (
267
- f"Job { name } already exists in namespace { namespace } , cannot exec."
268
- )
254
+ raise Exception (f"Job { name } already exists in namespace { namespace } , cannot exec." )
269
255
270
256
# starting the watcher.
271
257
watcher = ThreadedKubernetesNamespaceResourcesWatcher (coreClient )
272
258
watcher .auto_watch_pod_logs = read_logs
273
259
watcher .remove_deleted_kube_resources_from_memory = False
274
260
watcher .pipe (self )
275
261
watcher .watch_namespace (
276
- namespace ,
277
- label_selector = f"{ JOB_RUNNER_INSTANCE_ID_LABEL } ={ instance_id } " ,
278
- watch_for_kinds = ["Job" , "Pod" ],
262
+ namespace , label_selector = f"{ JOB_RUNNER_INSTANCE_ID_LABEL } ={ instance_id } " , watch_for_kinds = ["Job" , "Pod" ],
279
263
)
280
264
281
265
# starting the job
282
266
batchClient .create_namespaced_job (namespace , job_yaml )
283
267
284
268
# wait for job to start
285
- job_watcher = watcher .waitfor_status (
286
- "Job" , name , namespace , status = "Running" , timeout = start_timeout
287
- )
269
+ job_watcher = watcher .waitfor_status ("Job" , name , namespace , status = "Running" , timeout = start_timeout )
288
270
self .emit ("job_started" , job_watcher , self )
289
271
290
272
# waiting for the job to completed.
291
- job_watcher = watcher .waitfor_status (
292
- "Job" , name , namespace , status_list = ["Failed" , "Succeeded" , "Deleted" ]
293
- )
273
+ job_watcher = watcher .waitfor_status ("Job" , name , namespace , status_list = ["Failed" , "Succeeded" , "Deleted" ],)
294
274
295
275
# not need to read status and logs anymore.
296
276
watcher .stop ()
0 commit comments