11#!/usr/bin/env python 
22
3+ import  os 
34import  sys 
45import  copy 
56import  json 
7+ import  base64 
8+ import  tempfile 
69from  functools  import  partial 
7- from  six  import  PY2 
10+ from  six  import  PY2 ,  PY3 
811
912import  yaml 
1013from  pprint  import  pformat 
3235    'ResourceField' ,
3336]
3437
38+ class  CacheEncoder (json .JSONEncoder ):
39+ 
40+     def  default (self , o ):
41+         return  o .to_dict ()
42+ 
43+ def  cache_decoder (client ):
44+ 
45+     class  CacheDecoder (json .JSONDecoder ):
46+         def  __init__ (self , * args , ** kwargs ):
47+             json .JSONDecoder .__init__ (self , object_hook = self .object_hook , * args , ** kwargs )
48+ 
49+         def  object_hook (self , obj ):
50+             if  '_type'  not  in obj :
51+                 return  obj 
52+             _type  =  obj .pop ('_type' )
53+             if  _type  ==  'Resource' :
54+                 return  Resource (client = client , ** obj )
55+             elif  _type  ==  'ResourceList' :
56+                 return  ResourceList (obj ['resource' ])
57+             return  obj 
58+ 
59+     return  CacheDecoder 
3560
3661def  meta_request (func ):
3762    """ Handles parsing response structure and translating API Exceptions """ 
@@ -66,18 +91,45 @@ class DynamicClient(object):
6691        the kubernetes API 
6792    """ 
6893
69-     def  __init__ (self , client ):
94+     def  __init__ (self , client ,  cache_file = None ):
7095        self .client  =  client 
7196        self .configuration  =  client .configuration 
97+         default_cache_id  =  self .configuration .host 
98+         if  PY3 :
99+             default_cache_id  =  default_cache_id .encode ('utf-8' )
100+         default_cachefile_name  =  'osrcp-{0}.json' .format (base64 .b64encode (default_cache_id ).decode ('utf-8' ))
101+         self .__resources  =  ResourceContainer ({}, client = self )
102+         self .__cache_file  =  cache_file  or  os .path .join (tempfile .gettempdir (), default_cachefile_name )
103+         self .__init_cache ()
104+ 
105+     def  __init_cache (self , refresh = False ):
106+         if  refresh  or  not  os .path .exists (self .__cache_file ):
107+             self .__cache  =  {}
108+             refresh  =  True 
109+         else :
110+             with  open (self .__cache_file , 'r' ) as  f :
111+                 self .__cache  =  json .load (f , cls = cache_decoder (self ))
72112        self ._load_server_info ()
73-         self .__resources  =  ResourceContainer (self .parse_api_groups ())
113+         self .__resources .update (self .parse_api_groups ())
114+ 
115+         if  refresh :
116+             self .__write_cache ()
117+ 
118+     def  __write_cache (self ):
119+         with  open (self .__cache_file , 'w' ) as  f :
120+             json .dump (self .__cache , f , cls = CacheEncoder )
121+ 
122+     def  invalidate_cache (self ):
123+         self .__init_cache (refresh = True )
74124
75125    def  _load_server_info (self ):
76-         self .__version  =  {'kubernetes' : load_json (self .request ('get' , '/version' ))}
77-         try :
78-             self .__version ['openshift' ] =  load_json (self .request ('get' , '/version/openshift' ))
79-         except  ApiException :
80-             pass 
126+         if  not  self .__cache .get ('version' ):
127+             self .__cache ['version' ] =  {'kubernetes' : load_json (self .request ('get' , '/version' ))}
128+             try :
129+                 self .__cache ['version' ]['openshift' ] =  load_json (self .request ('get' , '/version/openshift' ))
130+             except  ApiException :
131+                 pass 
132+         self .__version  =  self .__cache ['version' ]
81133
82134    @property  
83135    def  resources (self ):
@@ -102,20 +154,22 @@ def default_groups(self):
102154
103155    def  parse_api_groups (self ):
104156        """ Discovers all API groups present in the cluster """ 
105-         prefix  =  'apis' 
106-         groups_response  =  load_json (self .request ('GET' , '/{}' .format (prefix )))['groups' ]
107- 
108-         groups  =  self .default_groups ()
109-         groups [prefix ] =  {}
110- 
111-         for  group  in  groups_response :
112-             new_group  =  {}
113-             for  version_raw  in  group ['versions' ]:
114-                 version  =  version_raw ['version' ]
115-                 preferred  =  version_raw  ==  group ['preferredVersion' ]
116-                 new_group [version ] =  self .get_resources_for_api_version (prefix , group ['name' ], version , preferred )
117-             groups [prefix ][group ['name' ]] =  new_group 
118-         return  groups 
157+         if  not  self .__cache .get ('resources' ):
158+             prefix  =  'apis' 
159+             groups_response  =  load_json (self .request ('GET' , '/{}' .format (prefix )))['groups' ]
160+ 
161+             groups  =  self .default_groups ()
162+             groups [prefix ] =  {}
163+ 
164+             for  group  in  groups_response :
165+                 new_group  =  {}
166+                 for  version_raw  in  group ['versions' ]:
167+                     version  =  version_raw ['version' ]
168+                     preferred  =  version_raw  ==  group ['preferredVersion' ]
169+                     new_group [version ] =  self .get_resources_for_api_version (prefix , group ['name' ], version , preferred )
170+                 groups [prefix ][group ['name' ]] =  new_group 
171+             self .__cache ['resources' ] =  groups 
172+         return  self .__cache ['resources' ]
119173
120174    def  get_resources_for_api_version (self , prefix , group , version , preferred ):
121175        """ returns a dictionary of resources associated with provided groupVersion""" 
@@ -369,6 +423,24 @@ def __init__(self, prefix=None, group=None, api_version=None, kind=None,
369423
370424        self .extra_args  =  kwargs 
371425
426+     def  to_dict (self ):
427+         return  {
428+             '_type' : 'Resource' ,
429+             'prefix' : self .prefix ,
430+             'group' : self .group ,
431+             'api_version' : self .api_version ,
432+             'kind' : self .kind ,
433+             'namespaced' : self .namespaced ,
434+             'verbs' : self .verbs ,
435+             'name' : self .name ,
436+             'preferred' : self .preferred ,
437+             'singular_name' : self .singular_name ,
438+             'short_names' : self .short_names ,
439+             'categories' : self .categories ,
440+             'subresources' : {k : sr .to_dict () for  k , sr  in  self .subresources .items ()},
441+             'extra_args' : self .extra_args ,
442+         }
443+ 
372444    @property  
373445    def  group_version (self ):
374446        if  self .group :
@@ -455,6 +527,12 @@ def patch(self, *args, **kwargs):
455527    def  __getattr__ (self , name ):
456528        return  getattr (self .resource , name )
457529
530+     def  to_dict (self ):
531+         return  {
532+             '_type' : 'ResourceList' ,
533+             'resource' : self .resource .to_dict (),
534+         }
535+ 
458536
459537class  Subresource (Resource ):
460538    """ Represents a subresource of an API resource. This generally includes operations 
@@ -498,13 +576,27 @@ def urls(self):
498576    def  __getattr__ (self , name ):
499577        return  partial (getattr (self .parent .client , name ), self )
500578
579+     def  to_dict (self ):
580+         return  {
581+             'kind' : self .kind ,
582+             'name' : self .name ,
583+             'subresource' : self .subresource ,
584+             'namespaced' : self .namespaced ,
585+             'verbs' : self .verbs ,
586+             'extra_args' : self .extra_args ,
587+         }
588+ 
501589
502590class  ResourceContainer (object ):
503591    """ A convenient container for storing discovered API resources. Allows 
504592        easy searching and retrieval of specific resources 
505593    """ 
506594
507-     def  __init__ (self , resources ):
595+     def  __init__ (self , resources , client = None ):
596+         self .__resources  =  resources 
597+         self .__client  =  client 
598+ 
599+     def  update (self , resources ):
508600        self .__resources  =  resources 
509601
510602    @property  
@@ -544,7 +636,11 @@ def search(self, **kwargs):
544636
545637            The arbitrary arguments can be any valid attribute for an openshift.dynamic.Resource object 
546638        """ 
547-         return  self .__search (self .__build_search (** kwargs ), self .__resources )
639+         results  =  self .__search (self .__build_search (** kwargs ), self .__resources )
640+         if  not  results :
641+             self .__client .invalidate_cache ()
642+             results  =  self .__search (self .__build_search (** kwargs ), self .__resources )
643+         return  results 
548644
549645    def  __build_search (self , kind = None , api_version = None , prefix = None , ** kwargs ):
550646        if  api_version  and  '/'  in  api_version :
0 commit comments