1- import copy
2- import yaml
3- from functools import partial
4-
5- from pprint import pformat
6-
7-
8- class Resource (object ):
9- """ Represents an API resource type, containing the information required to build urls for requests """
10-
11- def __init__ (self , prefix = None , group = None , api_version = None , kind = None ,
12- namespaced = False , verbs = None , name = None , preferred = False , client = None ,
13- singularName = None , shortNames = None , categories = None , subresources = None , ** kwargs ):
14-
15- if None in (api_version , kind , prefix ):
16- raise ValueError ("At least prefix, kind, and api_version must be provided" )
17-
18- self .prefix = prefix
19- self .group = group
20- self .api_version = api_version
21- self .kind = kind
22- self .namespaced = namespaced
23- self .verbs = verbs
24- self .name = name
25- self .preferred = preferred
26- self .client = client
27- self .singular_name = singularName or (name [:- 1 ] if name else "" )
28- self .short_names = shortNames
29- self .categories = categories
30- self .subresources = {
31- k : Subresource (self , ** v ) for k , v in (subresources or {}).items ()
32- }
33-
34- self .extra_args = kwargs
35-
36- def to_dict (self ):
37- return {
38- '_type' : 'Resource' ,
39- 'prefix' : self .prefix ,
40- 'group' : self .group ,
41- 'api_version' : self .api_version ,
42- 'kind' : self .kind ,
43- 'namespaced' : self .namespaced ,
44- 'verbs' : self .verbs ,
45- 'name' : self .name ,
46- 'preferred' : self .preferred ,
47- 'singular_name' : self .singular_name ,
48- 'short_names' : self .short_names ,
49- 'categories' : self .categories ,
50- 'subresources' : {k : sr .to_dict () for k , sr in self .subresources .items ()},
51- 'extra_args' : self .extra_args ,
52- }
53-
54- @property
55- def group_version (self ):
56- if self .group :
57- return '{}/{}' .format (self .group , self .api_version )
58- return self .api_version
59-
60- def __repr__ (self ):
61- return '<{}({}/{})>' .format (self .__class__ .__name__ , self .group_version , self .name )
62-
63- @property
64- def urls (self ):
65- full_prefix = '{}/{}' .format (self .prefix , self .group_version )
66- resource_name = self .name .lower ()
67- return {
68- 'base' : '/{}/{}' .format (full_prefix , resource_name ),
69- 'namespaced_base' : '/{}/namespaces/{{namespace}}/{}' .format (full_prefix , resource_name ),
70- 'full' : '/{}/{}/{{name}}' .format (full_prefix , resource_name ),
71- 'namespaced_full' : '/{}/namespaces/{{namespace}}/{}/{{name}}' .format (full_prefix , resource_name )
72- }
73-
74- def path (self , name = None , namespace = None ):
75- url_type = []
76- path_params = {}
77- if self .namespaced and namespace :
78- url_type .append ('namespaced' )
79- path_params ['namespace' ] = namespace
80- if name :
81- url_type .append ('full' )
82- path_params ['name' ] = name
83- else :
84- url_type .append ('base' )
85- return self .urls ['_' .join (url_type )].format (** path_params )
86-
87- def __getattr__ (self , name ):
88- if name in self .subresources :
89- return self .subresources [name ]
90- return partial (getattr (self .client , name ), self )
1+ from kubernetes .dynamic .resource import Resource , Subresource , ResourceField # noqa
912
923
934class ResourceList (Resource ):
@@ -110,92 +21,6 @@ def base_resource(self):
11021 return self .__base_resource
11122 return None
11223
113- def _items_to_resources (self , body ):
114- """ Takes a List body and return a dictionary with the following structure:
115- {
116- 'api_version': str,
117- 'kind': str,
118- 'items': [{
119- 'resource': Resource,
120- 'name': str,
121- 'namespace': str,
122- }]
123- }
124- """
125- if body is None :
126- raise ValueError ("You must provide a body when calling methods on a ResourceList" )
127-
128- api_version = body ['apiVersion' ]
129- kind = body ['kind' ]
130- items = body .get ('items' )
131- if not items :
132- raise ValueError ('The `items` field in the body must be populated when calling methods on a ResourceList' )
133-
134- if self .kind != kind :
135- raise ValueError ('Methods on a {} must be called with a body containing the same kind. Received {} instead' .format (self .kind , kind ))
136-
137- return {
138- 'api_version' : api_version ,
139- 'kind' : kind ,
140- 'items' : [self ._item_to_resource (item ) for item in items ]
141- }
142-
143- def _item_to_resource (self , item ):
144- metadata = item .get ('metadata' , {})
145- resource = self .base_resource ()
146- if not resource :
147- api_version = item .get ('apiVersion' , self .api_version )
148- kind = item .get ('kind' , self .base_kind )
149- resource = self .client .resources .get (api_version = api_version , kind = kind )
150- return {
151- 'resource' : resource ,
152- 'definition' : item ,
153- 'name' : metadata .get ('name' ),
154- 'namespace' : metadata .get ('namespace' )
155- }
156-
157- def get (self , body = None , name = None , namespace = None , ** kwargs ):
158- if name :
159- raise ValueError ('Operations on ResourceList objects do not support the `name` argument' )
160- resource_list = self ._items_to_resources (body )
161- response = copy .deepcopy (body )
162-
163- response ['items' ] = [
164- item ['resource' ].get (name = item ['name' ], namespace = item ['namespace' ] or namespace , ** kwargs ).to_dict ()
165- for item in resource_list ['items' ]
166- ]
167- return ResourceInstance (self , response )
168-
169- def delete (self , body = None , name = None , namespace = None , ** kwargs ):
170- if name :
171- raise ValueError ('Operations on ResourceList objects do not support the `name` argument' )
172- resource_list = self ._items_to_resources (body )
173- response = copy .deepcopy (body )
174-
175- response ['items' ] = [
176- item ['resource' ].delete (name = item ['name' ], namespace = item ['namespace' ] or namespace , ** kwargs ).to_dict ()
177- for item in resource_list ['items' ]
178- ]
179- return ResourceInstance (self , response )
180-
181- def verb_mapper (self , verb , body = None , ** kwargs ):
182- resource_list = self ._items_to_resources (body )
183- response = copy .deepcopy (body )
184- response ['items' ] = [
185- getattr (item ['resource' ], verb )(body = item ['definition' ], ** kwargs ).to_dict ()
186- for item in resource_list ['items' ]
187- ]
188- return ResourceInstance (self , response )
189-
190- def create (self , * args , ** kwargs ):
191- return self .verb_mapper ('create' , * args , ** kwargs )
192-
193- def replace (self , * args , ** kwargs ):
194- return self .verb_mapper ('replace' , * args , ** kwargs )
195-
196- def patch (self , * args , ** kwargs ):
197- return self .verb_mapper ('patch' , * args , ** kwargs )
198-
19924 def apply (self , * args , ** kwargs ):
20025 return self .verb_mapper ('apply' , * args , ** kwargs )
20126
@@ -215,54 +40,6 @@ def __getattr__(self, name):
21540 return None
21641
21742
218- class Subresource (Resource ):
219- """ Represents a subresource of an API resource. This generally includes operations
220- like scale, as well as status objects for an instantiated resource
221- """
222-
223- def __init__ (self , parent , ** kwargs ):
224- self .parent = parent
225- self .prefix = parent .prefix
226- self .group = parent .group
227- self .api_version = parent .api_version
228- self .kind = kwargs .pop ('kind' )
229- self .name = kwargs .pop ('name' )
230- self .subresource = self .name .split ('/' )[1 ]
231- self .namespaced = kwargs .pop ('namespaced' , False )
232- self .verbs = kwargs .pop ('verbs' , None )
233- self .extra_args = kwargs
234-
235- #TODO(fabianvf): Determine proper way to handle differences between resources + subresources
236- def create (self , body = None , name = None , namespace = None , ** kwargs ):
237- name = name or body .get ('metadata' , {}).get ('name' )
238- body = self .parent .client .serialize_body (body )
239- if self .parent .namespaced :
240- namespace = self .parent .client .ensure_namespace (self .parent , namespace , body )
241- path = self .path (name = name , namespace = namespace )
242- return self .parent .client .request ('post' , path , body = body , ** kwargs )
243-
244- @property
245- def urls (self ):
246- full_prefix = '{}/{}' .format (self .prefix , self .group_version )
247- return {
248- 'full' : '/{}/{}/{{name}}/{}' .format (full_prefix , self .parent .name , self .subresource ),
249- 'namespaced_full' : '/{}/namespaces/{{namespace}}/{}/{{name}}/{}' .format (full_prefix , self .parent .name , self .subresource )
250- }
251-
252- def __getattr__ (self , name ):
253- return partial (getattr (self .parent .client , name ), self )
254-
255- def to_dict (self ):
256- return {
257- 'kind' : self .kind ,
258- 'name' : self .name ,
259- 'subresource' : self .subresource ,
260- 'namespaced' : self .namespaced ,
261- 'verbs' : self .verbs ,
262- 'extra_args' : self .extra_args ,
263- }
264-
265-
26643class ResourceInstance (object ):
26744 """ A parsed instance of an API resource. It exists solely to
26845 ease interaction with API objects by allowing attributes to
@@ -285,93 +62,3 @@ def __init__(self, client, instance):
28562
28663 self .attributes = self .__deserialize (instance )
28764 self .__initialised = True
288-
289- def __deserialize (self , field ):
290- if isinstance (field , dict ):
291- return ResourceField (** {
292- k : self .__deserialize (v ) for k , v in field .items ()
293- })
294- elif isinstance (field , (list , tuple )):
295- return [self .__deserialize (item ) for item in field ]
296- else :
297- return field
298-
299- def __serialize (self , field ):
300- if isinstance (field , ResourceField ):
301- return {
302- k : self .__serialize (v ) for k , v in field .__dict__ .items ()
303- }
304- elif isinstance (field , (list , tuple )):
305- return [self .__serialize (item ) for item in field ]
306- elif isinstance (field , ResourceInstance ):
307- return field .to_dict ()
308- else :
309- return field
310-
311- def to_dict (self ):
312- return self .__serialize (self .attributes )
313-
314- def to_str (self ):
315- return repr (self )
316-
317- def __repr__ (self ):
318- return "ResourceInstance[{}]:\n {}" .format (
319- self .attributes .kind ,
320- ' ' .join (yaml .safe_dump (self .to_dict ()).splitlines (True ))
321- )
322-
323- def __getattr__ (self , name ):
324- if not '_ResourceInstance__initialised' in self .__dict__ :
325- return super (ResourceInstance , self ).__getattr__ (name )
326- return getattr (self .attributes , name )
327-
328- def __setattr__ (self , name , value ):
329- if not '_ResourceInstance__initialised' in self .__dict__ :
330- return super (ResourceInstance , self ).__setattr__ (name , value )
331- elif name in self .__dict__ :
332- return super (ResourceInstance , self ).__setattr__ (name , value )
333- else :
334- self .attributes [name ] = value
335-
336- def __getitem__ (self , name ):
337- return self .attributes [name ]
338-
339- def __setitem__ (self , name , value ):
340- self .attributes [name ] = value
341-
342- def __dir__ (self ):
343- return dir (type (self )) + list (self .attributes .__dict__ .keys ())
344-
345-
346- class ResourceField (object ):
347- """ A parsed instance of an API resource attribute. It exists
348- solely to ease interaction with API objects by allowing
349- attributes to be accessed with '.' notation
350- """
351-
352- def __init__ (self , ** kwargs ):
353- self .__dict__ .update (kwargs )
354-
355- def __repr__ (self ):
356- return pformat (self .__dict__ )
357-
358- def __eq__ (self , other ):
359- return self .__dict__ == other .__dict__
360-
361- def __getitem__ (self , name ):
362- return self .__dict__ .get (name )
363-
364- # Here resource.items will return items if available or resource.__dict__.items function if not
365- # resource.get will call resource.__dict__.get after attempting resource.__dict__.get('get')
366- def __getattr__ (self , name ):
367- return self .__dict__ .get (name , getattr (self .__dict__ , name , None ))
368-
369- def __setattr__ (self , name , value ):
370- self .__dict__ [name ] = value
371-
372- def __dir__ (self ):
373- return dir (type (self )) + list (self .__dict__ .keys ())
374-
375- def __iter__ (self ):
376- for k , v in self .__dict__ .items ():
377- yield (k , v )
0 commit comments