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
|
""" Retrieves entries from clients and integrates the information into
the repository """
import getopt
import sys
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] <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"))
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))
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)
|