""" Retrieves entries from clients and integrates the information into the repository """ import os import sys import getopt import select import Bcfg2.Server.Admin from Bcfg2.Compat import input # pylint: disable=W0622 class Pull(Bcfg2.Server.Admin.MetadataCore): """ Retrieves entries from clients and integrates the information into the repository """ __usage__ = ("[options] \n\n" " %-25s%s\n" " %-25s%s\n" " %-25s%s\n" " %-25s%s\n" % ("-v", "be verbose", "-f", "force", "-I", "interactive", "-s", "stdin")) def __init__(self, setup): Bcfg2.Server.Admin.MetadataCore.__init__(self, setup) self.log = False self.mode = 'interactive' def __call__(self, args): use_stdin = False try: opts, gargs = getopt.getopt(args, 'vfIs') except: print(self.__doc__) raise SystemExit(1) for opt in opts: if opt[0] == '-v': self.log = True elif opt[0] == '-f': self.mode = 'force' elif opt[0] == '-I': self.mode = 'interactive' elif opt[0] == '-s': use_stdin = True if use_stdin: for line in sys.stdin: try: self.PullEntry(*line.split(None, 3)) except SystemExit: print(" for %s" % line) except: print("Bad entry: %s" % line.strip()) elif len(gargs) < 3: self.usage() else: self.PullEntry(gargs[0], gargs[1], gargs[2]) def BuildNewEntry(self, client, etype, ename): """Construct a new full entry for given client/entry from statistics. """ new_entry = {'type': etype, 'name': ename} for plugin in self.bcore.pull_sources: try: (owner, group, mode, contents) = \ plugin.GetCurrentEntry(client, etype, ename) break except Bcfg2.Server.Plugin.PluginExecutionError: if plugin == self.bcore.pull_sources[-1]: print("Pull Source failure; could not fetch current state") raise SystemExit(1) try: data = {'owner': owner, 'group': group, 'mode': mode, 'text': contents} except UnboundLocalError: print("Unable to build entry. " "Do you have a statistics plugin enabled?") raise SystemExit(1) for key, val in list(data.items()): if val: new_entry[key] = val return new_entry def Choose(self, choices): """Determine where to put pull data.""" if self.mode == 'interactive': for choice in choices: print("Plugin returned choice:") if id(choice) == id(choices[0]): print("(current entry) ") if choice.all: print(" => global entry") elif choice.group: print(" => group entry: %s (prio %d)" % (choice.group, choice.prio)) else: print(" => host entry: %s" % (choice.hostname)) # flush input buffer while len(select.select([sys.stdin.fileno()], [], [], 0.0)[0]) > 0: os.read(sys.stdin.fileno(), 4096) ans = input("Use this entry? [yN]: ") in ['y', 'Y'] if ans: return choice return False else: # mode == 'force' if not choices: return False return choices[0] def PullEntry(self, client, etype, ename): """Make currently recorded client state correct for entry.""" new_entry = self.BuildNewEntry(client, etype, ename) meta = self.bcore.build_metadata(client) # Find appropriate plugin in bcore glist = [gen for gen in self.bcore.generators if ename in gen.Entries.get(etype, {})] if len(glist) != 1: self.errExit("Got wrong numbers of matching generators for entry:" "%s" % ([g.name for g in glist])) plugin = glist[0] if not isinstance(plugin, Bcfg2.Server.Plugin.PullTarget): self.errExit("Configuration upload not supported by plugin %s" % plugin.name) try: choices = plugin.AcceptChoices(new_entry, meta) specific = self.Choose(choices) if specific: plugin.AcceptPullData(specific, new_entry, self.log) except Bcfg2.Server.Plugin.PluginExecutionError: self.errExit("Configuration upload not supported by plugin %s" % plugin.name) # Commit if running under a VCS for vcsplugin in list(self.bcore.plugins.values()): if isinstance(vcsplugin, Bcfg2.Server.Plugin.Version): files = "%s/%s" % (plugin.data, ename) comment = 'file "%s" pulled from host %s' % (files, client) vcsplugin.commit_data([files], comment)