/
loader.py
1494 lines (1295 loc) · 50.6 KB
/
loader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
'''
We wanna be free to ride our machines without being hassled by The Man!
And we wanna get loaded!
And we wanna have a good time!
And that's what we are gonna do. We are gonna have a good time...
'''
# Import python libs
from __future__ import absolute_import
import os
import imp
import sys
import salt
import time
import logging
import inspect
import tempfile
import functools
from collections import MutableMapping
from zipimport import zipimporter
# Import salt libs
from salt.exceptions import LoaderError
from salt.template import check_render_pipe_str
from salt.utils.decorators import Depends
from salt.utils import context
import salt.utils.lazy
import salt.utils.odict
import salt.utils.event
import salt.utils.odict
# Solve the Chicken and egg problem where grains need to run before any
# of the modules are loaded and are generally available for any usage.
import salt.modules.cmdmod
# Import 3rd-party libs
import salt.ext.six as six
__salt__ = {
'cmd.run': salt.modules.cmdmod._run_quiet
}
log = logging.getLogger(__name__)
SALT_BASE_PATH = os.path.abspath(os.path.dirname(salt.__file__))
LOADED_BASE_NAME = 'salt.loaded'
# Because on the cloud drivers we do `from salt.cloud.libcloudfuncs import *`
# which simplifies code readability, it adds some unsupported functions into
# the driver's module scope.
# We list un-supported functions here. These will be removed from the loaded.
LIBCLOUD_FUNCS_NOT_SUPPORTED = (
'parallels.avail_sizes',
'parallels.avail_locations',
'proxmox.avail_sizes',
'saltify.destroy',
'saltify.avail_sizes',
'saltify.avail_images',
'saltify.avail_locations',
'rackspace.reboot',
'openstack.list_locations',
'rackspace.list_locations'
)
# Will be set to pyximport module at runtime if cython is enabled in config.
pyximport = None
def static_loader(
opts,
ext_type,
tag,
pack=None,
int_type=None,
ext_dirs=True,
ext_type_dirs=None,
base_path=None,
filter_name=None,
):
funcs = LazyLoader(_module_dirs(opts,
ext_type,
tag,
int_type,
ext_dirs,
ext_type_dirs,
base_path),
opts,
tag=tag,
pack=pack,
)
ret = {}
funcs._load_all()
if filter_name:
funcs = FilterDictWrapper(funcs, filter_name)
for key in funcs:
ret[key] = funcs[key]
return ret
def _module_dirs(
opts,
ext_type,
tag,
int_type=None,
ext_dirs=True,
ext_type_dirs=None,
base_path=None,
):
sys_types = os.path.join(base_path or SALT_BASE_PATH, int_type or ext_type)
ext_types = os.path.join(opts['extension_modules'], ext_type)
ext_type_types = []
if ext_dirs:
if ext_type_dirs is None:
ext_type_dirs = '{0}_dirs'.format(tag)
if ext_type_dirs in opts:
ext_type_types.extend(opts[ext_type_dirs])
cli_module_dirs = []
# The dirs can be any module dir, or a in-tree _{ext_type} dir
for _dir in opts.get('module_dirs', []):
# Prepend to the list to match cli argument ordering
maybe_dir = os.path.join(_dir, ext_type)
if os.path.isdir(maybe_dir):
cli_module_dirs.insert(0, maybe_dir)
continue
maybe_dir = os.path.join(_dir, '_{0}'.format(ext_type))
if os.path.isdir(maybe_dir):
cli_module_dirs.insert(0, maybe_dir)
return cli_module_dirs + ext_type_types + [ext_types, sys_types]
def minion_mods(
opts,
context=None,
utils=None,
whitelist=None,
include_errors=False,
initial_load=False,
loaded_base_name=None,
notify=False,
proxy=None):
'''
Load execution modules
Returns a dictionary of execution modules appropriate for the current
system by evaluating the __virtual__() function in each module.
:param dict opts: The Salt options dictionary
:param dict context: A Salt context that should be made present inside
generated modules in __context__
:param dict utils: Utility functions which should be made available to
Salt modules in __utils__. See `utils_dir` in
salt.config for additional information about
configuration.
:param list whitelist: A list of modules which should be whitelisted.
:param bool include_errors: Deprecated flag! Unused.
:param bool initial_load: Deprecated flag! Unused.
:param str loaded_base_name: A string marker for the loaded base name.
:param bool notify: Flag indicating that an event should be fired upon
completion of module loading.
.. code-block:: python
import salt.config
import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion')
__grains__ = salt.loader.grains(__opts__)
__opts__['grains'] = __grains__
__salt__ = salt.loader.minion_mods(__opts__)
__salt__['test.ping']()
'''
# TODO Publish documentation for module whitelisting
if context is None:
context = {}
if utils is None:
utils = {}
if proxy is None:
proxy = {}
if not whitelist:
whitelist = opts.get('whitelist_modules', None)
ret = LazyLoader(_module_dirs(opts, 'modules', 'module'),
opts,
tag='module',
pack={'__context__': context, '__utils__': utils,
'__proxy__': proxy},
whitelist=whitelist,
loaded_base_name=loaded_base_name)
ret.pack['__salt__'] = ret
# Load any provider overrides from the configuration file providers option
# Note: Providers can be pkg, service, user or group - not to be confused
# with cloud providers.
providers = opts.get('providers', False)
if providers and isinstance(providers, dict):
for mod in providers:
# sometimes providers opts is not to diverge modules but
# for other configuration
try:
funcs = raw_mod(opts, providers[mod], ret)
except TypeError:
break
else:
if funcs:
for func in funcs:
f_key = '{0}{1}'.format(mod, func[func.rindex('.'):])
ret[f_key] = funcs[func]
if notify:
evt = salt.utils.event.get_event('minion', opts=opts, listen=False)
evt.fire_event({'complete': True}, tag='/salt/minion/minion_mod_complete')
return ret
def raw_mod(opts, name, functions, mod='modules'):
'''
Returns a single module loaded raw and bypassing the __virtual__ function
.. code-block:: python
import salt.config
import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion')
testmod = salt.loader.raw_mod(__opts__, 'test', None)
testmod['test.ping']()
'''
loader = LazyLoader(_module_dirs(opts, mod, 'rawmodule'),
opts,
tag='rawmodule',
virtual_enable=False,
pack={'__salt__': functions})
# if we don't have the module, return an empty dict
if name not in loader.file_mapping:
return {}
loader._load_module(name) # load a single module (the one passed in)
return dict(loader._dict) # return a copy of *just* the funcs for `name`
def engines(opts, functions, runners):
'''
Return the master services plugins
'''
pack = {'__salt__': functions,
'__runners__': runners}
return LazyLoader(_module_dirs(opts, 'engines', 'engines'),
opts,
tag='engines',
pack=pack)
def proxy(opts, functions=None, returners=None, whitelist=None):
'''
Returns the proxy module for this salt-proxy-minion
'''
ret = LazyLoader(_module_dirs(opts, 'proxy', 'proxy'),
opts,
tag='proxy',
pack={'__salt__': functions,
'__ret__': returners})
ret.pack['__proxy__'] = ret
return ret
def returners(opts, functions, whitelist=None, context=None):
'''
Returns the returner modules
'''
if context is None:
context = {}
return LazyLoader(_module_dirs(opts, 'returners', 'returner'),
opts,
tag='returner',
whitelist=whitelist,
pack={'__salt__': functions,
'__context__': context})
def utils(opts, whitelist=None, context=None):
'''
Returns the utility modules
'''
if context is None:
context = {}
return LazyLoader(_module_dirs(opts, 'utils', 'utils', ext_type_dirs='utils_dirs'),
opts,
tag='utils',
whitelist=whitelist,
pack={'__context__': context})
def pillars(opts, functions, context=None):
'''
Returns the pillars modules
'''
if context is None:
context = {}
ret = LazyLoader(_module_dirs(opts, 'pillar', 'pillar'),
opts,
tag='pillar',
pack={'__salt__': functions,
'__context__': context})
return FilterDictWrapper(ret, '.ext_pillar')
def tops(opts):
'''
Returns the tops modules
'''
if 'master_tops' not in opts:
return {}
whitelist = list(opts['master_tops'].keys())
ret = LazyLoader(_module_dirs(opts, 'tops', 'top'),
opts,
tag='top',
whitelist=whitelist)
return FilterDictWrapper(ret, '.top')
def wheels(opts, whitelist=None):
'''
Returns the wheels modules
'''
return LazyLoader(_module_dirs(opts, 'wheel', 'wheel'),
opts,
tag='wheel',
whitelist=whitelist)
def outputters(opts):
'''
Returns the outputters modules
:param dict opts: The Salt options dictionary
:returns: LazyLoader instance, with only outputters present in the keyspace
'''
ret = LazyLoader(_module_dirs(opts, 'output', 'output', ext_type_dirs='outputter_dirs'),
opts,
tag='output')
wrapped_ret = FilterDictWrapper(ret, '.output')
# TODO: this name seems terrible... __salt__ should always be execution mods
ret.pack['__salt__'] = wrapped_ret
return wrapped_ret
def serializers(opts):
'''
Returns the serializers modules
:param dict opts: The Salt options dictionary
:returns: LazyLoader instance, with only serializers present in the keyspace
'''
return LazyLoader(_module_dirs(opts, 'serializers', 'serializers'),
opts,
tag='serializers')
def auth(opts, whitelist=None):
'''
Returns the auth modules
:param dict opts: The Salt options dictionary
:returns: LazyLoader
'''
return LazyLoader(_module_dirs(opts, 'auth', 'auth'),
opts,
tag='auth',
whitelist=whitelist,
pack={'__salt__': minion_mods(opts)})
def fileserver(opts, backends):
'''
Returns the file server modules
'''
return LazyLoader(_module_dirs(opts, 'fileserver', 'fileserver'),
opts,
tag='fileserver',
whitelist=backends)
def roster(opts, whitelist=None):
'''
Returns the roster modules
'''
return LazyLoader(_module_dirs(opts, 'roster', 'roster'),
opts,
tag='roster',
whitelist=whitelist)
def states(opts, functions, utils, whitelist=None):
'''
Returns the state modules
:param dict opts: The Salt options dictionary
:param dict functions: A dictionary of minion modules, with module names as
keys and funcs as values.
.. code-block:: python
import salt.config
import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion')
statemods = salt.loader.states(__opts__, None, None)
'''
ret = LazyLoader(_module_dirs(opts, 'states', 'states'),
opts,
tag='states',
pack={'__salt__': functions},
whitelist=whitelist)
ret.pack['__states__'] = ret
ret.pack['__utils__'] = utils
return ret
def beacons(opts, functions, context=None):
'''
Load the beacon modules
:param dict opts: The Salt options dictionary
:param dict functions: A dictionary of minion modules, with module names as
keys and funcs as values.
'''
if context is None:
context = {}
return LazyLoader(_module_dirs(opts, 'beacons', 'beacons'),
opts,
tag='beacons',
pack={'__context__': context,
'__salt__': functions})
def search(opts, returners, whitelist=None):
'''
Returns the search modules
:param dict opts: The Salt options dictionary
:param returners: Undocumented
:param whitelist: Undocumented
'''
# TODO Document returners arg
# TODO Document whitelist arg
return LazyLoader(_module_dirs(opts, 'search', 'search'),
opts,
tag='search',
whitelist=whitelist,
pack={'__ret__': returners})
def log_handlers(opts):
'''
Returns the custom logging handler modules
:param dict opts: The Salt options dictionary
'''
ret = LazyLoader(_module_dirs(opts,
'log_handlers',
'log_handlers',
int_type='handlers',
base_path=os.path.join(SALT_BASE_PATH, 'log')),
opts,
tag='log_handlers',
)
return FilterDictWrapper(ret, '.setup_handlers')
def ssh_wrapper(opts, functions=None, context=None):
'''
Returns the custom logging handler modules
'''
if context is None:
context = {}
if functions is None:
functions = {}
return LazyLoader(_module_dirs(opts,
'wrapper',
'wrapper',
base_path=os.path.join(SALT_BASE_PATH, os.path.join('client', 'ssh'))),
opts,
tag='wrapper',
pack={'__salt__': functions, '__context__': context},
)
def render(opts, functions, states=None):
'''
Returns the render modules
'''
pack = {'__salt__': functions}
if states:
pack['__states__'] = states
ret = LazyLoader(_module_dirs(opts,
'renderers',
'render',
ext_type_dirs='render_dirs'),
opts,
tag='render',
pack=pack,
)
rend = FilterDictWrapper(ret, '.render')
if not check_render_pipe_str(opts['renderer'], rend):
err = ('The renderer {0} is unavailable, this error is often because '
'the needed software is unavailable'.format(opts['renderer']))
log.critical(err)
raise LoaderError(err)
return rend
def grain_funcs(opts):
'''
Returns the grain functions
.. code-block:: python
import salt.config
import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion')
grainfuncs = salt.loader.grain_funcs(__opts__)
'''
return LazyLoader(_module_dirs(opts,
'grains',
'grain',
ext_type_dirs='grains_dirs'),
opts,
tag='grains',
)
def grains(opts, force_refresh=False, proxy=None):
'''
Return the functions for the dynamic grains and the values for the static
grains.
.. code-block:: python
import salt.config
import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion')
__grains__ = salt.loader.grains(__opts__)
print __grains__['id']
'''
# if we hae no grains, lets try loading from disk (TODO: move to decorator?)
if not force_refresh:
if opts.get('grains_cache', False):
cfn = os.path.join(
opts['cachedir'],
'grains.cache.p'
)
if os.path.isfile(cfn):
grains_cache_age = int(time.time() - os.path.getmtime(cfn))
if opts.get('grains_cache_expiration', 300) >= grains_cache_age and not \
opts.get('refresh_grains_cache', False) and not force_refresh:
log.debug('Retrieving grains from cache')
try:
serial = salt.payload.Serial(opts)
with salt.utils.fopen(cfn, 'rb') as fp_:
cached_grains = serial.load(fp_)
return cached_grains
except (IOError, OSError):
pass
else:
if force_refresh:
log.debug('Grains refresh requested. Refreshing grains.')
else:
log.debug('Grains cache last modified {0} seconds ago and '
'cache expiration is set to {1}. '
'Grains cache expired. Refreshing.'.format(
grains_cache_age,
opts.get('grains_cache_expiration', 300)
))
else:
log.debug('Grains cache file does not exist.')
if opts.get('skip_grains', False):
return {}
if 'conf_file' in opts:
pre_opts = {}
pre_opts.update(salt.config.load_config(
opts['conf_file'], 'SALT_MINION_CONFIG',
salt.config.DEFAULT_MINION_OPTS['conf_file']
))
default_include = pre_opts.get(
'default_include', opts['default_include']
)
include = pre_opts.get('include', [])
pre_opts.update(salt.config.include_config(
default_include, opts['conf_file'], verbose=False
))
pre_opts.update(salt.config.include_config(
include, opts['conf_file'], verbose=True
))
if 'grains' in pre_opts:
opts['grains'] = pre_opts['grains']
else:
opts['grains'] = {}
else:
opts['grains'] = {}
grains_data = {}
funcs = grain_funcs(opts)
if force_refresh: # if we refresh, lets reload grain modules
funcs.clear()
# Run core grains
for key, fun in six.iteritems(funcs):
if not key.startswith('core.'):
continue
log.trace('Loading {0} grain'.format(key))
ret = fun()
if not isinstance(ret, dict):
continue
grains_data.update(ret)
# Run the rest of the grains
for key, fun in six.iteritems(funcs):
if key.startswith('core.') or key == '_errors':
continue
try:
ret = fun()
except Exception:
log.critical(
'Failed to load grains defined in grain file {0} in '
'function {1}, error:\n'.format(
key, fun
),
exc_info=True
)
continue
if not isinstance(ret, dict):
continue
grains_data.update(ret)
grains_data.update(opts['grains'])
# Write cache if enabled
if opts.get('grains_cache', False):
cumask = os.umask(0o77)
try:
if salt.utils.is_windows():
# Make sure cache file isn't read-only
__salt__['cmd.run']('attrib -R "{0}"'.format(cfn))
with salt.utils.fopen(cfn, 'w+b') as fp_:
try:
serial = salt.payload.Serial(opts)
serial.dump(grains_data, fp_)
except TypeError:
# Can't serialize pydsl
pass
except (IOError, OSError):
msg = 'Unable to write to grains cache file {0}'
log.error(msg.format(cfn))
os.umask(cumask)
return grains_data
# TODO: get rid of? Does anyone use this? You should use raw() instead
def call(fun, **kwargs):
'''
Directly call a function inside a loader directory
'''
args = kwargs.get('args', [])
dirs = kwargs.get('dirs', [])
funcs = LazyLoader([os.path.join(SALT_BASE_PATH, 'modules')] + dirs,
None,
tag='modules',
virtual_enable=False,
)
return funcs[fun](*args)
def runner(opts):
'''
Directly call a function inside a loader directory
'''
ret = LazyLoader(_module_dirs(opts, 'runners', 'runner', ext_type_dirs='runner_dirs'),
opts,
tag='runners',
)
# TODO: change from __salt__ to something else, we overload __salt__ too much
ret.pack['__salt__'] = ret
return ret
def queues(opts):
'''
Directly call a function inside a loader directory
'''
return LazyLoader(_module_dirs(opts, 'queues', 'queue', ext_type_dirs='queue_dirs'),
opts,
tag='queues',
)
def sdb(opts, functions=None, whitelist=None):
'''
Make a very small database call
'''
return LazyLoader(_module_dirs(opts, 'sdb', 'sdb'),
opts,
tag='sdb',
pack={'__sdb__': functions},
whitelist=whitelist,
)
def pkgdb(opts):
'''
Return modules for SPM's package database
.. versionadded:: 2015.8.0
'''
return LazyLoader(
_module_dirs(
opts,
'pkgdb',
'pkgdb',
base_path=os.path.join(SALT_BASE_PATH, 'spm')
),
opts,
tag='pkgdb'
)
def pkgfiles(opts):
'''
Return modules for SPM's file handling
.. versionadded:: 2015.8.0
'''
return LazyLoader(
_module_dirs(
opts,
'pkgfiles',
'pkgfiles',
base_path=os.path.join(SALT_BASE_PATH, 'spm')
),
opts,
tag='pkgfiles'
)
def clouds(opts):
'''
Return the cloud functions
'''
# Let's bring __active_provider_name__, defaulting to None, to all cloud
# drivers. This will get temporarily updated/overridden with a context
# manager when needed.
functions = LazyLoader(_module_dirs(opts,
'clouds',
'cloud',
base_path=os.path.join(SALT_BASE_PATH, 'cloud'),
int_type='clouds'),
opts,
tag='clouds',
pack={'__active_provider_name__': None},
)
for funcname in LIBCLOUD_FUNCS_NOT_SUPPORTED:
log.trace(
'{0!r} has been marked as not supported. Removing from the list '
'of supported cloud functions'.format(
funcname
)
)
functions.pop(funcname, None)
return functions
def netapi(opts):
'''
Return the network api functions
'''
return LazyLoader(_module_dirs(opts, 'netapi', 'netapi'),
opts,
tag='netapi',
)
def _generate_module(name):
if name in sys.modules:
return
code = "'''Salt loaded {0} parent module'''".format(name.split('.')[-1])
module = imp.new_module(name)
exec(code, module.__dict__)
sys.modules[name] = module
def _mod_type(module_path):
if module_path.startswith(SALT_BASE_PATH):
return 'int'
return 'ext'
# TODO: move somewhere else?
class FilterDictWrapper(MutableMapping):
'''
Create a dict which wraps another dict with a specific key suffix on get
This is to replace "filter_load"
'''
def __init__(self, d, suffix):
self._dict = d
self.suffix = suffix
def __setitem__(self, key, val):
self._dict[key] = val
def __delitem__(self, key):
del self._dict[key]
def __getitem__(self, key):
return self._dict[key + self.suffix]
def __len__(self):
return len(self._dict)
def __iter__(self):
for key in self._dict:
if key.endswith(self.suffix):
yield key.replace(self.suffix, '')
class LazyLoader(salt.utils.lazy.LazyDict):
'''
Goals here:
- lazy loading
- minimize disk usage
# TODO:
- move modules_max_memory into here
- singletons (per tag)
'''
def __init__(self,
module_dirs,
opts=None,
tag='module',
loaded_base_name=None,
mod_type_check=None,
pack=None,
whitelist=None,
virtual_enable=True,
): # pylint: disable=W0231
self.inject_globals = {}
self.opts = self.__prep_mod_opts(opts)
self.module_dirs = module_dirs
if opts is None:
opts = {}
self.tag = tag
self.loaded_base_name = loaded_base_name or LOADED_BASE_NAME
self.mod_type_check = mod_type_check or _mod_type
self.pack = {} if pack is None else pack
if '__context__' not in self.pack:
self.pack['__context__'] = {}
self.whitelist = whitelist
self.virtual_enable = virtual_enable
self.initial_load = True
# names of modules that we don't have (errors, __virtual__, etc.)
self.missing_modules = {} # mapping of name -> error
self.loaded_modules = {} # mapping of module_name -> dict_of_functions
self.loaded_files = set() # TODO: just remove them from file_mapping?
self.disabled = set(self.opts.get('disable_{0}s'.format(self.tag), []))
self.refresh_file_mapping()
super(LazyLoader, self).__init__() # late init the lazy loader
# create all of the import namespaces
_generate_module('{0}.int'.format(self.loaded_base_name))
_generate_module('{0}.int.{1}'.format(self.loaded_base_name, tag))
_generate_module('{0}.ext'.format(self.loaded_base_name))
_generate_module('{0}.ext.{1}'.format(self.loaded_base_name, tag))
def __getitem__(self, item):
'''
Override the __getitem__ in order to decorate the returned function if we need
to last-minute inject globals
'''
func = super(LazyLoader, self).__getitem__(item)
if self.inject_globals:
return global_injector_decorator(self.inject_globals)(func)
else:
return func
def __getattr__(self, mod_name):
'''
Allow for "direct" attribute access-- this allows jinja templates to
access things like `salt.test.ping()`
'''
# if we have an attribute named that, lets return it.
try:
return object.__getattr__(self, mod_name)
except AttributeError:
pass
# otherwise we assume its jinja template access
if mod_name not in self.loaded_modules and not self.loaded:
for name in self._iter_files(mod_name):
if name in self.loaded_files:
continue
# if we got what we wanted, we are done
if self._load_module(name) and mod_name in self.loaded_modules:
break
if mod_name in self.loaded_modules:
return self.loaded_modules[mod_name]
else:
raise AttributeError(mod_name)
def missing_fun_string(self, function_name):
'''
Return the error string for a missing function.
This can range from "not available' to "__virtual__" returned False
'''
mod_name = function_name.split('.')[0]
if mod_name in self.loaded_modules:
return '\'{0}\' is not available.'.format(function_name)
else:
try:
reason = self.missing_modules[mod_name]
except KeyError:
return '\'{0}\' is not available.'.format(function_name)
else:
if reason is not None:
return '\'{0}\' __virtual__ returned False: {1}'.format(mod_name, reason)
else:
return '\'{0}\' __virtual__ returned False'.format(mod_name)
def refresh_file_mapping(self):
'''
refresh the mapping of the FS on disk
'''
# map of suffix to description for imp
self.suffix_map = {}
suffix_order = [] # local list to determine precedence of extensions
for (suffix, mode, kind) in imp.get_suffixes():
self.suffix_map[suffix] = (suffix, mode, kind)
suffix_order.append(suffix)
if self.opts.get('cython_enable', True) is True:
try:
global pyximport
pyximport = __import__('pyximport') # pylint: disable=import-error
pyximport.install()
# add to suffix_map so file_mapping will pick it up
self.suffix_map['.pyx'] = tuple()
except ImportError:
log.info('Cython is enabled in the options but not present '
'in the system path. Skipping Cython modules.')
# Allow for zipimport of modules
if self.opts.get('enable_zip_modules', True) is True:
self.suffix_map['.zip'] = tuple()
# allow for module dirs
self.suffix_map[''] = ('', '', imp.PKG_DIRECTORY)
# create mapping of filename (without suffix) to (path, suffix)
self.file_mapping = {}
for mod_dir in self.module_dirs:
files = []
try:
files = os.listdir(mod_dir)
except OSError:
continue
for filename in files:
try:
if filename.startswith('_'):
# skip private modules
# log messages omitted for obviousness
continue
f_noext, ext = os.path.splitext(filename)
# make sure it is a suffix we support
if ext not in self.suffix_map:
continue
if f_noext in self.disabled:
log.trace(
'Skipping {0}, it is disabled by configuration'.format(
filename
)