1
# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""MAAS-specific import fascist.
6
This is designed to stop the unwary from importing modules from where
9
We do this to help separate concerns. For example, ``provisioningserver`` is
10
meant to run on cluster controllers, perhaps using code from ``apiclient``,
11
but it must *not* import from ``maas``, ``maasserver``, or ``metadataserver``
12
because these are solely the preserve of the region controller, and because
13
they contain Django applications which need some extra environmental support
16
See https://docs.python.org/2/library/sys.html#sys.meta_path and
17
https://docs.python.org/2.7/reference/simple_stmts.html#the-import-statement
18
for more information on how this module is able to work.
29
_getframe as getframe,
35
"""Provides the ``find_module`` method.
37
``find_module`` is a Python-specified hook for overriding the normal
38
import mechanisms. Put an instance of this class into ``sys.meta_hook``
39
and Python will consult it when looking to import a module.
41
If ``find_module`` returns an object other than ``None`` it is then used
42
to load the module. However, it's also possible to use this to *prevent*
43
module loading, which is what this class does in cooperation with
47
# module to import: forbidden from
71
def find_rule(self, name):
72
"""Search `rules` for `name`.
74
If `name` isn't found but `name` is a dot-separated name, another
75
search is attempted with the right-most part of the name removed.
76
For example, given `foo.bar.baz` it will search in order::
82
If no name matches, it returns `None`.
84
if name in self.rules:
85
return self.rules[name]
87
name, _, _ = name.rpartition(".")
88
return self.find_rule(name)
92
def is_forbidden(self, forbidden, name):
93
"""Search `forbidden` for `name`.
95
If `name` isn't found but `name` is a dot-separated name, another
96
search is attempted with the right-most part of the name removed.
97
See `find_rule` for details.
102
name, _, _ = name.rpartition(".")
103
return self.is_forbidden(forbidden, name)
107
def find_module(self, fullname, path=None):
108
"""Consult `rules` to see if `fullname` can be loaded.
112
forbidden = self.find_rule(fullname)
113
if forbidden is not None:
114
origin_frame = getframe(1)
115
origin_module = getmodule(origin_frame)
116
if origin_module is not None:
117
origin_module_name = origin_module.__name__
118
if self.is_forbidden(forbidden, origin_module_name):
119
return FascistLoader(origin_module_name)
120
# Good. Out of the door, line on the left, one cross each.
124
"""Install this at the front of `meta_path`.
126
If it's already installed, it is moved to the front.
128
if self in meta_path:
129
meta_path.remove(self)
130
meta_path.insert(0, self)
134
"""Prevent import of the specified module.
136
With a message explaining what has been prevented and why.
139
rules_location = getsourcefile(FascistFinder)
140
if rules_location is None:
141
rules_location = FascistFinder.__name__
143
def __init__(self, whence):
144
super(FascistLoader, self).__init__()
147
def load_module(self, name):
148
"""Raises `ImportError` to prevent loading of `name`."""
150
"Don't import %r from %r. This is MAAS policy. If you "
151
"think this is wrong, amend the rules in %r." % (
152
name, self.whence, self.rules_location))
155
fascist = FascistFinder()