14
14
# See the License for the specific language governing permissions and
15
15
# limitations under the License.
16
16
17
+ import os
17
18
import re
18
19
19
20
20
- class VarGraph (object ):
21
+ class DependencyGraph (object ):
21
22
# This is based on the JobGraph from Zuul.
22
23
24
+ def __init__ (self ):
25
+ self ._names = set ()
26
+ self ._dependencies = {} # dependent_name -> set(parent_names)
27
+
28
+ def add (self , name , dependencies ):
29
+ # Append the dependency information
30
+ self ._dependencies .setdefault (name , set ())
31
+ try :
32
+ for dependency in dependencies :
33
+ # Make sure a circular dependency is never created
34
+ ancestors = self ._getParentNamesRecursively (
35
+ dependency , soft = True )
36
+ ancestors .add (dependency )
37
+ if name in ancestors :
38
+ raise Exception ("Dependency cycle detected in {}" .
39
+ format (name ))
40
+ self ._dependencies [name ].add (dependency )
41
+ except Exception :
42
+ del self ._dependencies [name ]
43
+ raise
44
+
45
+ def getDependenciesRecursively (self , parent ):
46
+ dependencies = []
47
+
48
+ current_dependencies = self ._dependencies [parent ]
49
+ for current in current_dependencies :
50
+ if current not in dependencies :
51
+ dependencies .append (current )
52
+ for dep in self .getDependenciesRecursively (current ):
53
+ if dep not in dependencies :
54
+ dependencies .append (dep )
55
+ return dependencies
56
+
57
+ def _getParentNamesRecursively (self , dependent , soft = False ):
58
+ all_parent_items = set ()
59
+ items_to_iterate = set ([dependent ])
60
+ while len (items_to_iterate ) > 0 :
61
+ current_item = items_to_iterate .pop ()
62
+ current_parent_items = self ._dependencies .get (current_item )
63
+ if current_parent_items is None :
64
+ if soft :
65
+ current_parent_items = set ()
66
+ else :
67
+ raise Exception ("Dependent item {} not found: " .format (
68
+ dependent ))
69
+ new_parent_items = current_parent_items - all_parent_items
70
+ items_to_iterate |= new_parent_items
71
+ all_parent_items |= new_parent_items
72
+ return all_parent_items
73
+
74
+
75
+ class VarGraph (DependencyGraph ):
23
76
def __init__ (self , vars ):
77
+ super (VarGraph , self ).__init__ ()
24
78
self .vars = {}
25
79
self ._varnames = set ()
26
- self ._dependencies = {} # dependent_var_name -> set(parent_var_names)
27
80
for k , v in vars .items ():
28
81
self ._varnames .add (k )
29
82
for k , v in vars .items ():
@@ -38,77 +91,127 @@ def _addVar(self, key, value):
38
91
raise Exception ("Variable {} already added" .format (key ))
39
92
self .vars [key ] = value
40
93
# Append the dependency information
41
- self ._dependencies .setdefault (key , set ())
94
+ dependencies = set ()
95
+ for dependency in self .getDependencies (value ):
96
+ if dependency == key :
97
+ # A variable is allowed to reference itself; no
98
+ # dependency link needed in that case.
99
+ continue
100
+ if dependency not in self ._varnames :
101
+ # It's not necessary to create a link for an
102
+ # external variable.
103
+ continue
104
+ dependencies .add (dependency )
42
105
try :
43
- for dependency in self .getDependencies (value ):
44
- if dependency == key :
45
- # A variable is allowed to reference itself; no
46
- # dependency link needed in that case.
47
- continue
48
- if dependency not in self ._varnames :
49
- # It's not necessary to create a link for an
50
- # external variable.
51
- continue
52
- # Make sure a circular dependency is never created
53
- ancestor_vars = self ._getParentVarNamesRecursively (
54
- dependency , soft = True )
55
- ancestor_vars .add (dependency )
56
- if any ((key == anc_var ) for anc_var in ancestor_vars ):
57
- raise Exception ("Dependency cycle detected in var {}" .
58
- format (key ))
59
- self ._dependencies [key ].add (dependency )
106
+ self .add (key , dependencies )
60
107
except Exception :
61
108
del self .vars [key ]
62
- del self ._dependencies [key ]
63
109
raise
64
110
65
111
def getVars (self ):
66
112
ret = []
67
113
keys = sorted (self .vars .keys ())
68
114
seen = set ()
69
115
for key in keys :
70
- dependencies = self .getDependentVarsRecursively (key )
116
+ dependencies = self .getDependenciesRecursively (key )
71
117
for var in dependencies + [key ]:
72
118
if var not in seen :
73
119
ret .append ((var , self .vars [var ]))
74
120
seen .add (var )
75
121
return ret
76
122
77
- def getDependentVarsRecursively (self , parent_var ):
78
- dependent_vars = []
79
-
80
- current_dependent_vars = self ._dependencies [parent_var ]
81
- for current_var in current_dependent_vars :
82
- if current_var not in dependent_vars :
83
- dependent_vars .append (current_var )
84
- for dep in self .getDependentVarsRecursively (current_var ):
85
- if dep not in dependent_vars :
86
- dependent_vars .append (dep )
87
- return dependent_vars
88
-
89
- def _getParentVarNamesRecursively (self , dependent_var , soft = False ):
90
- all_parent_vars = set ()
91
- vars_to_iterate = set ([dependent_var ])
92
- while len (vars_to_iterate ) > 0 :
93
- current_var = vars_to_iterate .pop ()
94
- current_parent_vars = self ._dependencies .get (current_var )
95
- if current_parent_vars is None :
96
- if soft :
97
- current_parent_vars = set ()
98
- else :
99
- raise Exception ("Dependent var {} not found: " .format (
100
- dependent_var ))
101
- new_parent_vars = current_parent_vars - all_parent_vars
102
- vars_to_iterate |= new_parent_vars
103
- all_parent_vars |= new_parent_vars
104
- return all_parent_vars
123
+
124
+ class PluginGraph (DependencyGraph ):
125
+ def __init__ (self , base_dir , plugins ):
126
+ super (PluginGraph , self ).__init__ ()
127
+ # The dependency trees expressed by all the plugins we found
128
+ # (which may be more than those the job is using).
129
+ self ._plugin_dependencies = {}
130
+ self .loadPluginNames (base_dir )
131
+
132
+ self .plugins = {}
133
+ self ._pluginnames = set ()
134
+ for k , v in plugins .items ():
135
+ self ._pluginnames .add (k )
136
+ for k , v in plugins .items ():
137
+ self ._addPlugin (k , str (v ))
138
+
139
+ def loadPluginNames (self , base_dir ):
140
+ if base_dir is None :
141
+ return
142
+ git_roots = []
143
+ for root , dirs , files in os .walk (base_dir ):
144
+ if '.git' not in dirs :
145
+ continue
146
+ # Don't go deeper than git roots
147
+ dirs [:] = []
148
+ git_roots .append (root )
149
+ for root in git_roots :
150
+ devstack = os .path .join (root , 'devstack' )
151
+ if not (os .path .exists (devstack ) and os .path .isdir (devstack )):
152
+ continue
153
+ settings = os .path .join (devstack , 'settings' )
154
+ if not (os .path .exists (settings ) and os .path .isfile (settings )):
155
+ continue
156
+ self .loadDevstackPluginInfo (settings )
157
+
158
+ define_re = re .compile (r'^define_plugin\s+(\w+).*' )
159
+ require_re = re .compile (r'^plugin_requires\s+(\w+)\s+(\w+).*' )
160
+ def loadDevstackPluginInfo (self , fn ):
161
+ name = None
162
+ reqs = set ()
163
+ with open (fn ) as f :
164
+ for line in f :
165
+ m = self .define_re .match (line )
166
+ if m :
167
+ name = m .group (1 )
168
+ m = self .require_re .match (line )
169
+ if m :
170
+ if name == m .group (1 ):
171
+ reqs .add (m .group (2 ))
172
+ if name and reqs :
173
+ self ._plugin_dependencies [name ] = reqs
174
+
175
+ def getDependencies (self , value ):
176
+ return self ._plugin_dependencies .get (value , [])
177
+
178
+ def _addPlugin (self , key , value ):
179
+ if key in self .plugins :
180
+ raise Exception ("Plugin {} already added" .format (key ))
181
+ self .plugins [key ] = value
182
+ # Append the dependency information
183
+ dependencies = set ()
184
+ for dependency in self .getDependencies (key ):
185
+ if dependency == key :
186
+ continue
187
+ dependencies .add (dependency )
188
+ try :
189
+ self .add (key , dependencies )
190
+ except Exception :
191
+ del self .plugins [key ]
192
+ raise
193
+
194
+ def getPlugins (self ):
195
+ ret = []
196
+ keys = sorted (self .plugins .keys ())
197
+ seen = set ()
198
+ for key in keys :
199
+ dependencies = self .getDependenciesRecursively (key )
200
+ for plugin in dependencies + [key ]:
201
+ if plugin not in seen :
202
+ ret .append ((plugin , self .plugins [plugin ]))
203
+ seen .add (plugin )
204
+ return ret
105
205
106
206
107
207
class LocalConf (object ):
108
208
109
- def __init__ (self , localrc , localconf , base_services , services , plugins ):
209
+ def __init__ (self , localrc , localconf , base_services , services , plugins ,
210
+ base_dir ):
110
211
self .localrc = []
111
212
self .meta_sections = {}
213
+ self .plugin_deps = {}
214
+ self .base_dir = base_dir
112
215
if plugins :
113
216
self .handle_plugins (plugins )
114
217
if services or base_services :
@@ -119,7 +222,8 @@ def __init__(self, localrc, localconf, base_services, services, plugins):
119
222
self .handle_localconf (localconf )
120
223
121
224
def handle_plugins (self , plugins ):
122
- for k , v in plugins .items ():
225
+ pg = PluginGraph (self .base_dir , plugins )
226
+ for k , v in pg .getPlugins ():
123
227
if v :
124
228
self .localrc .append ('enable_plugin {} {}' .format (k , v ))
125
229
@@ -171,6 +275,7 @@ def main():
171
275
services = dict (type = 'dict' ),
172
276
localrc = dict (type = 'dict' ),
173
277
local_conf = dict (type = 'dict' ),
278
+ base_dir = dict (type = 'path' ),
174
279
path = dict (type = 'str' ),
175
280
)
176
281
)
@@ -180,14 +285,18 @@ def main():
180
285
p .get ('local_conf' ),
181
286
p .get ('base_services' ),
182
287
p .get ('services' ),
183
- p .get ('plugins' ))
288
+ p .get ('plugins' ),
289
+ p .get ('base_dir' ))
184
290
lc .write (p ['path' ])
185
291
186
292
module .exit_json ()
187
293
188
294
189
- from ansible .module_utils .basic import * # noqa
190
- from ansible .module_utils .basic import AnsibleModule
295
+ try :
296
+ from ansible .module_utils .basic import * # noqa
297
+ from ansible .module_utils .basic import AnsibleModule
298
+ except ImportError :
299
+ pass
191
300
192
301
if __name__ == '__main__' :
193
302
main ()
0 commit comments