15
15
# added to get around circular dependencies caused by k8s.py clashing with
16
16
# k8s/__init__.py
17
17
import datetime
18
- import functools
19
18
import json
20
19
import logging
21
20
import pathlib
33
32
from framework .infrastructure .k8s_internal import k8s_port_forwarder
34
33
35
34
logger = logging .getLogger (__name__ )
35
+
36
36
# Type aliases
37
37
_HighlighterYaml = framework .helpers .highlighter .HighlighterYaml
38
38
PodLogCollector = k8s_log_collector .PodLogCollector
39
39
PortForwarder = k8s_port_forwarder .PortForwarder
40
+ ApiClient = client .ApiClient
40
41
V1Deployment = client .V1Deployment
41
42
V1ServiceAccount = client .V1ServiceAccount
42
43
V1Pod = client .V1Pod
43
44
V1PodList = client .V1PodList
44
45
V1Service = client .V1Service
45
46
V1Namespace = client .V1Namespace
46
47
ApiException = client .ApiException
48
+ FailToCreateError = utils .FailToCreateError
47
49
48
50
49
- def simple_resource_get (func ):
51
+ def _simple_resource_get (func ):
50
52
51
- def wrap_not_found_return_none ( * args , ** kwargs ):
53
+ def _wrap_simple_resource_get ( self : 'KubernetesNamespace' , * args , ** kwargs ):
52
54
try :
53
- return func (* args , ** kwargs )
54
- except client . ApiException as e :
55
+ return func (self , * args , ** kwargs )
56
+ except ApiException as e :
55
57
if e .status == 404 :
56
- # Ignore 404
58
+ # Instead of trowing an error when a resource doesn't exist,
59
+ # just return None.
57
60
return None
61
+ elif e .status == 401 :
62
+ # 401 Unauthorized: token might be expired, attempt auth refresh
63
+ self .refresh_auth ()
64
+ return func (self , * args , ** kwargs )
65
+ # Reraise for anything else.
58
66
raise
59
67
60
- return wrap_not_found_return_none
68
+ return _wrap_simple_resource_get
61
69
62
70
63
71
def label_dict_to_selector (labels : dict ) -> str :
64
72
return ',' .join (f'{ k } =={ v } ' for k , v in labels .items ())
65
73
66
74
67
75
class KubernetesApiManager :
76
+ _client : ApiClient
77
+ context : str
78
+ apps : client .AppsV1Api
79
+ core : client .CoreV1Api
80
+ _apis : set
68
81
69
- def __init__ (self , context ):
82
+ def __init__ (self , context : str ):
70
83
self .context = context
71
- self .client = self ._cached_api_client_for_context (context )
84
+ self ._client = self ._new_client_from_context (context )
72
85
self .apps = client .AppsV1Api (self .client )
73
86
self .core = client .CoreV1Api (self .client )
87
+ self ._apis = {self .apps , self .core }
88
+
89
+ @property
90
+ def client (self ) -> ApiClient :
91
+ return self ._client
74
92
75
93
def close (self ):
76
94
self .client .close ()
77
95
78
- @classmethod
79
- @functools .lru_cache (None )
80
- def _cached_api_client_for_context (cls , context : str ) -> client .ApiClient :
96
+ def reload (self ):
97
+ self .close ()
98
+ self ._client = self ._new_client_from_context (self .context )
99
+ # Update default configuration so that modules that initialize
100
+ # ApiClient implicitly (e.g. kubernetes.watch.Watch) get the updates.
101
+ client .Configuration .set_default (self ._client .configuration )
102
+ for api in self ._apis :
103
+ api .api_client = self ._client
104
+
105
+ @staticmethod
106
+ def _new_client_from_context (context : str ) -> ApiClient :
81
107
client_instance = kubernetes .config .new_client_from_config (
82
108
context = context )
83
109
logger .info ('Using kubernetes context "%s", active host: %s' , context ,
@@ -101,16 +127,20 @@ def __init__(self, api: KubernetesApiManager, name: str):
101
127
self .name = name
102
128
self .api = api
103
129
130
+ def refresh_auth (self ):
131
+ logger .info ('Reloading k8s api client to refresh the auth.' )
132
+ self .api .reload ()
133
+
104
134
def apply_manifest (self , manifest ):
105
135
return utils .create_from_dict (self .api .client ,
106
136
manifest ,
107
137
namespace = self .name )
108
138
109
- @simple_resource_get
139
+ @_simple_resource_get
110
140
def get_service (self , name ) -> V1Service :
111
141
return self .api .core .read_namespaced_service (name , self .name )
112
142
113
- @simple_resource_get
143
+ @_simple_resource_get
114
144
def get_service_account (self , name ) -> V1Service :
115
145
return self .api .core .read_namespaced_service_account (name , self .name )
116
146
@@ -134,7 +164,7 @@ def delete_service_account(self,
134
164
propagation_policy = 'Foreground' ,
135
165
grace_period_seconds = grace_period_seconds ))
136
166
137
- @simple_resource_get
167
+ @_simple_resource_get
138
168
def get (self ) -> V1Namespace :
139
169
return self .api .core .read_namespace (self .name )
140
170
@@ -202,7 +232,7 @@ def get_service_neg(self, service_name: str,
202
232
neg_zones : List [str ] = neg_info ['zones' ]
203
233
return neg_name , neg_zones
204
234
205
- @simple_resource_get
235
+ @_simple_resource_get
206
236
def get_deployment (self , name ) -> V1Deployment :
207
237
return self .api .apps .read_namespaced_deployment (name , self .name )
208
238
0 commit comments