1
'''This contains all Bcfg2 Tool modules'''
2
__revision__ = '$Revision: 2435 $'
4
__all__ = ["APT", "Blast", "Chkconfig", "DebInit", "Encap", "PostInstall",
5
"POSIX", "RPM", "SMF", "SYSV"]
7
import os, popen2, stat, sys, Bcfg2.Client.XML
9
class toolInstantiationError(Exception):
10
'''This error is called if the toolset cannot be instantiated'''
13
class readonlypipe(popen2.Popen4):
14
'''This pipe sets up stdin --> /dev/null'''
15
def __init__(self, cmd, bufsize=-1):
17
c2pread, c2pwrite = os.pipe()
18
null = open('/dev/null', 'w+')
22
os.dup2(null.fileno(), sys.__stdin__.fileno())
28
self.fromchild = os.fdopen(c2pread, 'r', bufsize)
29
popen2._active.append(self)
32
'''this class runs stuff for us'''
33
def __init__(self, logger):
36
def run(self, command):
37
'''Run a command in a pipe dealing with stdout buffer overloads'''
38
self.logger.debug('> %s' % command)
40
runpipe = readonlypipe(command, bufsize=16384)
44
runpipe.fromchild.flush()
45
moreOutput = runpipe.fromchild.read()
46
if len(moreOutput) > 0:
47
self.logger.debug('< %s' % moreOutput)
49
cmdstat = runpipe.poll()
51
return (cmdstat, [line for line in output.split('\n') if line])
54
'''All tools subclass this. It defines all interfaces that need to be defined'''
61
def __init__(self, logger, setup, config, states):
64
if not hasattr(self, '__ireq__'):
65
self.__ireq__ = self.__req__
67
self.cmd = executor(logger)
71
self.handled = [entry for struct in self.config for entry in struct \
72
if self.handlesEntry(entry)]
73
for filename in self.__execs__:
75
mode = stat.S_IMODE(os.stat(filename)[stat.ST_MODE])
76
if mode & stat.S_IEXEC != stat.S_IEXEC:
77
self.logger.debug("%s: %s not executable" % \
78
(self.__name__, filename))
79
raise toolInstantiationError
81
raise toolInstantiationError
83
self.logger.debug("%s failed" % filename, exc_info=1)
84
raise toolInstantiationError
86
def BundleUpdated(self, _):
87
'''This callback is used when bundle updates occur'''
90
def Inventory(self, structures=[]):
91
'''Dispatch verify calls to underlying methods'''
93
structures = self.config.getchildren()
94
for (struct, entry) in [(struct, entry) for struct in structures \
95
for entry in struct.getchildren() \
96
if self.canVerify(entry)]:
98
func = getattr(self, "Verify%s" % (entry.tag))
99
self.states[entry] = func(entry, self.buildModlist(entry, struct))
102
"Unexpected failure of verification method for entry type %s" \
103
% (entry.tag), exc_info=1)
104
self.extra = self.FindExtra()
106
def Install(self, entries):
107
'''Install all entries in sublist'''
108
for entry in entries:
110
func = getattr(self, "Install%s" % (entry.tag))
111
self.states[entry] = func(entry)
112
self.modified.append(entry)
114
self.logger.error("Unexpected failure of install method for entry type %s" \
115
% (entry.tag), exc_info=1)
117
def Remove(self, entries):
118
'''Remove specified extra entries'''
121
def getSupportedEntries(self):
122
'''return a list of supported entries'''
123
return [entry for struct in self.config.getchildren() for entry in struct.getchildren() \
124
if self.handlesEntry(entry)]
126
def handlesEntry(self, entry):
127
'''return if entry is handled by this Tool'''
128
return (entry.tag, entry.get('type')) in self.__handles__
130
def buildModlist(self, entry, struct):
131
'''Build a list of potentially modified POSIX paths for this entry'''
132
if entry.tag != 'Package' or struct.tag != 'Bundle':
134
return [sentry.get('name') for sentry in struct if sentry.tag in \
135
['ConfigFile', 'SymLink', 'Directory']]
137
def canVerify(self, entry):
138
'''test if entry has enough information to be verified'''
139
if not self.handlesEntry(entry):
142
if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]:
143
self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
144
% (entry.tag, entry.get('name')))
150
'''Return a list of extra entries'''
153
def canInstall(self, entry):
154
'''test if entry has enough information to be installed'''
155
if not self.handlesEntry(entry):
157
if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]:
158
self.logger.error("Incomplete information for entry %s:%s; cannot install" \
159
% (entry.tag, entry.get('name')))
164
'''PkgTool provides a one-pass install with fallback for use with packaging systems'''
165
pkgtool = ('echo %s', ('%s', ['name']))
169
def __init__(self, logger, setup, config, states):
170
Tool.__init__(self, logger, setup, config, states)
172
self.Remove = self.RemovePackages
173
self.FindExtra = self.FindExtraPackages
174
self.RefreshPackages()
176
def VerifyPackage(self, dummy, _):
177
'''Dummy verification method'''
180
def Install(self, packages):
181
'''Run a one-pass install, followed by single pkg installs in case of failure'''
182
self.logger.info("Trying single pass package install for pkgtype %s" % \
185
data = [tuple([pkg.get(field) for field in self.pkgtool[1][1]]) for pkg in packages]
186
pkgargs = " ".join([self.pkgtool[1][0] % datum for datum in data])
188
self.logger.debug("Installing packages: :%s:" % pkgargs)
189
self.logger.debug("Running command ::%s::" % (self.pkgtool[0] % pkgargs))
191
cmdrc = self.cmd.run(self.pkgtool[0] % pkgargs)[0]
193
self.logger.info("Single Pass Succeded")
194
# set all package states to true and flush workqueues
195
pkgnames = [pkg.get('name') for pkg in packages]
196
for entry in [entry for entry in self.states.keys()
197
if entry.tag == 'Package' and entry.get('type') == self.pkgtype
198
and entry.get('name') in pkgnames]:
199
self.logger.debug('Setting state to true for pkg %s' % (entry.get('name')))
200
self.states[entry] = True
201
self.RefreshPackages()
203
self.logger.error("Single Pass Failed")
204
# do single pass installs
205
self.RefreshPackages()
207
# handle state tracking updates
208
if self.VerifyPackage(pkg, []):
209
self.logger.info("Forcing state to true for pkg %s" % (pkg.get('name')))
210
self.states[pkg] = True
212
self.logger.info("Installing pkg %s version %s" %
213
(pkg.get('name'), pkg.get('version')))
214
cmdrc = self.cmd.run(self.pkgtool[0] %
215
(self.pkgtool[1][0] %
216
tuple([pkg.get(field) for field in self.pkgtool[1][1]])))
218
self.states[pkg] = True
220
self.logger.error("Failed to install package %s" % (pkg.get('name')))
221
for entry in [ent for ent in packages if self.states[ent]]:
222
self.modified.append(entry)
224
def RefreshPackages(self):
225
'''Dummy state refresh method'''
228
def RemovePackages(self, packages):
229
'''Dummy implementation of package removal method'''
232
def FindExtraPackages(self):
233
'''Find extra packages'''
234
packages = [entry.get('name') for entry in self.getSupportedEntries()]
235
extras = [key for key in self.installed if key not in packages]
236
return [Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) \
240
'''This class defines basic Service behavior'''
243
def BundleUpdated(self, bundle):
244
'''The Bundle has been updated'''
246
if self.handlesEntry(entry):
247
if entry.get('status') == 'on':
248
self.logger.debug('Restarting service %s' % entry.get('name'))
249
self.cmd.run('/etc/init.d/%s %s' % \
250
(entry.get('name'), entry.get('reload', 'reload')))
252
self.logger.debug('Stopping service %s' % entry.get('name'))
253
self.cmd.run('/etc/init.d/%s stop' % (entry.get('name')))