diff options
40 files changed, 731 insertions, 525 deletions
@@ -23,17 +23,17 @@ Installation For details about the installation of Bcfg2 please refer to the following pages in the Bcfg2 wiki. -* Prerequisites: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Prereqs -* Download: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Download -* Installation: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Install +* Prerequisites: http://bcfg2.org/wiki/Prereqs +* Download: http://bcfg2.org/wiki/Download +* Installation: http://bcfg2.org/wiki/Install Need help --------- -* FAQ: http://trac.mcs.anl.gov/projects/bcfg2/wiki/FAQ +* FAQ: http://bcfg2.org/wiki/FAQ * IRC: #bcfg2 on chat.freenode.net * Mailing list: https://lists.mcs.anl.gov/mailman/listinfo/bcfg-dev -* Bug tracker: http://trac.mcs.anl.gov/projects/bcfg2/report +* Bug tracker: http://bcfg2.org/report Documentation ------------- @@ -41,8 +41,8 @@ Documentation A lot of documentation is available in the Bcfg2 wiki and the Bcfg2 manual. -Wiki: http://trac.mcs.anl.gov/projects/bcfg2/wiki/ -Manual: http://doc.bcfg2.fourkitchens.com/ +Wiki: http://bcfg2.org/wiki/ +Manual: http://docs.bcfg2.org/ Bcfg2 is licensed under BSD, for more details check COPYING. diff --git a/debian/bcfg2-server.install b/debian/bcfg2-server.install index 859806fa0..91b1b2aef 100644 --- a/debian/bcfg2-server.install +++ b/debian/bcfg2-server.install @@ -1,5 +1,7 @@ debian/bcfg2-server.default usr/share/bcfg2 debian/tmp/usr/bin/bcfg2-* usr/sbin debian/tmp/usr/lib/python*/*-packages/Bcfg2/Server/* -debian/tmp/usr/share/bcfg2/* +debian/tmp/usr/share/bcfg2/Hostbase/* +debian/tmp/usr/share/bcfg2/schemas/* +debian/tmp/usr/share/bcfg2/xsl-transforms/* debian/tmp/usr/share/man/man8/* diff --git a/debian/bcfg2-web.install b/debian/bcfg2-web.install new file mode 100644 index 000000000..68caa98fa --- /dev/null +++ b/debian/bcfg2-web.install @@ -0,0 +1,3 @@ +misc/apache/bcfg2.conf etc/apache2/conf.d/ +debian/tmp/usr/share/bcfg2/reports.wsgi +debian/tmp/usr/share/bcfg2/site_media/* diff --git a/debian/control b/debian/control index acfc6cc9c..4db926f77 100644 --- a/debian/control +++ b/debian/control @@ -29,3 +29,13 @@ Description: Configuration management server for clients bound by client profiles. bcfg2-server is the server for bcfg2 clients, which generates configuration sets and stores statistics of client system states. + +Package: bcfg2-web +Architecture: all +Depends: ${python:Depends}, ${misc:Depends}, bcfg2 (= ${binary:Version}), python-django, libapache2-mod-wsgi +Suggests: python-mysqldb, python-psycopg2, python-sqlite +XB-Python-Version: >= 2.4 +Description: Configuration management web interface + Bcfg2 is a configuration management system that generates configuration sets + for clients bound by client profiles. + bcfg2-web is the reporting server for bcfg2. diff --git a/debian/rules b/debian/rules index 928b3d2d3..1638b8415 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,7 @@ #!/usr/bin/make -f + +WSGI_LOC = $(shell find debian/bcfg2-server/ -name reports.wsgi | perl -p -e 's|debian/bcfg2-server||') + %: dh --with python-support $@ @@ -14,3 +17,4 @@ override_dh_installinit: dh_installinit --package=bcfg2 --no-start # Install bcfg2-server initscript without starting it on postinst dh_installinit --package=bcfg2-server --no-start + diff --git a/misc/apache/bcfg2.conf b/misc/apache/bcfg2.conf new file mode 100644 index 000000000..b9b4b0452 --- /dev/null +++ b/misc/apache/bcfg2.conf @@ -0,0 +1,26 @@ +<IfModule mod_wsgi.c> + # + # If the root is changed update the static content alias as well + # + WSGIScriptAlias /bcfg2 "/usr/share/bcfg2/reports.wsgi" + + WSGISocketPrefix run + WSGIDaemonProcess Bcfg2.Server.Reports processes=1 threads=10 + WSGIProcessGroup Bcfg2.Server.Reports + + # + # Manually set this to override the static content + # + #SetEnv bcfg2.media_url /bcfg2/site_media/ + + # + # This should have the same prefix as WSGIScriptAlias + # + Alias "/bcfg2/site_media/" "/usr/share/bcfg2/site_media/" + <Directory "/usr/share/bcfg2/site_media/"> + Options None + AllowOverride None + Order allow,deny + Allow from all + </Directory> +</IfModule> diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec index 72008435a..8e77ad908 100644 --- a/misc/bcfg2.spec +++ b/misc/bcfg2.spec @@ -13,7 +13,7 @@ %define lxmldep %(rpm -q %{alt_lxml} 2>&1 > /dev/null && echo %{alt_lxml} || echo %{dfl_lxml}) Name: bcfg2 -Version: 1.1.0 +Version: 1.2.0 Release: %{release} Summary: Configuration management system @@ -93,6 +93,46 @@ systems are constantly changing; if required in your environment, Bcfg2 can enable the construction of complex change management and deployment strategies. +%package -n bcfg2-web +Version: %{version} +Summary: Bcfg2 Web Reporting Interface +Group: System Tools +Requires: bcfg2-server +Requires: httpd,Django +%if "%{_vendor}" == "redhat" +Requires: mod_wsgi +%define apache_conf %{_sysconfdir}/httpd +%else +Requires: apache2-mod_wsgi +%define apache_conf %{_sysconfdir}/apache2 +%endif + +%description -n bcfg2-web +Bcfg2 helps system administrators produce a consistent, reproducible, +and verifiable description of their environment, and offers +visualization and reporting tools to aid in day-to-day administrative +tasks. It is the fifth generation of configuration management tools +developed in the Mathematics and Computer Science Division of Argonne +National Laboratory. + +It is based on an operational model in which the specification can be +used to validate and optionally change the state of clients, but in a +feature unique to bcfg2 the client's response to the specification can +also be used to assess the completeness of the specification. Using +this feature, bcfg2 provides an objective measure of how good a job an +administrator has done in specifying the configuration of client +systems. Bcfg2 is therefore built to help administrators construct an +accurate, comprehensive specification. + +Bcfg2 has been designed from the ground up to support gentle +reconciliation between the specification and current client states. It +is designed to gracefully cope with manual system modifications. + +Finally, due to the rapid pace of updates on modern networks, client +systems are constantly changing; if required in your environment, +Bcfg2 can enable the construction of complex change management and +deployment strategies. + %prep %setup -q -n bcfg2-%{version} @@ -117,6 +157,9 @@ deployment strategies. %{__install} -m 755 debian/bcfg2.cron.hourly %{buildroot}%{_sysconfdir}/cron.hourly/bcfg2 %{__install} -m 755 tools/bcfg2-cron %{buildroot}%{_prefix}/lib/bcfg2/bcfg2-cron +%{__install} -d %{buildroot}%{apache_conf}/conf.d +%{__install} -m 644 misc/apache/bcfg2.conf %{buildroot}%{apache_conf}/conf.d/wsgi_bcfg2.conf + %clean [ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} || exit 2 @@ -147,7 +190,10 @@ deployment strategies. %{python_sitelib}/*egg-info %endif -%{_datadir}/bcfg2 +%dir %{_datadir}/bcfg2 +%{_datadir}/bcfg2/Hostbase +%{_datadir}/bcfg2/schemas +%{_datadir}/bcfg2/xsl-transforms %config(noreplace) %{_sysconfdir}/default/bcfg2-server %{_sbindir}/bcfg2-admin %{_sbindir}/bcfg2-build-reports @@ -160,6 +206,14 @@ deployment strategies. %{_mandir}/man8/*.8* %dir %{_prefix}/lib/bcfg2 +%files -n bcfg2-web +%defattr(-,root,root,-) + +%{_datadir}/bcfg2/reports.wsgi +%{_datadir}/bcfg2/site_media + +%config(noreplace) %{apache_conf}/conf.d/wsgi_bcfg2.conf + %changelog * Mon Jun 21 2010 Fabian Affolter <fabian@bernewireless.net> - 1.1.0rc3-0.1 - Changed source0 in order that it works with spectool diff --git a/reports/reports.wsgi b/reports/reports.wsgi new file mode 100644 index 000000000..232650485 --- /dev/null +++ b/reports/reports.wsgi @@ -0,0 +1,4 @@ +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.Server.Reports.settings' +import django.core.handlers.wsgi +application = django.core.handlers.wsgi.WSGIHandler() diff --git a/schemas/rules.xsd b/schemas/rules.xsd index 207eb65e5..80036834a 100644 --- a/schemas/rules.xsd +++ b/schemas/rules.xsd @@ -54,14 +54,6 @@ <xsd:attribute name='verify_flags' type='xsd:string'/> </xsd:complexType> - <xsd:complexType name='DirectoryType'> - <xsd:attribute type='xsd:string' name='name' use='required'/> - <xsd:attribute type='xsd:string' name='perms'/> - <xsd:attribute type='xsd:string' name='owner'/> - <xsd:attribute type='xsd:string' name='group'/> - <xsd:attribute type='xsd:string' name='prune'/> - </xsd:complexType> - <xsd:complexType name='ActionType'> <xsd:attribute type='ActionTimingEnum' name='timing' use='required'/> <xsd:attribute type='ActionWhenEnum' name='when' use='required'/> @@ -70,11 +62,6 @@ <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'/> - </xsd:complexType> - <xsd:complexType name='PathType'> <xsd:attribute type='PathTypeEnum' name='type' use='required'/> <xsd:attribute type='xsd:string' name='name' use='required'/> @@ -89,21 +76,11 @@ <xsd:attribute type='xsd:string' name='to'/> </xsd:complexType> - <xsd:complexType name='PermissionsType'> - <xsd:attribute type='xsd:string' name='name' use='required'/> - <xsd:attribute type='xsd:string' name='perms' use='required'/> - <xsd:attribute type='xsd:string' name='owner' use='required'/> - <xsd:attribute type='xsd:string' name='group' use='required'/> - </xsd:complexType> - <xsd:complexType name='RContainerType'> <xsd:choice minOccurs='0' maxOccurs='unbounded'> <xsd:element name='Service' type='ServiceType'/> - <xsd:element name='Directory' type='DirectoryType'/> - <xsd:element name='SymLink' type='SymLinkType'/> <xsd:element name='Package' type='PackageType'/> <xsd:element name='Path' type='PathType'/> - <xsd:element name='Permissions' type='PermissionsType'/> <xsd:element name='Action' type='ActionType'/> <xsd:element name='Group' type='RContainerType'/> <xsd:element name='Client' type='RContainerType'/> @@ -117,11 +94,8 @@ <xsd:complexType> <xsd:choice minOccurs='0' maxOccurs='unbounded'> <xsd:element name='Service' type='ServiceType'/> - <xsd:element name='Directory' type='DirectoryType'/> - <xsd:element name='SymLink' type='SymLinkType'/> <xsd:element name='Package' type='PackageType'/> <xsd:element name='Path' type='PathType'/> - <xsd:element name='Permissions' type='PermissionsType'/> <xsd:element name='Action' type='ActionType'/> <xsd:element name='Group' type='RContainerType'/> <xsd:element name='Client' type='RContainerType'/> @@ -38,6 +38,7 @@ setup(cmdclass=cmdclass, glob('reports/xsl-transforms/*.xsl')), ('share/bcfg2/xsl-transforms/xsl-transform-includes', glob('reports/xsl-transforms/xsl-transform-includes/*.xsl')), + ('share/bcfg2', ['reports/reports.wsgi']), ('share/man/man1', glob("man/bcfg2.1")), ('share/man/man5', glob("man/*.5")), ('share/man/man8', glob("man/*.8")), diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py index 6cfb19732..545d4b584 100644 --- a/src/lib/Client/Frame.py +++ b/src/lib/Client/Frame.py @@ -110,20 +110,6 @@ class Frame: self.logger.info("Loaded tool drivers:") self.logger.info([tool.name for tool in self.tools]) if not self.dryrun and not self.setup['bundle']: - for cfile in [cfl for cfl in config.findall(".//ConfigFile") \ - if cfl.get('name') in self.__important__]: - tl = [t for t in self.tools if t.handlesEntry(cfile) \ - and t.canVerify(cfile)] - if tl: - if not tl[0].VerifyConfigFile(cfile, []): - if self.setup['interactive'] and not \ - promptFilter("Install %s: %s? (y/N):", [cfile]): - continue - try: - self.states[cfile] = tl[0].InstallConfigFile(cfile) - except: - self.logger.error("Unexpected tool failure", - exc_info=1) for cfile in [cfl for cfl in config.findall(".//Path") \ if cfl.get('name') in self.__important__ and \ cfl.get('type') == 'file']: diff --git a/src/lib/Client/Tools/APT.py b/src/lib/Client/Tools/APT.py index 9dc2c5bba..2afe2eab7 100644 --- a/src/lib/Client/Tools/APT.py +++ b/src/lib/Client/Tools/APT.py @@ -55,7 +55,7 @@ class APT(Bcfg2.Client.Tools.Tool): '%s/apt/apt.conf' % etc_path, '%s/dpkg/dpkg.cfg' % etc_path] + \ [entry.get('name') for struct in config for entry in struct \ - if entry.tag in ['Path', 'ConfigFile'] and \ + if entry.tag == 'Path' and \ entry.get('name').startswith('%s/apt/sources.list' % etc_path)] self.nonexistent = [entry.get('name') for struct in config for entry in struct \ if entry.tag == 'Path' and entry.get('type') == 'nonexistent'] diff --git a/src/lib/Client/Tools/Action.py b/src/lib/Client/Tools/Action.py index 3610d9015..452788f94 100644 --- a/src/lib/Client/Tools/Action.py +++ b/src/lib/Client/Tools/Action.py @@ -3,23 +3,34 @@ __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'/> +""" +<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']} + 'Action': ['name', 'timing', 'when', 'command', 'status']} def RunAction(self, entry): """This method handles command execution and status return.""" if not self.setup['dryrun']: if self.setup['interactive']: - prompt = 'Run Action %s, %s: (y/N): ' % (entry.get('name'), entry.get('command')) + prompt = ('Run Action %s, %s: (y/N): ' % + (entry.get('name'), entry.get('command'))) if raw_input(prompt) not in ['y', 'Y']: return False if self.setup['servicemode'] == 'build': diff --git a/src/lib/Client/Tools/Blast.py b/src/lib/Client/Tools/Blast.py index 737c6924e..4f2891fd6 100644 --- a/src/lib/Client/Tools/Blast.py +++ b/src/lib/Client/Tools/Blast.py @@ -2,7 +2,9 @@ """This provides bcfg2 support for blastwave""" __revision__ = '$Revision$' -import Bcfg2.Client.Tools.SYSV, tempfile +import tempfile +import Bcfg2.Client.Tools.SYSV + class Blast(Bcfg2.Client.Tools.SYSV.SYSV): """Support for Blastwave packages""" @@ -27,7 +29,6 @@ class Blast(Bcfg2.Client.Tools.SYSV.SYSV): # Install comes from Bcfg2.Client.Tools.PkgTool # Extra comes from Bcfg2.Client.Tools.Tool # Remove comes from Bcfg2.Client.Tools.SYSV - def FindExtraPackages(self): """Pass through to null FindExtra call.""" return [] diff --git a/src/lib/Client/Tools/Chkconfig.py b/src/lib/Client/Tools/Chkconfig.py index 5dbb7b345..b7227ec3d 100644 --- a/src/lib/Client/Tools/Chkconfig.py +++ b/src/lib/Client/Tools/Chkconfig.py @@ -81,7 +81,9 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): self.logger.info("Installing Service %s" % (entry.get('name'))) pass1 = True if entry.get('status') == 'off': - rc = self.cmd.run(rcmd % (entry.get('name'), entry.get('status')) + " --level 0123456")[0] + rc = self.cmd.run(rcmd % (entry.get('name'), + entry.get('status')) + \ + " --level 0123456")[0] pass1 = rc == 0 rc = self.cmd.run(rcmd % (entry.get('name'), entry.get('status')))[0] return pass1 and rc == 0 @@ -93,5 +95,7 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool): self.logger.debug('Found active services:') self.logger.debug(allsrv) specified = [srv.get('name') for srv in self.getSupportedEntries()] - return [Bcfg2.Client.XML.Element('Service', type='chkconfig', name=name) \ + return [Bcfg2.Client.XML.Element('Service', + type='chkconfig', + name=name) \ for name in allsrv if name not in specified] diff --git a/src/lib/Client/Tools/DebInit.py b/src/lib/Client/Tools/DebInit.py index 0185f420c..aee8ffd65 100644 --- a/src/lib/Client/Tools/DebInit.py +++ b/src/lib/Client/Tools/DebInit.py @@ -1,16 +1,18 @@ """Debian Init Support for Bcfg2""" __revision__ = '$Revision$' -import glob, os, re +import glob +import os +import re import Bcfg2.Client.Tools - # Debian squeeze and beyond uses a dependecy based boot sequence DEBIAN_OLD_STYLE_BOOT_SEQUENCE = ( 'etch', '4.0', 'lenny', '5.0', '5.0.1', '5.0.2', '5.0.3', '5.0.4', '5.0.4', '5.0.5', ) + class DebInit(Bcfg2.Client.Tools.SvcTool): """Debian Service Support for Bcfg2.""" name = 'DebInit' @@ -35,7 +37,9 @@ class DebInit(Bcfg2.Client.Tools.SvcTool): start_sequence = int(entry.get('sequence')) kill_sequence = 100 - start_sequence else: - self.logger.warning("Your debian version boot sequence is dependency based \"sequence\" attribute wil be ignored.") + self.logger.warning("Your debian version boot sequence is " + "dependency based \"sequence\" attribute " + "will be ignored.") else: start_sequence = None diff --git a/src/lib/Client/Tools/FreeBSDInit.py b/src/lib/Client/Tools/FreeBSDInit.py index e597a294b..10f0f2e93 100644 --- a/src/lib/Client/Tools/FreeBSDInit.py +++ b/src/lib/Client/Tools/FreeBSDInit.py @@ -8,6 +8,7 @@ __revision__ = '$Rev$' import os import Bcfg2.Client.Tools + class FreeBSDInit(Bcfg2.Client.Tools.SvcTool): """FreeBSD service support for Bcfg2.""" name = 'FreeBSDInit' diff --git a/src/lib/Client/Tools/FreeBSDPackage.py b/src/lib/Client/Tools/FreeBSDPackage.py index 49cfa5bd2..04c05adaa 100644 --- a/src/lib/Client/Tools/FreeBSDPackage.py +++ b/src/lib/Client/Tools/FreeBSDPackage.py @@ -8,6 +8,7 @@ __revision__ = '$Rev$' import re import Bcfg2.Client.Tools + class FreeBSDPackage(Bcfg2.Client.Tools.PkgTool): """The FreeBSD toolset implements package operations and inherits the rest from Toolset.Toolset.""" @@ -24,7 +25,7 @@ class FreeBSDPackage(Bcfg2.Client.Tools.PkgTool): pattern = re.compile('(.*)-(\d.*)') for pkg in packages: if pattern.match(pkg): - name = pattern.match(pkg).group(1) + name = pattern.match(pkg).group(1) version = pattern.match(pkg).group(2) self.installed[name] = version diff --git a/src/lib/Client/Tools/IPS.py b/src/lib/Client/Tools/IPS.py index d7d84617f..9afd23143 100644 --- a/src/lib/Client/Tools/IPS.py +++ b/src/lib/Client/Tools/IPS.py @@ -1,11 +1,12 @@ """This is the Bcfg2 support for OpenSolaris packages.""" __revision__ = '$Revision$' -import Bcfg2.Client.Tools - import pkg.client.image as image import pkg.client.progress as progress +import Bcfg2.Client.Tools + + class IPS(Bcfg2.Client.Tools.PkgTool): """The IPS driver implements OpenSolaris package operations.""" name = 'IPS' diff --git a/src/lib/Client/Tools/MacPorts.py b/src/lib/Client/Tools/MacPorts.py index 70285fa3a..23b536451 100644 --- a/src/lib/Client/Tools/MacPorts.py +++ b/src/lib/Client/Tools/MacPorts.py @@ -3,6 +3,7 @@ __revision__ = '$Revision$' import Bcfg2.Client.Tools + class MacPorts(Bcfg2.Client.Tools.PkgTool): """macports package support.""" name = 'MacPorts' diff --git a/src/lib/Client/Tools/POSIX.py b/src/lib/Client/Tools/POSIX.py index 9d37b77d3..d2611130c 100644 --- a/src/lib/Client/Tools/POSIX.py +++ b/src/lib/Client/Tools/POSIX.py @@ -1,11 +1,11 @@ """All POSIX Type client support for Bcfg2.""" __revision__ = '$Revision$' -from datetime import datetime from stat import S_ISVTX, S_ISGID, S_ISUID, S_IXUSR, S_IWUSR, S_IRUSR, S_IXGRP from stat import S_IWGRP, S_IRGRP, S_IXOTH, S_IWOTH, S_IROTH, ST_MODE, S_ISDIR from stat import S_IFREG, ST_UID, ST_GID, S_ISREG, S_IFDIR, S_ISLNK, ST_MTIME import binascii +from datetime import datetime import difflib import errno import grp @@ -14,7 +14,6 @@ import os import pwd import shutil import stat -import string import time import Bcfg2.Client.Tools import Bcfg2.Options @@ -44,19 +43,6 @@ def calcPerms(initial, perms): tempperms |= perm return tempperms -def normUid(entry): - """ - This takes a user name or uid and - returns the corresponding uid or False. - """ - try: - try: - return int(entry.get('owner')) - except: - return int(pwd.getpwnam(entry.get('owner'))[2]) - except (OSError, KeyError): - log.error('UID normalization failed for %s' % (entry.get('name'))) - return False def normGid(entry): """ @@ -72,38 +58,33 @@ def normGid(entry): log.error('GID normalization failed for %s' % (entry.get('name'))) return False -text_chars = "".join([chr(y) for y in range(32, 127)] + list("\n\r\t\b")) -notrans = string.maketrans("", "") -def isString(strng): - """Returns true if a string contains no binary chars.""" - if "\0" in strng: +def normUid(entry): + """ + This takes a user name or uid and + returns the corresponding uid or False. + """ + try: + try: + return int(entry.get('owner')) + except: + return int(pwd.getpwnam(entry.get('owner'))[2]) + except (OSError, KeyError): + log.error('UID normalization failed for %s' % (entry.get('name'))) return False - if not strng: - return True - - return len(strng.translate(notrans, text_chars)) == 0 class POSIX(Bcfg2.Client.Tools.Tool): """POSIX File support code.""" name = 'POSIX' - __handles__ = [('ConfigFile', None), - ('Directory', None), - ('Path', 'device'), + __handles__ = [('Path', 'device'), ('Path', 'directory'), ('Path', 'file'), ('Path', 'hardlink'), ('Path', 'nonexistent'), ('Path', 'permissions'), - ('Path', 'symlink'), - ('Permissions', None), - ('SymLink', None)] - __req__ = {'ConfigFile': ['name', 'owner', 'group', 'perms'], - 'Directory': ['name', 'owner', 'group', 'perms'], - 'Path': ['name', 'type'], - 'Permissions': ['name', 'owner', 'group', 'perms'], - 'SymLink': ['name', 'to']} + ('Path', 'symlink')] + __req__ = {'Path': ['name', 'type']} # grab paranoid options from /etc/bcfg2.conf opts = {'ppath': Bcfg2.Options.PARANOID_PATH, @@ -116,63 +97,150 @@ class POSIX(Bcfg2.Client.Tools.Tool): def canInstall(self, entry): """Check if entry is complete for installation.""" if Bcfg2.Client.Tools.Tool.canInstall(self, entry): - if (entry.tag, entry.text, entry.get('empty', 'false')) == \ - ('ConfigFile', None, 'false'): + if (entry.tag, + entry.get('type'), + entry.text, + entry.get('empty', 'false')) == ('Path', + 'file', + None, + 'false'): return False return True else: return False - def VerifySymLink(self, entry, _): - """Verify SymLink Entry.""" - try: - sloc = os.readlink(entry.get('name')) - if sloc == entry.get('to'): - return True - self.logger.debug("Symlink %s points to %s, should be %s" % \ - (entry.get('name'), sloc, entry.get('to'))) - entry.set('current_to', sloc) - entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), - entry.get('to'))) + def gatherCurrentData(self, entry): + if entry.tag == 'Path' and entry.get('type') == 'file': + try: + ondisk = os.stat(entry.get('name')) + except OSError: + entry.set('current_exists', 'false') + self.logger.debug("%s %s does not exist" % + (entry.tag, entry.get('name'))) + return False + try: + entry.set('current_owner', str(ondisk[ST_UID])) + entry.set('current_group', str(ondisk[ST_GID])) + except (OSError, KeyError): + pass + entry.set('perms', str(oct(ondisk[ST_MODE])[-4:])) + try: + content = open(entry.get('name')).read() + entry.set('current_bfile', binascii.b2a_base64(content)) + except IOError, error: + self.logger.error("Failed to read %s: %s" % (error.filename, + error.strerror)) + + def Verifydevice(self, entry, _): + """Verify device entry.""" + if entry.get('dev_type') == None or \ + entry.get('owner') == None or \ + entry.get('group') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) return False + if entry.get('dev_type') in ['block', 'char']: + # check if major/minor are properly specified + if entry.get('major') == None or \ + entry.get('minor') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + try: + # check for file existence + filestat = os.stat(entry.get('name')) except OSError: entry.set('current_exists', 'false') - entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), - entry.get('to'))) + self.logger.debug("%s %s does not exist" % + (entry.tag, entry.get('name'))) return False - def InstallSymLink(self, entry): - """Install SymLink entry.""" - self.logger.info("Installing Symlink %s" % (entry.get('name'))) - if os.path.lexists(entry.get('name')): - try: - fmode = os.lstat(entry.get('name'))[ST_MODE] - if S_ISREG(fmode) or S_ISLNK(fmode): - self.logger.debug("Non-directory entry already exists at " - "%s. Unlinking entry." % \ - (entry.get('name'))) - os.unlink(entry.get('name')) - elif S_ISDIR(fmode): - self.logger.debug("Directory entry already exists at %s" %\ - (entry.get('name'))) - self.cmd.run("mv %s/ %s.bak" % \ - (entry.get('name'), - entry.get('name'))) - else: - os.unlink(entry.get('name')) - except OSError: - self.logger.info("Symlink %s cleanup failed" % (entry.get('name'))) try: - os.symlink(entry.get('to'), entry.get('name')) - return True + # attempt to verify device properties as specified in config + dev_type = entry.get('dev_type') + mode = calcPerms(device_map[dev_type], + entry.get('mode', '0600')) + owner = normUid(entry) + group = normGid(entry) + if dev_type in ['block', 'char']: + # check for incompletely specified entries + if entry.get('major') == None or \ + entry.get('minor') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + major = int(entry.get('major')) + minor = int(entry.get('minor')) + if major == os.major(filestat.st_rdev) and \ + minor == os.minor(filestat.st_rdev) and \ + mode == filestat.st_mode and \ + owner == filestat.st_uid and \ + group == filestat.st_gid: + return True + else: + return False + elif dev_type == 'fifo' and \ + mode == filestat.st_mode and \ + owner == filestat.st_uid and \ + group == filestat.st_gid: + return True + else: + self.logger.info('Device properties for %s incorrect' % \ + entry.get('name')) + return False except OSError: + self.logger.debug("%s %s failed to verify" % + (entry.tag, entry.get('name'))) return False - def VerifyDirectory(self, entry, modlist): - """Verify Directory entry.""" + def Installdevice(self, entry): + """Install device entries.""" + try: + # check for existing paths and remove them + os.lstat(entry.get('name')) + try: + os.unlink(entry.get('name')) + exists = False + except OSError: + self.logger.info('Failed to unlink %s' % \ + entry.get('name')) + return False + except OSError: + exists = False + + if not exists: + try: + dev_type = entry.get('dev_type') + mode = calcPerms(device_map[dev_type], + entry.get('mode', '0600')) + if dev_type in ['block', 'char']: + # check if major/minor are properly specified + if entry.get('major') == None or \ + entry.get('minor') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + major = int(entry.get('major')) + minor = int(entry.get('minor')) + device = os.makedev(major, minor) + os.mknod(entry.get('name'), mode, device) + else: + os.mknod(entry.get('name'), mode) + os.chown(entry.get('name'), normUid(entry), normGid(entry)) + return True + except KeyError: + self.logger.error('Failed to install %s' % entry.get('name')) + except OSError: + self.logger.error('Failed to install %s' % entry.get('name')) + return False + + def Verifydirectory(self, entry, modlist): + """Verify Path type='directory' entry.""" if entry.get('perms') == None or \ entry.get('owner') == None or \ entry.get('group') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) return False while len(entry.get('perms', '')) < 4: entry.set('perms', '0' + entry.get('perms', '')) @@ -205,8 +273,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): pruneTrue = True ex_ents = [] if entry.get('prune', 'false') == 'true' \ - and (entry.tag == 'Directory' or entry.get('type') == 'directory'): - # FIXME: need to verify both old and new POSIX types + and (entry.tag == 'Path' and entry.get('type') == 'directory'): + # check for any extra entries when prune='true' attribute is set try: entries = ['/'.join([entry.get('name'), ent]) \ for ent in os.listdir(entry.get('name'))] @@ -217,10 +285,12 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.get('name')) self.logger.debug(ex_ents) nqtext = entry.get('qtext', '') + '\n' - nqtext += "Directory %s contains extra entries:" % entry.get('name') + nqtext += "Directory %s contains extra entries:" % \ + entry.get('name') nqtext += ":".join(ex_ents) entry.set('qtest', nqtext) - [entry.append(XML.Element('Prune', path=x)) for x in ex_ents] + [entry.append(XML.Element('Prune', path=x)) \ + for x in ex_ents] except OSError: ex_ents = [] pruneTrue = True @@ -236,7 +306,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.set('qtext', nqtext) if group != str(normGid(entry)): entry.set('current_group', group) - self.logger.debug("%s %s group wrong" % (entry.tag, entry.get('name'))) + self.logger.debug("%s %s group wrong" % \ + (entry.tag, entry.get('name'))) nqtext = entry.get('qtext', '') + '\n' nqtext += "%s group is %s should be %s" % \ (entry.get('name'), group, entry.get('group')) @@ -244,10 +315,16 @@ class POSIX(Bcfg2.Client.Tools.Tool): if perms != entry.get('perms'): entry.set('current_perms', perms) self.logger.debug("%s %s permissions are %s should be %s" % - (entry.tag, entry.get('name'), perms, entry.get('perms'))) + (entry.tag, + entry.get('name'), + perms, + entry.get('perms'))) nqtext = entry.get('qtext', '') + '\n' - nqtext += "%s perms are %s should be %s" % \ - (entry.get('name'), perms, entry.get('perms')) + nqtext += "%s %s perms are %s should be %s" % \ + (entry.tag, + entry.get('name'), + perms, + entry.get('perms')) entry.set('qtext', nqtext) if mtime != entry.get('mtime', '-1'): entry.set('current_mtime', mtime) @@ -258,21 +335,23 @@ class POSIX(Bcfg2.Client.Tools.Tool): nqtext += "%s mtime is %s should be %s" % \ (entry.get('name'), mtime, entry.get('mtime')) entry.set('qtext', nqtext) - if entry.tag != 'ConfigFile': + if entry.get('type') != 'file': nnqtext = entry.get('qtext') - nnqtext += '\nInstall %s %s: (y/N) ' % (entry.tag, entry.get('name')) + nnqtext += '\nInstall %s %s: (y/N) ' % (entry.get('type'), + entry.get('name')) entry.set('qtext', nnqtext) return pTrue and pruneTrue - def InstallDirectory(self, entry): - """Install Directory entry.""" + def Installdirectory(self, entry): + """Install Path type='directory' entry.""" if entry.get('perms') == None or \ entry.get('owner') == None or \ entry.get('group') == None: self.logger.error('Entry %s not completely specified. ' - 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) return False - self.logger.info("Installing Directory %s" % (entry.get('name'))) + self.logger.info("Installing directory %s" % (entry.get('name'))) try: fmode = os.lstat(entry.get('name')) if not S_ISDIR(fmode[ST_MODE]): @@ -282,7 +361,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): os.unlink(entry.get('name')) exists = False except OSError: - self.logger.info("Failed to unlink %s" % (entry.get('name'))) + self.logger.info("Failed to unlink %s" % \ + (entry.get('name'))) return False else: self.logger.debug("Found a pre-existing directory at %s" % \ @@ -327,7 +407,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): pname = pent.get('path') ulfailed = False if os.path.isdir(pname): - self.logger.info("Not removing extra directory %s, please check and remove manually" % pname) + self.logger.info("Not removing extra directory %s, " + "please check and remove manually" % pname) continue try: self.logger.debug("Unlinking file %s" % pname) @@ -337,188 +418,12 @@ class POSIX(Bcfg2.Client.Tools.Tool): ulfailed = True if ulfailed: return False - return self.InstallPermissions(entry) - - def VerifyhardLink(self, entry, _): - """Verify HardLink entry.""" - try: - if os.path.samefile(entry.get('name'), entry.get('to')): - return True - self.logger.debug("Hardlink %s is incorrect" % \ - entry.get('name')) - entry.set('qtext', "Link %s to %s? [y/N] " % \ - (entry.get('name'), - entry.get('to'))) - return False - except OSError: - entry.set('current_exists', 'false') - entry.set('qtext', "Link %s to %s? [y/N] " % \ - (entry.get('name'), - entry.get('to'))) - return False - - def InstallhardLink(self, entry): - """Install HardLink entry.""" - self.logger.info("Installing Hardlink %s" % (entry.get('name'))) - if os.path.lexists(entry.get('name')): - try: - fmode = os.lstat(entry.get('name'))[ST_MODE] - if S_ISREG(fmode) or S_ISLNK(fmode): - self.logger.debug("Non-directory entry already exists at " - "%s. Unlinking entry." % (entry.get('name'))) - os.unlink(entry.get('name')) - elif S_ISDIR(fmode): - self.logger.debug("Directory entry already exists at %s" % \ - (entry.get('name'))) - self.cmd.run("mv %s/ %s.bak" % \ - (entry.get('name'), - entry.get('name'))) - else: - os.unlink(entry.get('name')) - except OSError: - self.logger.info("Hardlink %s cleanup failed" % (entry.get('name'))) - try: - os.link(entry.get('to'), entry.get('name')) - return True - except OSError: - return False - - def VerifyPermissions(self, entry, _): - """Verify Permissions entry""" - return self.VerifyDirectory(entry, _) + return self.Installpermissions(entry) - def InstallPermissions(self, entry): - """Install POSIX permissions""" - if entry.get('perms') == None or \ - entry.get('owner') == None or \ - entry.get('group') == None: - self.logger.error('Entry %s not completely specified. ' - 'Try running bcfg2-repo-validate.' % (entry.get('name'))) - return False - try: - os.chown(entry.get('name'), normUid(entry), normGid(entry)) - os.chmod(entry.get('name'), calcPerms(S_IFDIR, entry.get('perms'))) - return True - except (OSError, KeyError): - self.logger.error('Permission fixup failed for %s' % \ - (entry.get('name'))) - return False - - def Verifydevice(self, entry, _): - """Verify device entry.""" - try: - # check for file existence - filestat = os.stat(entry.get('name')) - except OSError: - entry.set('current_exists', 'false') - self.logger.debug("%s %s does not exist" % - (entry.tag, entry.get('name'))) - return False - - try: - # attempt to verify device properties as specified in config - dev_type = entry.get('dev_type') - mode = calcPerms(device_map[dev_type], - entry.get('mode', '0600')) - owner = entry.get('owner') - group = entry.get('group') - if dev_type in ['block', 'char']: - major = int(entry.get('major')) - minor = int(entry.get('minor')) - if major == os.major(filestat.st_rdev) and \ - minor == os.minor(filestat.st_rdev) and \ - mode == filestat.st_mode and \ - owner == filestat.st_uid and \ - group == filestat.st_gid: - return True - else: - return False - elif dev_type == 'fifo' and \ - mode == filestat.st_mode and \ - owner == filestat.st_uid and \ - group == filestat.st_gid: - return True - else: - self.logger.info('Device properties for %s incorrect' % \ - entry.get('name')) - return False - except OSError: - self.logger.debug("%s %s failed to verify" % - (entry.tag, entry.get('name'))) - return False - - def Installdevice(self, entry): - """Install device entries.""" - try: - # check for existing paths and remove them - filestat = os.lstat(entry.get('name')) - try: - os.unlink(entry.get('name')) - exists = False - except OSError: - self.logger.info('Failed to unlink %s' % \ - entry.get('name')) - return False - except OSError: - exists = False - - if not exists: - try: - dev_type = entry.get('dev_type') - mode = calcPerms(device_map[dev_type], - entry.get('mode', '0600')) - if dev_type in ['block', 'char']: - major = int(entry.get('major')) - minor = int(entry.get('minor')) - device = os.makedev(major, minor) - os.mknod(entry.get('name'), mode, device) - else: - os.mknod(entry.get('name'), mode) - os.chown(entry.get('name'), normUid(entry), normGid(entry)) - return True - except OSError: - self.logger.error('Failed to install %s' % entry.get('name')) - return False - - def Verifynonexistent(self, entry, _): - """Verify nonexistent entry.""" - # return true if path does _not_ exist - return not os.path.lexists(entry.get('name')) - - def Installnonexistent(self, entry): - '''Remove nonexistent entries''' - try: - os.remove(entry.get('name')) - return True - except OSError: - self.logger.error('Failed to remove %s' % entry.get('name')) - return False - - def gatherCurrentData(self, entry): - if entry.tag == 'ConfigFile': - try: - ondisk = os.stat(entry.get('name')) - except OSError: - entry.set('current_exists', 'false') - self.logger.debug("%s %s does not exist" % - (entry.tag, entry.get('name'))) - return False - try: - entry.set('current_owner', str(ondisk[ST_UID])) - entry.set('current_group', str(ondisk[ST_GID])) - except (OSError, KeyError): - pass - entry.set('perms', str(oct(ondisk[ST_MODE])[-4:])) - try: - content = open(entry.get('name')).read() - entry.set('current_bfile', binascii.b2a_base64(content)) - except IOError, error: - self.logger.error("Failed to read %s: %s" % (error.filename, error.strerror)) - - def VerifyConfigFile(self, entry, _): - """Install ConfigFile entry.""" - # configfile verify is permissions check + content check - permissionStatus = self.VerifyDirectory(entry, _) + def Verifyfile(self, entry, _): + """Verify Path type='file' entry.""" + # permissions check + content check + permissionStatus = self.Verifydirectory(entry, _) tbin = False if entry.get('encoding', 'ascii') == 'base64': tempdata = binascii.a2b_base64(entry.text) @@ -527,8 +432,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): tempdata = '' else: if entry.text == None: - self.logger.error("Cannot verify incomplete ConfigFile %s" % \ - (entry.get('name'))) + self.logger.error("Cannot verify incomplete Path type='%s' %s" % \ + (entry.get('type'), entry.get('name'))) return False tempdata = entry.text if type(tempdata) == unicode: @@ -551,7 +456,12 @@ class POSIX(Bcfg2.Client.Tools.Tool): # md5sum so it would be faster for big binary files contentStatus = content == tempdata if not contentStatus: - if tbin or not isString(content): + try: + content.decode('ascii') + isstring = True + except: + isstring = False + if tbin or not isstring: entry.set('current_bfile', binascii.b2a_base64(content)) nqtext = entry.get('qtext', '') nqtext += '\nBinary file, no printable diff' @@ -560,7 +470,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): rawdiff = [] start = time.time() longtime = False - for x in difflib.ndiff(content.split('\n'), tempdata.split('\n')): + for x in difflib.ndiff(content.split('\n'), + tempdata.split('\n')): now = time.time() rawdiff.append(x) if now - start > 5 and not longtime: @@ -599,9 +510,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): entry.set('qtext', qtxt) return contentStatus and permissionStatus - def InstallConfigFile(self, entry): - """Install ConfigFile entry.""" - self.logger.info("Installing ConfigFile %s" % (entry.get('name'))) + def Installfile(self, entry): + """Install Path type='file' entry.""" + self.logger.info("Installing file %s" % (entry.get('name'))) parent = "/".join(entry.get('name').split('/')[:-1]) if parent: @@ -635,9 +546,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): self.setup.get("paranoid", False) and not \ (entry.get('current_exists', 'true') == 'false'): bkupnam = entry.get('name').replace('/', '_') - # current list of backups for this ConfigFile + # current list of backups for this file bkuplist = [f for f in os.listdir(self.ppath) if - f.startswith(bkupnam)] + f.startswith(bkupnam)] bkuplist.sort() if len(bkuplist) == int(self.max_copies): # remove the oldest backup available @@ -656,7 +567,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): self.logger.info("Backup of %s saved to %s" % (entry.get('name'), self.ppath)) except IOError, e: - self.logger.error("Failed to create backup file for ConfigFile %s" % \ + self.logger.error("Failed to create backup file for %s" % \ (entry.get('name'))) self.logger.error(e) return False @@ -687,7 +598,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): os.utime(entry.get('name'), (int(entry.get('mtime')), int(entry.get('mtime')))) except: - self.logger.error("ConfigFile %s mtime fix failed" \ + self.logger.error("File %s mtime fix failed" \ % (entry.get('name'))) return False return True @@ -698,42 +609,158 @@ class POSIX(Bcfg2.Client.Tools.Tool): print(err) return False - def Verifydirectory(self, entry, _): - ret = getattr(self, 'VerifyDirectory') - return ret(entry, _) + def Verifyhardlink(self, entry, _): + """Verify HardLink entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + try: + if os.path.samefile(entry.get('name'), entry.get('to')): + return True + self.logger.debug("Hardlink %s is incorrect" % \ + entry.get('name')) + entry.set('qtext', "Link %s to %s? [y/N] " % \ + (entry.get('name'), + entry.get('to'))) + return False + except OSError: + entry.set('current_exists', 'false') + entry.set('qtext', "Link %s to %s? [y/N] " % \ + (entry.get('name'), + entry.get('to'))) + return False - def Installdirectory(self, entry): - ret = getattr(self, 'InstallDirectory') - return ret(entry) + def Installhardlink(self, entry): + """Install HardLink entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + self.logger.info("Installing Hardlink %s" % (entry.get('name'))) + if os.path.lexists(entry.get('name')): + try: + fmode = os.lstat(entry.get('name'))[ST_MODE] + if S_ISREG(fmode) or S_ISLNK(fmode): + self.logger.debug("Non-directory entry already exists at " + "%s. Unlinking entry." % (entry.get('name'))) + os.unlink(entry.get('name')) + elif S_ISDIR(fmode): + self.logger.debug("Directory already exists at %s" % \ + (entry.get('name'))) + self.cmd.run("mv %s/ %s.bak" % \ + (entry.get('name'), + entry.get('name'))) + else: + os.unlink(entry.get('name')) + except OSError: + self.logger.info("Hardlink %s cleanup failed" % \ + (entry.get('name'))) + try: + os.link(entry.get('to'), entry.get('name')) + return True + except OSError: + return False - def Verifyfile(self, entry, _): - ret = getattr(self, 'VerifyConfigFile') - return ret(entry, _) + def Verifynonexistent(self, entry, _): + """Verify nonexistent entry.""" + # return true if path does _not_ exist + return not os.path.lexists(entry.get('name')) - def Installfile(self, entry): - ret = getattr(self, 'InstallConfigFile') - return ret(entry) + def Installnonexistent(self, entry): + '''Remove nonexistent entries''' + try: + os.remove(entry.get('name')) + return True + except OSError: + self.logger.error('Failed to remove %s' % entry.get('name')) + return False def Verifypermissions(self, entry, _): - ret = getattr(self, 'VerifyPermissions') - return ret(entry, _) + """Verify Path type='permissions' entry""" + return self.Verifydirectory(entry, _) def Installpermissions(self, entry): - ret = getattr(self, 'InstallPermissions') - return ret(entry) + """Install POSIX permissions""" + if entry.get('perms') == None or \ + entry.get('owner') == None or \ + entry.get('group') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % (entry.get('name'))) + return False + try: + os.chown(entry.get('name'), normUid(entry), normGid(entry)) + os.chmod(entry.get('name'), calcPerms(S_IFDIR, entry.get('perms'))) + return True + except (OSError, KeyError): + self.logger.error('Permission fixup failed for %s' % \ + (entry.get('name'))) + return False def Verifysymlink(self, entry, _): - ret = getattr(self, 'VerifySymLink') - return ret(entry, _) + """Verify Path type='symlink' entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + try: + sloc = os.readlink(entry.get('name')) + if sloc == entry.get('to'): + return True + self.logger.debug("Symlink %s points to %s, should be %s" % \ + (entry.get('name'), sloc, entry.get('to'))) + entry.set('current_to', sloc) + entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), + entry.get('to'))) + return False + except OSError: + entry.set('current_exists', 'false') + entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'), + entry.get('to'))) + return False def Installsymlink(self, entry): - ret = getattr(self, 'InstallSymLink') - return ret(entry) + """Install Path type='symlink' entry.""" + if entry.get('to') == None: + self.logger.error('Entry %s not completely specified. ' + 'Try running bcfg2-repo-validate.' % \ + (entry.get('name'))) + return False + self.logger.info("Installing symlink %s" % (entry.get('name'))) + if os.path.lexists(entry.get('name')): + try: + fmode = os.lstat(entry.get('name'))[ST_MODE] + if S_ISREG(fmode) or S_ISLNK(fmode): + self.logger.debug("Non-directory entry already exists at " + "%s. Unlinking entry." % \ + (entry.get('name'))) + os.unlink(entry.get('name')) + elif S_ISDIR(fmode): + self.logger.debug("Directory already exists at %s" %\ + (entry.get('name'))) + self.cmd.run("mv %s/ %s.bak" % \ + (entry.get('name'), + entry.get('name'))) + else: + os.unlink(entry.get('name')) + except OSError: + self.logger.info("Symlink %s cleanup failed" %\ + (entry.get('name'))) + try: + os.symlink(entry.get('to'), entry.get('name')) + return True + except OSError: + return False def InstallPath(self, entry): + """Dispatch install to the proper method according to type""" ret = getattr(self, 'Install%s' % entry.get('type')) return ret(entry) def VerifyPath(self, entry, _): + """Dispatch verify to the proper method according to type""" ret = getattr(self, 'Verify%s' % entry.get('type')) return ret(entry, _) diff --git a/src/lib/Client/Tools/Portage.py b/src/lib/Client/Tools/Portage.py index 765e981fe..58d2aad29 100644 --- a/src/lib/Client/Tools/Portage.py +++ b/src/lib/Client/Tools/Portage.py @@ -4,9 +4,10 @@ __revision__ = '$Revision$' import re import Bcfg2.Client.Tools + class Portage(Bcfg2.Client.Tools.PkgTool): - """The Gentoo toolset implements package and service operations and inherits - the rest from Toolset.Toolset.""" + """The Gentoo toolset implements package and service operations and + inherits the rest from Toolset.Toolset.""" name = 'Portage' __execs__ = ['/usr/bin/emerge', '/usr/bin/equery'] __handles__ = [('Package', 'ebuild')] @@ -47,8 +48,7 @@ class Portage(Bcfg2.Client.Tools.PkgTool): if self.installed[entry.attrib['name']] == entry.attrib['version']: if not self.setup['quick'] and \ entry.get('verify', 'true') == 'true': - output = self.cmd.run \ - ("/usr/bin/equery check '=%s-%s' 2>&1 |grep '!!!' | awk '{print $2}'" \ + output = self.cmd.run("/usr/bin/equery check '=%s-%s' 2>&1 |grep '!!!' | awk '{print $2}'" \ % (entry.get('name'), entry.get('version')))[1] if [filename for filename in output \ if filename not in modlist]: diff --git a/src/lib/Client/Tools/RcUpdate.py b/src/lib/Client/Tools/RcUpdate.py index a91562c30..159172b78 100644 --- a/src/lib/Client/Tools/RcUpdate.py +++ b/src/lib/Client/Tools/RcUpdate.py @@ -87,5 +87,7 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool): self.logger.debug('Found active services:') self.logger.debug(allsrv) specified = [srv.get('name') for srv in self.getSupportedEntries()] - return [Bcfg2.Client.XML.Element('Service', type='rc-update', name=name) \ + return [Bcfg2.Client.XML.Element('Service', + type='rc-update', + name=name) \ for name in allsrv if name not in specified] diff --git a/src/lib/Client/Tools/SMF.py b/src/lib/Client/Tools/SMF.py index 733228d18..f0bc6bd05 100644 --- a/src/lib/Client/Tools/SMF.py +++ b/src/lib/Client/Tools/SMF.py @@ -1,15 +1,18 @@ """SMF support for Bcfg2""" __revision__ = '$Revision$' -import glob, os +import glob +import os + import Bcfg2.Client.Tools + class SMF(Bcfg2.Client.Tools.SvcTool): """Support for Solaris SMF Services.""" __handles__ = [('Service', 'smf')] __execs__ = ['/usr/sbin/svcadm', '/usr/bin/svcs'] name = 'SMF' - __req__ = {'Service':['name', 'status']} + __req__ = {'Service': ['name', 'status']} __ireq__ = {'Service': ['name', 'status', 'FMRI']} def get_svc_command(self, service, action): @@ -53,7 +56,8 @@ class SMF(Bcfg2.Client.Tools.SvcTool): (entry.get("FMRI"), ":".join(files))) return entry.get('status') == 'on' else: - self.logger.debug("No service matching %s" % (entry.get("FMRI"))) + self.logger.debug("No service matching %s" % \ + (entry.get("FMRI"))) return entry.get('status') == 'off' try: srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % \ @@ -79,7 +83,8 @@ class SMF(Bcfg2.Client.Tools.SvcTool): os.rename(loc, loc.replace('/S', '/DISABLED.S')) return True except OSError: - self.logger.error("Failed to rename init script %s" % (loc)) + self.logger.error("Failed to rename init script %s" % \ + (loc)) return False else: cmdrc = self.cmd.run("/usr/sbin/svcadm disable %s" % \ @@ -94,8 +99,8 @@ class SMF(Bcfg2.Client.Tools.SvcTool): os.rename(loc.replace('/S', '/DISABLED.S'), loc) cmdrc = 0 except OSError: - self.logger.debug("Failed to rename %s to %s" \ - % (loc.replace('/S', '/DISABLED.S'), loc)) + self.logger.debug("Failed to rename %s to %s" % \ + (loc.replace('/S', '/DISABLED.S'), loc)) cmdrc = 1 else: srvdata = self.cmd.run("/usr/bin/svcs -H -o STA %s" % diff --git a/src/lib/Client/Tools/SYSV.py b/src/lib/Client/Tools/SYSV.py index c2b0eb2cc..b5e1f1c59 100644 --- a/src/lib/Client/Tools/SYSV.py +++ b/src/lib/Client/Tools/SYSV.py @@ -2,7 +2,10 @@ """This provides bcfg2 support for Solaris SYSV packages.""" __revision__ = '$Revision$' -import tempfile, Bcfg2.Client.Tools, Bcfg2.Client.XML +import tempfile + +import Bcfg2.Client.Tools +import Bcfg2.Client.XML noask = ''' @@ -19,6 +22,7 @@ action=nocheck basedir=default ''' + class SYSV(Bcfg2.Client.Tools.PkgTool): """Solaris SYSV package support.""" __execs__ = ["/usr/sbin/pkgadd", "/usr/bin/pkginfo"] diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Client/Tools/YUMng.py index 077d71508..9cdcdca40 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Client/Tools/YUMng.py @@ -4,7 +4,6 @@ __revision__ = '$Revision$' import ConfigParser import copy import os.path -import sys import yum import yum.packages import yum.rpmtrans @@ -21,6 +20,7 @@ try: except NameError: from sets import Set as set + def build_yname(pkgname, inst): """Build yum appropriate package name.""" d = {} @@ -39,6 +39,7 @@ def build_yname(pkgname, inst): d['arch'] = inst.get('arch') return d + def short_yname(nevra): d = nevra.copy() if 'version' in d: @@ -49,17 +50,19 @@ def short_yname(nevra): del d['release'] return d + def nevraString(p): if isinstance(p, yum.packages.PackageObject): return str(p) else: ret = "" - for i, j in [('epoch','%s:'), ('name','%s'), ('version','-%s'), - ('release','-%s'), ('arch','.%s')]: + for i, j in [('epoch', '%s:'), ('name', '%s'), ('version', '-%s'), + ('release', '-%s'), ('arch', '.%s')]: if i in p: ret = "%s%s" % (ret, j % p[i]) return ret + class Parser(ConfigParser.ConfigParser): def get(self, section, option, default): @@ -73,6 +76,7 @@ class Parser(ConfigParser.ConfigParser): except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): return default + class RPMDisplay(yum.rpmtrans.RPMBaseCallback): """We subclass the default RPM transaction callback so that we can control Yum's verbosity and pipe it through the right logger.""" @@ -83,11 +87,11 @@ class RPMDisplay(yum.rpmtrans.RPMBaseCallback): self.state = None self.package = None - def event(self, package, action, te_current, te_total, + def event(self, package, action, te_current, te_total, ts_current, ts_total): - """ + """ @param package: A yum package object or simple string of a package name - @param action: A yum.constant transaction set state or in the obscure + @param action: A yum.constant transaction set state or in the obscure rpm repackage case it could be the string 'repackaging' @param te_current: Current number of bytes processed in the transaction element being processed @@ -114,6 +118,7 @@ class RPMDisplay(yum.rpmtrans.RPMBaseCallback): """Deal with error reporting.""" self.logger.error(msg) + class YumDisplay(yum.callbacks.ProcessTransBaseCallback): """Class to handle display of what step we are in the Yum transaction such as downloading packages, etc.""" @@ -141,10 +146,10 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): def __init__(self, logger, setup, config): self.yb = yum.YumBase() Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - self.ignores = [ entry.get('name') for struct in config \ - for entry in struct \ - if entry.tag == 'Path' and \ - entry.get('type') == 'ignore' ] + self.ignores = [entry.get('name') for struct in config \ + for entry in struct \ + if entry.tag == 'Path' and \ + entry.get('type') == 'ignore'] self.instance_status = {} self.extra_instances = [] self.modlists = {} @@ -152,7 +157,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): self.__important__ = self.__important__ + \ [entry.get('name') for struct in config \ for entry in struct \ - if entry.tag in ['Path', 'ConfigFile'] and \ + if entry.tag == 'Path' and \ (entry.get('name').startswith('/etc/yum.d') \ or entry.get('name').startswith('/etc/yum.repos.d')) \ or entry.get('name') == '/etc/yum.conf'] @@ -168,7 +173,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): except yum.Errors.YumBaseError, e: self.logger.error("YUMng error: %s" % e) raise Bcfg2.Client.Tools.toolInstantiationError - + yup = self.yb.doPackageLists(pkgnarrow='updates') if hasattr(self.yb.rpmdb, 'pkglist'): yinst = self.yb.rpmdb.pkglist @@ -201,7 +206,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): in truth self.doInstall = CP.get(self.name, "installed_action", "install").lower() == "install" - self.doUpgrade = CP.get(self.name, + self.doUpgrade = CP.get(self.name, "version_fail_action", "upgrade").lower() == "upgrade" self.doReinst = CP.get(self.name, "verify_fail_action", "reinstall").lower() == "reinstall" @@ -254,7 +259,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): instance = Bcfg2.Client.XML.SubElement(entry, 'Package') for attrib in list(entry.attrib.keys()): instance.attrib[attrib] = entry.attrib[attrib] - instances = [ instance ] + instances = [instance] return instances @@ -287,18 +292,20 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): else: results = verify(po) self.verifyCache[key] = results - if not rpmUtils.arch.isMultiLibArch(): return results + if not rpmUtils.arch.isMultiLibArch(): + return results # Okay deal with a buggy yum multilib and verify packages = self.yb.rpmdb.searchNevra(name=po.name, epoch=po.epoch, ver=po.version, rel=po.release) # find all arches of pkg - if len(packages) == 1: + if len(packages) == 1: return results # No mathcing multilib packages files = set(po.returnFileEntries()) # Will be the list of common fns common = {} for p in packages: - if p != po: files = files & set(p.returnFileEntries()) + if p != po: + files = files & set(p.returnFileEntries()) for p in packages: k = (p.name, p.epoch, p.version, p.release, p.arch) self.logger.debug("Multilib Verify: comparing %s to %s" \ @@ -320,10 +327,11 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): # this fn had verify problems in all but one of the multilib # packages. That means its correct in the package that's # "on top." Therefore, this is a fake verify problem. - if fn in results: del results[fn] + if fn in results: + del results[fn] return results - + def RefreshPackages(self): """ Creates self.installed{} which is a dict of installed packages. @@ -407,12 +415,13 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): virtPkg = True self.logger.info("%s appears to be provided by:" \ % entry.get('name')) - for p in POs: self.logger.info(" %s" % p) + for p in POs: + self.logger.info(" %s" % p) for inst in instances: nevra = build_yname(entry.get('name'), inst) snevra = short_yname(nevra) - if nevra in packageCache: + if nevra in packageCache: continue # Ignore duplicate instances else: packageCache.append(nevra) @@ -436,15 +445,16 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): qtext_versions.append("I(%s)" % nevra) continue - if not pkg_checks: continue + if not pkg_checks: + continue # Check EVR if virtPkg: self.logger.debug(" Not checking version for virtual package") - _POs = [ po for po in POs ] # Make a copy + _POs = [po for po in POs] # Make a copy elif entry.get('name') == 'gpg-pubkey': - _POs = [ p for p in POs if p.version == nevra['version'] \ - and p.release == nevra['release'] ] + _POs = [p for p in POs if p.version == nevra['version'] \ + and p.release == nevra['release']] else: _POs = self.yb.rpmdb.searchNevra(**snevra) if len(_POs) == 0: @@ -452,7 +462,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): stat['version_fail'] = True # Just chose the first pkg for the error message self.logger.info(" Wrong version installed. "\ - "Want %s, but have %s" % (nevraString(nevra), + "Want %s, but have %s" % (nevraString(nevra), nevraString(POs[0]))) qtext_versions.append("U(%s)" % str(POs[0])) continue @@ -485,7 +495,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): [ig.get('name') for ig in inst.findall('Ignore')] + \ self.ignores for fn, probs in vResult.items(): - if fn in modlist: + if fn in modlist: self.logger.debug(" %s in modlist, skipping" % fn) continue if fn in ignores: @@ -499,18 +509,19 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): tmp.append((p.type, p.message)) if tmp != []: stat['verify'][fn] = tmp - + if stat['verify'] != {}: stat['verify_fail'] = True package_fail = True self.logger.debug(" Verify Problems:") for fn, probs in stat['verify'].items(): self.logger.debug(" %s" % fn) - for p in probs: self.logger.debug(" %s: %s" % p) + for p in probs: + self.logger.debug(" %s: %s" % p) if len(POs) > 0: # Is this an install only package? We just look at the first one - provides = set([ p[0] for p in POs[0].provides ] + [ POs[0].name ]) + provides = set([p[0] for p in POs[0].provides] + [POs[0].name]) install_only = len(set(self.installOnlyPkgs) & provides) > 0 else: install_only = False @@ -530,7 +541,6 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): return not package_fail - def FindExtraInstances(self, entry, POs): """ Check for installed instances that are not in the config. @@ -538,12 +548,13 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): are no Instances to remove. """ - if len(POs) == 0: return None + if len(POs) == 0: + return None name = entry.get('name') - extra_entry = Bcfg2.Client.XML.Element('Package', name=name, + extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) instances = self._buildInstances(entry) - _POs = [ p for p in POs ] # Shallow copy + _POs = [p for p in POs] # Shallow copy # Algorythm is sensitive to duplicates, check for them checked = [] @@ -565,19 +576,19 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): epoch=p.epoch, name=p.name, version=p.version, release=p.release, arch=p.arch) - if _POs == []: + if _POs == []: return None else: return extra_entry def FindExtraPackages(self): """Find extra packages.""" - packages = [ e.get('name') for e in self.getSupportedEntries() ] + packages = [e.get('name') for e in self.getSupportedEntries()] extras = [] for p in self.installed.keys(): if p not in packages: - entry = Bcfg2.Client.XML.Element('Package', name=p, + entry = Bcfg2.Client.XML.Element('Package', name=p, type=self.pkgtype) for i in self.installed[p]: inst = Bcfg2.Client.XML.SubElement(entry, 'Instance', \ @@ -608,16 +619,16 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): rel = yum.misc.keyIdToRPMVer(gpg['timestamp']) if not (ver == inst.get('version') and rel == inst.get('release')): self.logger.info("GPG key file %s does not match gpg-pubkey-%s-%s"\ - % (key_file, inst.get('version'), + % (key_file, inst.get('version'), inst.get('release'))) return False - if not yum.misc.keyInstalled(ts, gpg['keyid'], + if not yum.misc.keyInstalled(ts, gpg['keyid'], gpg['timestamp']) == 0: result = ts.pgpImportPubkey(yum.misc.procgpgkey(rawkey)) else: self.logger.debug("gpg-pubkey-%s-%s already installed"\ - % (inst.get('version'), + % (inst.get('version'), inst.get('release'))) return True diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py index 1d89e6d02..8a90e130c 100644 --- a/src/lib/Client/Tools/__init__.py +++ b/src/lib/Client/Tools/__init__.py @@ -172,8 +172,7 @@ class Tool: '''Build a list of potentially modified POSIX paths for this entry''' return [entry.get('name') for struct in self.config.getchildren() \ for entry in struct.getchildren() \ - if entry.tag in ['ConfigFile', 'SymLink', 'Directory', - 'Permissions', 'Ignore', 'Path']] + if entry.tag in ['Ignore', 'Path']] def gatherCurrentData(self, entry): """Default implementation of the information gathering routines.""" diff --git a/src/lib/Client/Tools/launchd.py b/src/lib/Client/Tools/launchd.py index a8b785016..db6d94c1b 100644 --- a/src/lib/Client/Tools/launchd.py +++ b/src/lib/Client/Tools/launchd.py @@ -2,20 +2,23 @@ __revision__ = '$Revision$' import os -import Bcfg2.Client.Tools import popen2 +import Bcfg2.Client.Tools + + class launchd(Bcfg2.Client.Tools.Tool): """Support for Mac OS X launchd services.""" __handles__ = [('Service', 'launchd')] __execs__ = ['/bin/launchctl', '/usr/bin/defaults'] name = 'launchd' - __req__ = {'Service':['name', 'status']} + __req__ = {'Service': ['name', 'status']} ''' Currently requires the path to the plist to load/unload, and Name is acually a reverse-fqdn (or the label). ''' + def __init__(self, logger, setup, config): Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) @@ -77,7 +80,6 @@ class launchd(Bcfg2.Client.Tools.Tool): return 'on' return False - def InstallService(self, entry): """Enable or disable launchd item.""" name = entry.get('name') @@ -95,18 +97,19 @@ class launchd(Bcfg2.Client.Tools.Tool): """Remove Extra launchd entries.""" pass - - def FindExtra(self): """Find Extra launchd services.""" try: - allsrv = self.cmd.run("/bin/launchctl list")[1] + allsrv = self.cmd.run("/bin/launchctl list")[1] except IndexError: allsrv = [] [allsrv.remove(svc) for svc in [entry.get("name") for entry in self.getSupportedEntries()] if svc in allsrv] - return [Bcfg2.Client.XML.Element("Service", type='launchd', name=name, status='on') for name in allsrv] + return [Bcfg2.Client.XML.Element("Service", + type='launchd', + name=name, + status='on') for name in allsrv] def BundleUpdated(self, bundle, states): """Reload launchd plist.""" @@ -126,4 +129,3 @@ class launchd(Bcfg2.Client.Tools.Tool): #only if necessary.... self.cmd.run("/bin/launchctl stop %s" % name) self.cmd.run("/bin/launchctl unload -w %s" % (self.FindPlist(entry))) - diff --git a/src/lib/Server/Admin/Bundle.py b/src/lib/Server/Admin/Bundle.py index e293c6a4f..41cd5727e 100644 --- a/src/lib/Server/Admin/Bundle.py +++ b/src/lib/Server/Admin/Bundle.py @@ -91,7 +91,7 @@ class Bundle(Bcfg2.Server.Admin.MetadataCore): tree = lxml.etree.parse(bundle_list[int(lineno)]) #Prints bundle content #print lxml.etree.tostring(tree) - names = ['Action', 'ConfigFile', 'Directory', 'Package', 'Permission', 'Service', 'SymLink'] + names = ['Action', 'Package', 'Path', 'Service'] for name in names: for node in tree.findall("//" + name): print "%s:\t%s" % (name, node.attrib["name"]) diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index 0458a4ce0..95569e3ac 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -33,8 +33,8 @@ info_regex = re.compile( \ 'encoding:(\s)*(?P<encoding>\w+)|' + 'group:(\s)*(?P<group>\S+)|' + 'important:(\s)*(?P<important>\S+)|' + - 'mtime:(\s)*(?P<mtime>\w+)$' + - '^owner:(\s)*(?P<owner>\S+)|' + + 'mtime:(\s)*(?P<mtime>\w+)|' + + 'owner:(\s)*(?P<owner>\S+)|' + 'paranoid:(\s)*(?P<paranoid>\S+)|' + 'perms:(\s)*(?P<perms>\w+)|') diff --git a/src/lib/Server/Plugins/Guppy.py b/src/lib/Server/Plugins/Guppy.py new file mode 100644 index 000000000..b217378d6 --- /dev/null +++ b/src/lib/Server/Plugins/Guppy.py @@ -0,0 +1,63 @@ +""" +This plugin is used to trace memory leaks within the bcfg2-server +process using Guppy. By default the remote debugger is started +when this plugin is enabled. The debugger can be shutoff in a running +process using "bcfg2-admin xcmd Guppy.Disable" and reenabled using +"bcfg2-admin xcmd Guppy.Enable". + +To attach the console run: + +python -c "from guppy import hpy;hpy().monitor()" + +For example: + +# python -c "from guppy import hpy;hpy().monitor()" +<Monitor> +*** Connection 1 opened *** +<Monitor> lc +CID PID ARGV + 1 25063 ['/usr/sbin/bcfg2-server', '-D', '/var/run/bcfg2-server.pid'] +<Monitor> sc 1 +Remote connection 1. To return to Monitor, type <Ctrl-C> or .<RETURN> +<Annex> int +Remote interactive console. To return to Annex, type '-'. +>>> hp.heap() +... + + +""" +import re +import Bcfg2.Server.Plugin + +class Guppy(Bcfg2.Server.Plugin.Plugin): + """Guppy is a debugging plugin to help trace memory leaks""" + name = 'Guppy' + __version__ = '$Id$' + __author__ = 'bcfg-dev@mcs.anl.gov' + + experimental = True + __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['Enable','Disable'] + + def __init__(self, core, datastore): + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) + + self.Enable() + + def Enable(self): + """Enable remote debugging""" + try: + from guppy.heapy import Remote + Remote.on() + except: + self.logger.error("Failed to create Heapy context") + raise Bcfg2.Server.Plugin.PluginInitError + + def Disable(self): + """Disable remote debugging""" + try: + from guppy.heapy import Remote + Remote.off() + except: + self.logger.error("Failed to disable Heapy") + raise Bcfg2.Server.Plugin.PluginInitError + diff --git a/src/lib/Server/Plugins/Metadata.py b/src/lib/Server/Plugins/Metadata.py index 3161a69e3..81fd3e173 100644 --- a/src/lib/Server/Plugins/Metadata.py +++ b/src/lib/Server/Plugins/Metadata.py @@ -39,14 +39,21 @@ class ClientMetadata(object): """Test to see if client is a member of group.""" return group in self.groups + def group_in_category(self, category): + for grp in self.query.all_groups_in_category(category): + if grp in self.groups: + return grp + return '' + class MetadataQuery(object): - def __init__(self, by_name, get_clients, by_groups, by_profiles, all_groups): + def __init__(self, by_name, get_clients, by_groups, by_profiles, all_groups, all_groups_in_category): # resolver is set later self.by_name = by_name self.names_by_groups = by_groups self.names_by_profiles = by_profiles self.all_clients = get_clients self.all_groups = all_groups + self.all_groups_in_category = all_groups_in_category def by_groups(self, groups): return [self.by_name(name) for name in self.names_by_groups(groups)] @@ -105,7 +112,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, lambda:self.clients.keys(), self.get_client_names_by_groups, self.get_client_names_by_profiles, - self.get_all_group_names) + self.get_all_group_names, + self.get_all_groups_in_category) @classmethod def init_repo(cls, repo, groups, os_selection, clients): @@ -592,6 +600,12 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, [all_groups.update(g[1]) for g in self.groups.values()] return all_groups + def get_all_groups_in_category(self, category): + all_groups = set() + [all_groups.add(g) for g in self.categories \ + if self.categories[g] == category] + return all_groups + def get_client_names_by_profiles(self, profiles): return [client for client, profile in self.clients.iteritems() \ if profile in profiles] diff --git a/src/lib/Server/Plugins/POSIXCompat.py b/src/lib/Server/Plugins/POSIXCompat.py deleted file mode 100644 index fc16e4b48..000000000 --- a/src/lib/Server/Plugins/POSIXCompat.py +++ /dev/null @@ -1,38 +0,0 @@ -"""This plugin provides a compatibility layer which turns new-style -POSIX entries into old-style entries. -""" - -__revision__ = '$Revision$' - -import Bcfg2.Server.Plugin - -COMPAT_DICT = {'file': 'ConfigFile', - 'directory': 'Directory', - 'permissions': 'Permissions', - 'symlink': 'SymLink'} - - -class POSIXCompat(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.GoalValidator): - """POSIXCompat is a goal validator plugin for POSIX entries.""" - name = 'POSIXCompat' - __version__ = '$Id$' - __author__ = 'bcfg-dev@mcs.anl.gov' - - def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.GoalValidator.__init__(self) - - def validate_goals(self, metadata, goals): - """Verify that we are generating correct old - Cfg/Directory/Symlink entries. - """ - for goal in goals: - for entry in goal.getchildren(): - if entry.tag == 'Path' and \ - entry.get('type') not in ['nonexistent', 'device']: - # Use new entry 'type' attribute to map old entry tags - oldentry = entry - entry.tag = COMPAT_DICT[entry.get('type')] - del entry.attrib['type'] - goal.replace(oldentry, entry) diff --git a/src/lib/Server/Plugins/__init__.py b/src/lib/Server/Plugins/__init__.py index 758f1219d..c69c37452 100644 --- a/src/lib/Server/Plugins/__init__.py +++ b/src/lib/Server/Plugins/__init__.py @@ -22,7 +22,6 @@ __all__ = [ 'Properties', 'Probes', 'Pkgmgr', - 'POSIXCompat', 'Rules', 'SSHbase', 'Snapshots', diff --git a/src/lib/Server/Reports/reports/templates/base.html b/src/lib/Server/Reports/reports/templates/base.html index 7a36c9893..9bd9da218 100644 --- a/src/lib/Server/Reports/reports/templates/base.html +++ b/src/lib/Server/Reports/reports/templates/base.html @@ -1,3 +1,5 @@ +{% load bcfg2_tags %} + <?xml version="1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> @@ -10,19 +12,19 @@ <meta name="robots" content="noindex, nofollow" /> <meta http-equiv="cache-control" content="no-cache" /> -<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}/bcfg2_base.css" media="all" /> -<script type="text/javascript" src="{{ MEDIA_URL }}/bcfg2.js"></script> -<script type="text/javascript" src="{{ MEDIA_URL }}/date.js"></script> -<script type="text/javascript" src="{{ MEDIA_URL }}/AnchorPosition.js"></script> -<script type="text/javascript" src="{{ MEDIA_URL }}/CalendarPopup.js"></script> -<script type="text/javascript" src="{{ MEDIA_URL }}/PopupWindow.js"></script> +<link rel="stylesheet" type="text/css" href="{% to_media_url bcfg2_base.css %}" media="all" /> +<script type="text/javascript" src="{% to_media_url bcfg2.js %}"></script> +<script type="text/javascript" src="{% to_media_url date.js %}"></script> +<script type="text/javascript" src="{% to_media_url AnchorPosition.js %}"></script> +<script type="text/javascript" src="{% to_media_url CalendarPopup.js %}"></script> +<script type="text/javascript" src="{% to_media_url PopupWindow.js %}"></script> {% block extra_header_info %}{% endblock %} </head> <body onload="{% block body_onload %}{% endblock %}"> <div id="header"> - <a href="http://bcfg2.org"><img src='{{ MEDIA_URL }}/bcfg2_logo.png' + <a href="http://bcfg2.org"><img src='{% to_media_url bcfg2_logo.png %}' height='115' width='300' alt='Bcfg2' style='float:left; height: 115px' /></a> </div> diff --git a/src/lib/Server/Reports/reports/templates/config_items/item.html b/src/lib/Server/Reports/reports/templates/config_items/item.html index 41474922b..58aed1684 100644 --- a/src/lib/Server/Reports/reports/templates/config_items/item.html +++ b/src/lib/Server/Reports/reports/templates/config_items/item.html @@ -88,7 +88,7 @@ div.entry_list h3 { <div class='entry_list'> <div class='entry_list_head'> - <h3>Occurances on {{ timestamp|date:"Y-m-d" }}</h3> + <h3>Occurences on {{ timestamp|date:"Y-m-d" }}</h3> </div> {% if associated_list %} <table class="entry_list" cellpadding="3"> diff --git a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py index 2c27aab04..7fffe289d 100644 --- a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py +++ b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py @@ -18,7 +18,7 @@ def page_navigator(context): """ fragment = dict() try: - path = context['request'].path + path = context['request'].META['PATH_INFO'] total_pages = int(context['total_pages']) records_per_page = int(context['records_per_page']) except KeyError, e: @@ -96,7 +96,7 @@ def page_navigator(context): @register.inclusion_tag('widgets/filter_bar.html', takes_context=True) def filter_navigator(context): try: - path = context['request'].path + path = context['request'].META['PATH_INFO'] view, args, kwargs = resolve(path) # Strip any page limits and numbers @@ -179,7 +179,7 @@ class AddUrlFilter(template.Node): def render(self, context): link = '#' try: - path = context['request'].path + path = context['request'].META['PATH_INFO'] view, args, kwargs = resolve(path) filter_value = self.filter_value.resolve(context, True) if filter_value: @@ -237,3 +237,38 @@ def sortwell(value): configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) return configItems +class MediaTag(template.Node): + def __init__(self, filter_value): + self.filter_value = filter_value + + def render(self, context): + base = context['MEDIA_URL'] + try: + request = context['request'] + try: + base = request.environ['bcfg2.media_url'] + except: + if request.path != request.META['PATH_INFO']: + offset = request.path.find(request.META['PATH_INFO']) + if offset > 0: + base = "%s/%s" % (request.path[:offset], \ + context['MEDIA_URL'].strip('/')) + except: + pass + return "%s/%s" % (base, self.filter_value) + +@register.tag +def to_media_url(parser, token): + """ + Return a url relative to the media_url. + + {% to_media_url /bcfg2.css %} + """ + try: + tag_name, filter_value = token.split_contents() + filter_value = parser.compile_filter(filter_value) + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0] + + return MediaTag(filter_value) + diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Server/Reports/reports/views.py index a061a3964..00d35c092 100644 --- a/src/lib/Server/Reports/reports/views.py +++ b/src/lib/Server/Reports/reports/views.py @@ -46,7 +46,7 @@ def timeview(fn): if cal_date.find(' ') > -1: fmt += " %H:%M" timestamp = datetime(*strptime(cal_date, fmt)[0:6]) - view, args, kw = resolve(request.path) + view, args, kw = resolve(request.META['PATH_INFO']) kw['year'] = "%0.4d" % timestamp.year kw['month'] = "%02.d" % timestamp.month kw['day'] = "%02.d" % timestamp.day @@ -367,7 +367,7 @@ def prepare_paginated_list(request, context, paged_list, page=1, max_results=25) if page > total_pages: # If we passed beyond the end send back try: - view, args, kwargs = resolve(request.path) + view, args, kwargs = resolve(request.META['PATH_INFO']) kwargs['page_number'] = total_pages raise PaginationError, HttpResponseRedirect( reverse(view, kwargs=kwargs) ) except (Resolver404, NoReverseMatch, ValueError): diff --git a/src/lib/Server/Reports/urls.py b/src/lib/Server/Reports/urls.py index 5d298c974..d7ff1eee5 100644 --- a/src/lib/Server/Reports/urls.py +++ b/src/lib/Server/Reports/urls.py @@ -3,24 +3,12 @@ from django.http import HttpResponsePermanentRedirect handler500 = 'Bcfg2.Server.Reports.reports.views.server_error' -from ConfigParser import ConfigParser, NoSectionError, NoOptionError -c = ConfigParser() -c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']) - -# web_prefix should have a trailing slash, but no leading slash -# e.g. web_prefix = bcfg2/ -# web_prefix_root is a workaround for the index -if c.has_option('statistics', 'web_prefix'): - web_prefix = c.get('statistics', 'web_prefix').lstrip('/') -else: - web_prefix = '' - urlpatterns = patterns('', - (r'^%s' % web_prefix, include('Bcfg2.Server.Reports.reports.urls')) + (r'^', include('Bcfg2.Server.Reports.reports.urls')) ) -urlpatterns += patterns("django.views", - url(r"media/(?P<path>.*)$", "static.serve", { - "document_root": '/Users/tlaszlo/svn/bcfg2/reports/site_media/', - }) -) +#urlpatterns += patterns("django.views", +# url(r"media/(?P<path>.*)$", "static.serve", { +# "document_root": '/Users/tlaszlo/svn/bcfg2/reports/site_media/', +# }) +#) diff --git a/src/sbin/bcfg2-repo-validate b/src/sbin/bcfg2-repo-validate index 1889c9892..3d5efb093 100755 --- a/src/sbin/bcfg2-repo-validate +++ b/src/sbin/bcfg2-repo-validate @@ -88,14 +88,14 @@ if __name__ == '__main__': # (as defined in doc/server/configurationentries) # TODO: See if it is possible to do this in the schema instead required_configuration_attrs = { - 'device':['name', 'owner', 'group', 'dev_type'], - 'directory':['name', 'owner', 'group', 'perms'], - 'file':['name', 'owner', 'group', 'perms'], - 'hardlink':['name', 'to'], - 'symlink':['name', 'to'], - 'ignore':['name'], - 'nonexist':['name'], - 'permissions':['name', 'owner', 'group', 'perms']} + 'device': ['name', 'owner', 'group', 'dev_type'], + 'directory': ['name', 'owner', 'group', 'perms'], + 'file': ['name', 'owner', 'group', 'perms'], + 'hardlink': ['name', 'to'], + 'symlink': ['name', 'to'], + 'ignore': ['name'], + 'nonexist': ['name'], + 'permissions': ['name', 'owner', 'group', 'perms']} for rfile in rules_list: try: xdata = lxml.etree.parse(rfile) @@ -110,6 +110,11 @@ if __name__ == '__main__': + ['type']) except KeyError: continue + if 'dev_type' in required_attrs: + dev_type = posixpath.get('dev_type') + if dev_type in ['block', 'char']: + # check if major/minor are specified + required_attrs |= set(['major', 'minor']) if pathset.issuperset(required_attrs): continue else: @@ -146,16 +151,16 @@ if __name__ == '__main__': else: pset.add(ptuple) - filesets = {'metadata':(metadata_list, "%s/metadata.xsd"), - 'clients':(clients_list, "%s/clients.xsd"), - 'info':(info_list, "%s/info.xsd"), - 'bundle':(bundle_list, "%s/bundle.xsd"), - 'pkglist':(pkg_list, "%s/pkglist.xsd"), - 'base':(base_list, "%s/base.xsd"), - 'rules':(rules_list, "%s/rules.xsd"), - 'imageinfo':(imageinfo_list, "%s/report-configuration.xsd"), - 'services':(services_list, "%s/services.xsd"), - 'deps':(deps_list, "%s/deps.xsd"), + filesets = {'metadata': (metadata_list, "%s/metadata.xsd"), + 'clients': (clients_list, "%s/clients.xsd"), + 'info': (info_list, "%s/info.xsd"), + 'bundle': (bundle_list, "%s/bundle.xsd"), + 'pkglist': (pkg_list, "%s/pkglist.xsd"), + 'base': (base_list, "%s/base.xsd"), + 'rules': (rules_list, "%s/rules.xsd"), + 'imageinfo': (imageinfo_list, "%s/report-configuration.xsd"), + 'services': (services_list, "%s/services.xsd"), + 'deps': (deps_list, "%s/deps.xsd"), 'decisions': (dec_list, "%s/decisions.xsd"), 'packages': (pkgcfg_list, "%s/packages.xsd"), 'grouppatterns': (gp_list, "%s/grouppatterns.xsd"), |