summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Admin/Pull.py
blob: 64327e018d85d643a81f5a7c2d2e0b5e30f95b1a (plain)
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
import getopt
import sys

import Bcfg2.Server.Admin
from Bcfg2.Bcfg2Py3k import input


class Pull(Bcfg2.Server.Admin.MetadataCore):
    """Pull mode retrieves entries from clients and
    integrates the information into the repository.
    """
    __shorthelp__ = ("Integrate configuration information "
                     "from clients into the server repository")
    __longhelp__ = (__shorthelp__ + "\n\nbcfg2-admin pull [-v] [-f][-I] [-s] "
                                    "<client> <entry type> <entry name>\n")
    __usage__ = ("bcfg2-admin pull [options] <client> <entry type> "
                 "<entry name>\n\n"
                 "     %-25s%s\n"
                 "     %-25s%s\n"
                 "     %-25s%s\n"
                 "     %-25s%s\n" %
                ("-v",
                 "be verbose",
                 "-f",
                 "force",
                 "-I",
                 "interactive",
                 "-s",
                 "stdin"))
    allowed = ['Metadata', "DBStats", "Statistics", "Cfg", "SSHbase"]

    def __init__(self, setup):
        Bcfg2.Server.Admin.MetadataCore.__init__(self, setup)
        self.log = False
        self.mode = 'interactive'

    def __call__(self, args):
        Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
        use_stdin = False
        try:
            opts, gargs = getopt.getopt(args, 'vfIs')
        except:
            print(self.__shorthelp__)
            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:
            print(self.__longhelp__)
            raise SystemExit(1)
        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, perms, 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,
                    'perms': perms,
                    'text': contents}
        except UnboundLocalError:
            print("Unable to build entry. "
                  "Do you have a statistics plugin enabled?")
            raise SystemExit(1)
        for k, v in list(data.items()):
            if v:
                new_entry[k] = v
        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))

                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)