# Copyright (C) 2017 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # # See the License for the specific language governing permissions and # limitations under the License. import re class VarGraph(object): # This is based on the JobGraph from Zuul. def __init__(self, vars): self.vars = {} self._varnames = set() self._dependencies = {} # dependent_var_name -> set(parent_var_names) for k, v in vars.items(): self._varnames.add(k) for k, v in vars.items(): self._addVar(k, v) bash_var_re = re.compile(r'\$\{?(\w+)') def getDependencies(self, value): return self.bash_var_re.findall(value) def _addVar(self, key, value): if key in self.vars: raise Exception("Variable {} already added".format(key)) self.vars[key] = value # Append the dependency information self._dependencies.setdefault(key, set()) try: for dependency in self.getDependencies(value): if dependency == key: # A variable is allowed to reference itself; no # dependency link needed in that case. continue if dependency not in self._varnames: # It's not necessary to create a link for an # external variable. continue # Make sure a circular dependency is never created ancestor_vars = self._getParentVarNamesRecursively( dependency, soft=True) ancestor_vars.add(dependency) if any((key == anc_var) for anc_var in ancestor_vars): raise Exception("Dependency cycle detected in var {}". format(key)) self._dependencies[key].add(dependency) except Exception: del self.vars[key] del self._dependencies[key] raise def getVars(self): ret = [] keys = sorted(self.vars.keys()) seen = set() for key in keys: dependencies = self.getDependentVarsRecursively(key) for var in dependencies + [key]: if var not in seen: ret.append((var, self.vars[var])) seen.add(var) return ret def getDependentVarsRecursively(self, parent_var): dependent_vars = [] current_dependent_vars = self._dependencies[parent_var] for current_var in current_dependent_vars: if current_var not in dependent_vars: dependent_vars.append(current_var) for dep in self.getDependentVarsRecursively(current_var): if dep not in dependent_vars: dependent_vars.append(dep) return dependent_vars def _getParentVarNamesRecursively(self, dependent_var, soft=False): all_parent_vars = set() vars_to_iterate = set([dependent_var]) while len(vars_to_iterate) > 0: current_var = vars_to_iterate.pop() current_parent_vars = self._dependencies.get(current_var) if current_parent_vars is None: if soft: current_parent_vars = set() else: raise Exception("Dependent var {} not found: ".format( dependent_var)) new_parent_vars = current_parent_vars - all_parent_vars vars_to_iterate |= new_parent_vars all_parent_vars |= new_parent_vars return all_parent_vars class LocalConf(object): def __init__(self, localrc, localconf, services, plugins): self.localrc = [] self.meta_sections = {} if plugins: self.handle_plugins(plugins) if services: self.handle_services(services) if localrc: self.handle_localrc(localrc) if localconf: self.handle_localconf(localconf) def handle_plugins(self, plugins): for k, v in plugins.items(): if v: self.localrc.append('enable_plugin {} {}'.format(k, v)) def handle_services(self, services): for k, v in services.items(): if v is False: self.localrc.append('disable_service {}'.format(k)) elif v is True: self.localrc.append('enable_service {}'.format(k)) def handle_localrc(self, localrc): vg = VarGraph(localrc) for k, v in vg.getVars(): self.localrc.append('{}={}'.format(k, v)) def handle_localconf(self, localconf): for phase, phase_data in localconf.items(): for fn, fn_data in phase_data.items(): ms_name = '[[{}|{}]]'.format(phase, fn) ms_data = [] for section, section_data in fn_data.items(): ms_data.append('[{}]'.format(section)) for k, v in section_data.items(): ms_data.append('{} = {}'.format(k, v)) ms_data.append('') self.meta_sections[ms_name] = ms_data def write(self, path): with open(path, 'w') as f: f.write('[[local|localrc]]\n') f.write('\n'.join(self.localrc)) f.write('\n\n') for section, lines in self.meta_sections.items(): f.write('{}\n'.format(section)) f.write('\n'.join(lines)) def main(): module = AnsibleModule( argument_spec=dict( plugins=dict(type='dict'), services=dict(type='dict'), localrc=dict(type='dict'), local_conf=dict(type='dict'), path=dict(type='str'), ) ) p = module.params lc = LocalConf(p.get('localrc'), p.get('local_conf'), p.get('services'), p.get('plugins')) lc.write(p['path']) module.exit_json() from ansible.module_utils.basic import * # noqa from ansible.module_utils.basic import AnsibleModule if __name__ == '__main__': main()