diff options
-rw-r--r-- | schemas/bundle.xsd | 4 | ||||
-rw-r--r-- | schemas/rules.xsd | 12 | ||||
-rw-r--r-- | schemas/types.xsd | 26 | ||||
-rw-r--r-- | src/lib/Client/Frame.py | 77 | ||||
-rw-r--r-- | src/lib/Client/Tools/Action.py | 52 | ||||
-rw-r--r-- | src/lib/Client/Tools/__init__.py | 4 |
6 files changed, 142 insertions, 33 deletions
diff --git a/schemas/bundle.xsd b/schemas/bundle.xsd index 1890108c8..cdbc5f900 100644 --- a/schemas/bundle.xsd +++ b/schemas/bundle.xsd @@ -18,6 +18,7 @@ <xsd:element name='SymLink' type='StructureEntry'/> <xsd:element name='Permissions' type='StructureEntry'/> <xsd:element name='PostInstall' type='StructureEntry'/> + <xsd:element name='Action' type='StructureEntry'/> <xsd:element name='Group' type='GroupType'/> </xsd:choice> <xsd:attribute type='xsd:string' name='name' use='required'/> @@ -34,6 +35,7 @@ <xsd:element name='SymLink' type='StructureEntry'/> <xsd:element name='Permission' type='StructureEntry'/> <xsd:element name='PostInstall' type='StructureEntry'/> + <xsd:element name='Action' type='StructureEntry'/> <xsd:element name='Group' type='GroupType'/> </xsd:choice> <xsd:attribute type='xsd:string' name='name'/> @@ -42,4 +44,4 @@ <xsd:attribute type='xsd:string' name='revision'/> </xsd:complexType> </xsd:element> -</xsd:schema>
\ No newline at end of file +</xsd:schema> diff --git a/schemas/rules.xsd b/schemas/rules.xsd index 270b08828..82ed4aaa0 100644 --- a/schemas/rules.xsd +++ b/schemas/rules.xsd @@ -29,6 +29,14 @@ <xsd:attribute type='xsd:string' name='group'/> </xsd:complexType> + <xsd:complexType name='ActionType'> + <xsd:attribute type='ActionTimingEnum' name='timing' use='required'/> + <xsd:attribute type='ActionWhenEnum' name='when' use='required'/> + <xsd:attribute type='ActionStatusEnum' name='status' use='required'/> + <xsd:attribute type='xsd:string' name='name' use='required'/> + <xsd:attribute type='xsd:string' name='command' use='required'/> + </xsd:complexType> + <xsd:complexType name='SymLinkType'> <xsd:attribute type='xsd:string' name='name' use='required'/> <xsd:attribute type='xsd:string' name='to' use='required'/> @@ -48,6 +56,7 @@ <xsd:element name='SymLink' type='SymLinkType'/> <xsd:element name='Package' type='PackageType'/> <xsd:element name='Permissions' type='PermissionsType'/> + <xsd:element name='Action' type='ActionType'/> <xsd:element name='Group' type='RContainerType'/> <xsd:element name='Client' type='RContainerType'/> </xsd:choice> @@ -64,10 +73,11 @@ <xsd:element name='SymLink' type='SymLinkType'/> <xsd:element name='Package' type='PackageType'/> <xsd:element name='Permissions' type='PermissionsType'/> + <xsd:element name='Action' type='ActionType'/> <xsd:element name='Group' type='RContainerType'/> <xsd:element name='Client' type='RContainerType'/> </xsd:choice> <xsd:attribute name='priority' type='xsd:integer' use='required'/> </xsd:complexType> </xsd:element> -</xsd:schema>
\ No newline at end of file +</xsd:schema> diff --git a/schemas/types.xsd b/schemas/types.xsd index b9d7131dd..f722309f8 100644 --- a/schemas/types.xsd +++ b/schemas/types.xsd @@ -8,7 +8,7 @@ </xsd:documentation> </xsd:annotation> - <xsd:simpleType name='PackageTypeEnum'> + <xsd:simpleType name='PackageTypeEnum'> <xsd:restriction base='xsd:string'> <xsd:enumeration value='deb' /> <xsd:enumeration value='rpm' /> @@ -25,4 +25,26 @@ </xsd:restriction> </xsd:simpleType> -</xsd:schema>
\ No newline at end of file + <xsd:simpleType name='ActionTimingEnum'> + <xsd:restriction base='xsd:string'> + <xsd:enumeration value='both'/> + <xsd:enumeration value='pre'/> + <xsd:enumeration value='post'/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name='ActionWhenEnum'> + <xsd:restriction base='xsd:string'> + <xsd:enumeration value='modified'/> + <xsd:enumeration value='always'/> + </xsd:restriction> + </xsd:simpleType> + + <xsd:simpleType name='ActionStatusEnum'> + <xsd:restriction base='xsd:string'> + <xsd:enumeration value='ignore'/> + <xsd:enumeration value='check'/> + </xsd:restriction> + </xsd:simpleType> + +</xsd:schema> diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py index 38b65f8e8..9b3d482c4 100644 --- a/src/lib/Client/Frame.py +++ b/src/lib/Client/Frame.py @@ -43,6 +43,7 @@ class Frame: self.tools = [] self.states = {} self.whitelist = [] + self.blacklist = [] self.removal = [] self.logger = logging.getLogger("Bcfg2.Client.Frame") if self.setup['drivers']: @@ -137,6 +138,7 @@ class Frame: elif self.setup['remove'] == 'packages': self.removal = [entry for entry in self.extra if entry.tag == 'Package'] + candidates = [entry for entry in self.states if not self.states[entry]] if self.setup['dryrun']: updated = [entry for entry in self.states if not self.states[entry]] if updated: @@ -150,21 +152,41 @@ class Frame: self.removal = [] return elif self.setup['interactive']: - self.whitelist = promptFilter(prompt, [entry for entry in self.states \ - if not self.states[entry]]) + self.whitelist = promptFilter(prompt, candidates) + self.blacklist = [c for c in candidates if c not in self.whitelist] self.removal = promptFilter(rprompt, self.removal) - elif self.setup['bundle']: - # only install entries in specified bundle - mbs = [bund for bund in self.config.findall('./Bundle') \ - if bund.get('name') == self.setup['bundle']] - if not mbs: - self.logger.error("Could not find bundle %s" % (self.setup['bundle'])) - return - self.whitelist = [entry for entry in self.states if not self.states[entry] \ - and entry in mbs[0].getchildren()] else: - # all systems are go - self.whitelist = [entry for entry in self.states if not self.states[entry]] + # need to do bundle and pre-action checks + if self.setup['bundle']: + bundles = [b for b in self.config.findall('./Bundle') \ + if b.get('name') == self.setup['bundle']] + else: + bundles = self.config.findall('./Bundle') + gbundles = [] + for bundle in bundles: + actions = [a for a in bundle.findall('./Action') \ + if a.get('timing') != 'post'] + # run all actions if modified or always + for action in actions: + if action.get('when') == 'always': + self.DispatchInstallCalls([action]) + else: + # when == modified + # check if bundle should be modified + if [c for c in candidates if c in bundle]: + self.DispatchInstallCalls([action]) + if False in [self.states[a] for a in actions]: + self.logger.info("Bundle %s failed prerequisite action" % \ + (bundle.get('name'))) + continue + else: + gbundles.append(bundle) + + for entry in candidates: + if [bundle for bundle in gbundles if entry in bundle]: + self.whitelist.append(entry) + else: + self.blacklist.append(entry) def DispatchInstallCalls(self, entries): '''Dispatch install calls to underlying tools''' @@ -180,11 +202,12 @@ class Frame: def Install(self): '''Install all entries''' self.DispatchInstallCalls(self.whitelist) + mods = self.modified + mbundles = [struct for struct in self.config.findall('Bundle') if \ + [mod for mod in mods if mod in struct]] + if self.modified: # Handle Bundle interdeps - mods = self.modified - mbundles = [struct for struct in self.config.findall('Bundle') if \ - [mod for mod in mods if mod in struct]] if mbundles: self.logger.info("The Following Bundles have been modifed:") self.logger.info([mbun.get('name') for mbun in mbundles]) @@ -196,7 +219,7 @@ class Frame: except: self.logger.error("%s.Inventory() call failed:" % tool.__name__, exc_info=1) clobbered = [entry for bundle in mbundles for entry in bundle \ - if not self.states[entry]] + if not self.states[entry] and entry not in self.blacklist] if clobbered: self.logger.debug("Found clobbered entries:") self.logger.debug(["%s:%s" % (entry.tag, entry.get('name')) \ @@ -204,16 +227,16 @@ class Frame: if not self.setup['interactive']: self.DispatchInstallCalls(clobbered) - for bundle in self.config.findall('.//Bundle'): - for tool in self.tools: - try: - if bundle in mbundles: - tool.BundleUpdated(bundle) - else: - tool.BundleNotUpdated(bundle) - except: - self.logger.error("%s.BundleNotUpdated() call failed:" % \ - (tool.__name__), exc_info=1) + for bundle in self.config.findall('.//Bundle'): + for tool in self.tools: + try: + if bundle in mbundles: + tool.BundleUpdated(bundle) + else: + tool.BundleNotUpdated(bundle) + except: + self.logger.error("%s.BundleNotUpdated() call failed:" % \ + (tool.__name__), exc_info=1) def Remove(self): '''Remove extra entries''' diff --git a/src/lib/Client/Tools/Action.py b/src/lib/Client/Tools/Action.py new file mode 100644 index 000000000..ddd15a264 --- /dev/null +++ b/src/lib/Client/Tools/Action.py @@ -0,0 +1,52 @@ +'''Action driver''' +__revision__ = '$Revision$' + +import Bcfg2.Client.Tools + +# <Action timing='pre|post|both' name='name' command='cmd text' when='always|modified' +# status='ignore|check'/> +# <PostInstall name='foo'/> +# => <Action timing='post' when='modified' name='n' command='foo' status='ignore'/> + +class Action(Bcfg2.Client.Tools.Tool): + '''Implement Actions''' + __name__ = 'Action' + __handles__ = [('PostInstall', None), ('Action', None)] + __req__ = {'PostInstall': ['name'], + 'Action':['name', 'timing', 'when', 'command', 'status']} + + def RunAction(self, entry): + '''This method handles command execution and status return''' + self.logger.debug("Running Action %s" % (entry.get('name'))) + rc = self.cmd.run(entry.get('command'))[0] + self.logger.debug("Action: %s got rc %s" % (entry.get('command'), rc)) + if entry.get('status', 'check') == 'ignore': + return True + else: + return rc == 0 + + def VerifyAction(self, dummy, _): + '''Actions always verify true''' + return True + + def InstallAction(self, entry): + '''Run actions as pre-checks for bundle installation''' + if entry.get('timing') != 'post': + self.states[entry] = self.RunAction(entry) + return self.states[entry] + return True + + def BundleUpdated(self, bundle): + '''Run postinstalls when bundles have been updated''' + for postinst in bundle.findall("PostInstall"): + self.cmd.run(postinst.get('name')) + for action in bundle.findall("Action"): + if action.get('timing') in ['post', 'both']: + self.states[action] = self.RunAction(action) + + def BundleNotUpdated(self, bundle): + '''Run Actions when bundles have not been updated''' + for action in bundle.findall("Action"): + if action.get('timing') in ['post', 'both'] and \ + action.get('when') != 'modified': + self.states[action] = self.RunAction(action) diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py index 142084792..90d3cedbc 100644 --- a/src/lib/Client/Tools/__init__.py +++ b/src/lib/Client/Tools/__init__.py @@ -1,8 +1,8 @@ '''This contains all Bcfg2 Tool modules''' __revision__ = '$Revision$' -__all__ = ["APT", "Blast", "Chkconfig", "DebInit", "Encap", "Portage", "Yum", - "PostInstall", "POSIX", "RPM", "RcUpdate", "SMF", "SYSV", "launchd"] +__all__ = ["Action", "APT", "Blast", "Chkconfig", "DebInit", "Encap", "launchd", + "Portage", "POSIX", "RPM", "RcUpdate", "SMF", "SYSV", "Yum"] import os, popen2, stat, sys, Bcfg2.Client.XML |