1- import six
2- import json
3-
4- from kubernetes import watch
5- from kubernetes .client .rest import ApiException
1+ from kubernetes .dynamic .client import meta_request # noqa
2+ from kubernetes .dynamic .client import DynamicClient as K8sDynamicClient
63
74from .apply import apply
85from .discovery import EagerDiscoverer , LazyDiscoverer
9- from .exceptions import api_exception , KubernetesValidateMissing , ApplyException
106from .resource import Resource , ResourceList , Subresource , ResourceInstance , ResourceField
7+ from .exceptions import ApplyException
118
129try :
13- import kubernetes_validate
10+ import kubernetes_validate # noqa
1411 HAS_KUBERNETES_VALIDATE = True
1512except ImportError :
1613 HAS_KUBERNETES_VALIDATE = False
@@ -33,103 +30,14 @@ class VersionNotSupportedError(NotImplementedError):
3330]
3431
3532
36- def meta_request (func ):
37- """ Handles parsing response structure and translating API Exceptions """
38- def inner (self , * args , ** kwargs ):
39- serialize_response = kwargs .pop ('serialize' , True )
40- serializer = kwargs .pop ('serializer' , ResourceInstance )
41- try :
42- resp = func (self , * args , ** kwargs )
43- except ApiException as e :
44- raise api_exception (e )
45- if serialize_response :
46- try :
47- if six .PY2 :
48- return serializer (self , json .loads (resp .data ))
49- return serializer (self , json .loads (resp .data .decode ('utf8' )))
50- except ValueError :
51- if six .PY2 :
52- return resp .data
53- return resp .data .decode ('utf8' )
54- return resp
55-
56- return inner
57-
58-
59- class DynamicClient (object ):
33+ class DynamicClient (K8sDynamicClient ):
6034 """ A kubernetes client that dynamically discovers and interacts with
6135 the kubernetes API
6236 """
6337
6438 def __init__ (self , client , cache_file = None , discoverer = None ):
65- # Setting default here to delay evaluation of LazyDiscoverer class
66- # until constructor is called
6739 discoverer = discoverer or LazyDiscoverer
68-
69- self .client = client
70- self .configuration = client .configuration
71- self .__discoverer = discoverer (self , cache_file )
72-
73- @property
74- def resources (self ):
75- return self .__discoverer
76-
77- @property
78- def version (self ):
79- return self .__discoverer .version
80-
81- def ensure_namespace (self , resource , namespace , body ):
82- namespace = namespace or body .get ('metadata' , {}).get ('namespace' )
83- if not namespace :
84- raise ValueError ("Namespace is required for {}.{}" .format (resource .group_version , resource .kind ))
85- return namespace
86-
87- def serialize_body (self , body ):
88- if hasattr (body , 'to_dict' ):
89- return body .to_dict ()
90- return body or {}
91-
92- def get (self , resource , name = None , namespace = None , ** kwargs ):
93- path = resource .path (name = name , namespace = namespace )
94- return self .request ('get' , path , ** kwargs )
95-
96- def create (self , resource , body = None , namespace = None , ** kwargs ):
97- body = self .serialize_body (body )
98- if resource .namespaced :
99- namespace = self .ensure_namespace (resource , namespace , body )
100- path = resource .path (namespace = namespace )
101- return self .request ('post' , path , body = body , ** kwargs )
102-
103- def delete (self , resource , name = None , namespace = None , label_selector = None , field_selector = None , ** kwargs ):
104- if not (name or label_selector or field_selector ):
105- raise ValueError ("At least one of name|label_selector|field_selector is required" )
106- if resource .namespaced and not (label_selector or field_selector or namespace ):
107- raise ValueError ("At least one of namespace|label_selector|field_selector is required" )
108- path = resource .path (name = name , namespace = namespace )
109- return self .request ('delete' , path , label_selector = label_selector , field_selector = field_selector , ** kwargs )
110-
111- def replace (self , resource , body = None , name = None , namespace = None , ** kwargs ):
112- body = self .serialize_body (body )
113- name = name or body .get ('metadata' , {}).get ('name' )
114- if not name :
115- raise ValueError ("name is required to replace {}.{}" .format (resource .group_version , resource .kind ))
116- if resource .namespaced :
117- namespace = self .ensure_namespace (resource , namespace , body )
118- path = resource .path (name = name , namespace = namespace )
119- return self .request ('put' , path , body = body , ** kwargs )
120-
121- def patch (self , resource , body = None , name = None , namespace = None , ** kwargs ):
122- body = self .serialize_body (body )
123- name = name or body .get ('metadata' , {}).get ('name' )
124- if not name :
125- raise ValueError ("name is required to patch {}.{}" .format (resource .group_version , resource .kind ))
126- if resource .namespaced :
127- namespace = self .ensure_namespace (resource , namespace , body )
128-
129- content_type = kwargs .pop ('content_type' , 'application/strategic-merge-patch+json' )
130- path = resource .path (name = name , namespace = namespace )
131-
132- return self .request ('patch' , path , body = body , content_type = content_type , ** kwargs )
40+ K8sDynamicClient .__init__ (self , client , cache_file = cache_file , discoverer = discoverer )
13341
13442 def apply (self , resource , body = None , name = None , namespace = None ):
13543 body = self .serialize_body (body )
@@ -144,139 +52,3 @@ def apply(self, resource, body=None, name=None, namespace=None):
14452 except ApplyException as e :
14553 raise ValueError ("Could not apply strategic merge to %s/%s: %s" %
14654 (body ['kind' ], body ['metadata' ]['name' ], e ))
147-
148- def watch (self , resource , namespace = None , name = None , label_selector = None , field_selector = None , resource_version = None , timeout = None ):
149- """
150- Stream events for a resource from the Kubernetes API
151-
152- :param resource: The API resource object that will be used to query the API
153- :param namespace: The namespace to query
154- :param name: The name of the resource instance to query
155- :param label_selector: The label selector with which to filter results
156- :param field_selector: The field selector with which to filter results
157- :param resource_version: The version with which to filter results. Only events with
158- a resource_version greater than this value will be returned
159- :param timeout: The amount of time in seconds to wait before terminating the stream
160-
161- :return: Event object with these keys:
162- 'type': The type of event such as "ADDED", "DELETED", etc.
163- 'raw_object': a dict representing the watched object.
164- 'object': A ResourceInstance wrapping raw_object.
165-
166- Example:
167- client = DynamicClient(k8s_client)
168- v1_pods = client.resources.get(api_version='v1', kind='Pod')
169-
170- for e in v1_pods.watch(resource_version=0, namespace=default, timeout=5):
171- print(e['type'])
172- print(e['object'].metadata)
173- """
174- watcher = watch .Watch ()
175- for event in watcher .stream (
176- resource .get ,
177- namespace = namespace ,
178- name = name ,
179- field_selector = field_selector ,
180- label_selector = label_selector ,
181- resource_version = resource_version ,
182- serialize = False ,
183- timeout_seconds = timeout
184- ):
185- event ['object' ] = ResourceInstance (resource , event ['object' ])
186- yield event
187-
188- @meta_request
189- def request (self , method , path , body = None , ** params ):
190- if not path .startswith ('/' ):
191- path = '/' + path
192-
193- path_params = params .get ('path_params' , {})
194- query_params = params .get ('query_params' , [])
195- if params .get ('pretty' ) is not None :
196- query_params .append (('pretty' , params ['pretty' ]))
197- if params .get ('_continue' ) is not None :
198- query_params .append (('continue' , params ['_continue' ]))
199- if params .get ('include_uninitialized' ) is not None :
200- query_params .append (('includeUninitialized' , params ['include_uninitialized' ]))
201- if params .get ('field_selector' ) is not None :
202- query_params .append (('fieldSelector' , params ['field_selector' ]))
203- if params .get ('label_selector' ) is not None :
204- query_params .append (('labelSelector' , params ['label_selector' ]))
205- if params .get ('limit' ) is not None :
206- query_params .append (('limit' , params ['limit' ]))
207- if params .get ('resource_version' ) is not None :
208- query_params .append (('resourceVersion' , params ['resource_version' ]))
209- if params .get ('timeout_seconds' ) is not None :
210- query_params .append (('timeoutSeconds' , params ['timeout_seconds' ]))
211- if params .get ('watch' ) is not None :
212- query_params .append (('watch' , params ['watch' ]))
213- if params .get ('dry_run' ) is not None :
214- query_params .append (('dryRun' , params ['dry_run' ]))
215-
216- header_params = params .get ('header_params' , {})
217- form_params = []
218- local_var_files = {}
219-
220- # Checking Accept header.
221- new_header_params = dict ((key .lower (), value ) for key , value in header_params .items ())
222- if not 'accept' in new_header_params :
223- header_params ['Accept' ] = self .client .select_header_accept ([
224- 'application/json' ,
225- 'application/yaml' ,
226- ])
227-
228- # HTTP header `Content-Type`
229- if params .get ('content_type' ):
230- header_params ['Content-Type' ] = params ['content_type' ]
231- else :
232- header_params ['Content-Type' ] = self .client .select_header_content_type (['*/*' ])
233-
234- # Authentication setting
235- auth_settings = ['BearerToken' ]
236-
237- return self .client .call_api (
238- path ,
239- method .upper (),
240- path_params ,
241- query_params ,
242- header_params ,
243- body = body ,
244- post_params = form_params ,
245- async_req = params .get ('async_req' ),
246- files = local_var_files ,
247- auth_settings = auth_settings ,
248- _preload_content = False ,
249- _return_http_data_only = params .get ('_return_http_data_only' , True )
250- )
251-
252- def validate (self , definition , version = None , strict = False ):
253- """validate checks a kubernetes resource definition
254-
255- Args:
256- definition (dict): resource definition
257- version (str): version of kubernetes to validate against
258- strict (bool): whether unexpected additional properties should be considered errors
259-
260- Returns:
261- warnings (list), errors (list): warnings are missing validations, errors are validation failures
262- """
263- if not HAS_KUBERNETES_VALIDATE :
264- raise KubernetesValidateMissing ()
265-
266- errors = list ()
267- warnings = list ()
268- try :
269- if version is None :
270- try :
271- version = self .version ['kubernetes' ]['gitVersion' ]
272- except KeyError :
273- version = kubernetes_validate .latest_version ()
274- kubernetes_validate .validate (definition , version , strict )
275- except kubernetes_validate .utils .ValidationError as e :
276- errors .append ("resource definition validation error at %s: %s" % ('.' .join ([str (item ) for item in e .path ]), e .message )) # noqa: B306
277- except VersionNotSupportedError :
278- errors .append ("Kubernetes version %s is not supported by kubernetes-validate" % version )
279- except kubernetes_validate .utils .SchemaNotFoundError as e :
280- warnings .append ("Could not find schema for object kind %s with API version %s in Kubernetes version %s (possibly Custom Resource?)" %
281- (e .kind , e .api_version , e .version ))
282- return warnings , errors
0 commit comments